标签归档:mutable

Python中存在可变的命名元组吗?

问题:Python中存在可变的命名元组吗?

任何人都可以修改namedtuple或提供替代类,以使其适用于可变对象吗?

主要是为了提高可读性,我想要执行类似于namedtuple的操作:

from Camelot import namedgroup

Point = namedgroup('Point', ['x', 'y'])
p = Point(0, 0)
p.x = 10

>>> p
Point(x=10, y=0)

>>> p.x *= 10
Point(x=100, y=0)

腌制所得物体必须是可能的。并且根据命名元组的特征,在表示对象时输出的顺序必须与构造对象时参数列表的顺序相匹配。

Can anyone amend namedtuple or provide an alternative class so that it works for mutable objects?

Primarily for readability, I would like something similar to namedtuple that does this:

from Camelot import namedgroup

Point = namedgroup('Point', ['x', 'y'])
p = Point(0, 0)
p.x = 10

>>> p
Point(x=10, y=0)

>>> p.x *= 10
Point(x=100, y=0)

It must be possible to pickle the resulting object. And per the characteristics of named tuple, the ordering of the output when represented must match the order of the parameter list when constructing the object.


回答 0

还有就是一个可变的替代方案collections.namedtuplerecordclass

它具有与API相同的API和内存占用量,namedtuple并且支持分配(它也应该更快)。例如:

from recordclass import recordclass

Point = recordclass('Point', 'x y')

>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

对于python 3.6及更高版本recordclass(从0.5开始)支持typehints:

from recordclass import recordclass, RecordClass

class Point(RecordClass):
   x: int
   y: int

>>> Point.__annotations__
{'x':int, 'y':int}
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

有一个更完整的示例(还包括性能比较)。

由于0.9 recordclass库提供了另一个变体- recordclass.structclass工厂功能。它可以产生类,其实例比__slots__基于实例的实例占用更少的内存。这对于具有属性值的实例非常重要,该属性值不打算具有参考周期。如果您需要创建数百万个实例,则可能有助于减少内存使用。这是一个说明性的例子

There is a mutable alternative to collections.namedtuplerecordclass.

It has the same API and memory footprint as namedtuple and it supports assignments (It should be faster as well). For example:

from recordclass import recordclass

Point = recordclass('Point', 'x y')

>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

For python 3.6 and higher recordclass (since 0.5) support typehints:

from recordclass import recordclass, RecordClass

class Point(RecordClass):
   x: int
   y: int

>>> Point.__annotations__
{'x':int, 'y':int}
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

There is a more complete example (it also includes performance comparisons).

Since 0.9 recordclass library provides another variant — recordclass.structclass factory function. It can produce classes, whose instances occupy less memory than __slots__-based instances. This is can be important for the instances with attribute values, which has not intended to have reference cycles. It may help reduce memory usage if you need to create millions of instances. Here is an illustrative example.


回答 1

types.SimpleNamespace在Python 3.3中引入,并支持所要求的要求。

from types import SimpleNamespace
t = SimpleNamespace(foo='bar')
t.ham = 'spam'
print(t)
namespace(foo='bar', ham='spam')
print(t.foo)
'bar'
import pickle
with open('/tmp/pickle', 'wb') as f:
    pickle.dump(t, f)

types.SimpleNamespace was introduced in Python 3.3 and supports the requested requirements.

from types import SimpleNamespace
t = SimpleNamespace(foo='bar')
t.ham = 'spam'
print(t)
namespace(foo='bar', ham='spam')
print(t.foo)
'bar'
import pickle
with open('/tmp/pickle', 'wb') as f:
    pickle.dump(t, f)

回答 2

作为此任务的一种非常Pythonic的替代方法,从Python-3.7开始,您可以使用 dataclasses不仅行为可变的模块,NamedTuple因为它们使用常规的类定义,而且还支持其他类功能。

从PEP-0557:

尽管它们使用了非常不同的机制,但是可以将数据类视为“具有默认值的可变命名元组”。因为数据类使用普通的类定义语法,所以您可以自由使用继承,元类,文档字符串,用户定义的方法,类工厂和其他Python类功能。

提供了一个类装饰器,该类装饰器检查类定义中具有类型注释的变量,如PEP 526 “变量注释的语法”中所定义。在本文档中,此类变量称为字段。装饰器使用这些字段将生成的方法定义添加到类中,以支持实例初始化,repr,比较方法以及(可选)规范部分中描述的其他方法。这样的类称为数据类,但该类实际上没有什么特别的:装饰器将生成的方法添加到该类中并返回给定的相同类。

PEP-0557中引入了此功能,您可以在提供的文档链接上详细了解它。

例:

In [20]: from dataclasses import dataclass

In [21]: @dataclass
    ...: class InventoryItem:
    ...:     '''Class for keeping track of an item in inventory.'''
    ...:     name: str
    ...:     unit_price: float
    ...:     quantity_on_hand: int = 0
    ...: 
    ...:     def total_cost(self) -> float:
    ...:         return self.unit_price * self.quantity_on_hand
    ...:    

演示:

In [23]: II = InventoryItem('bisc', 2000)

In [24]: II
Out[24]: InventoryItem(name='bisc', unit_price=2000, quantity_on_hand=0)

In [25]: II.name = 'choco'

In [26]: II.name
Out[26]: 'choco'

In [27]: 

In [27]: II.unit_price *= 3

In [28]: II.unit_price
Out[28]: 6000

In [29]: II
Out[29]: InventoryItem(name='choco', unit_price=6000, quantity_on_hand=0)

As a very Pythonic alternative for this task, since Python-3.7, you can use dataclasses module that not only behaves like a mutable NamedTuple because they use normal class definitions they also support other classes features.

From PEP-0557:

Although they use a very different mechanism, Data Classes can be thought of as “mutable namedtuples with defaults”. Because Data Classes use normal class definition syntax, you are free to use inheritance, metaclasses, docstrings, user-defined methods, class factories, and other Python class features.

A class decorator is provided which inspects a class definition for variables with type annotations as defined in PEP 526, “Syntax for Variable Annotations”. In this document, such variables are called fields. Using these fields, the decorator adds generated method definitions to the class to support instance initialization, a repr, comparison methods, and optionally other methods as described in the Specification section. Such a class is called a Data Class, but there’s really nothing special about the class: the decorator adds generated methods to the class and returns the same class it was given.

This feature is introduced in PEP-0557 that you can read about it in more details on provided documentation link.

Example:

In [20]: from dataclasses import dataclass

In [21]: @dataclass
    ...: class InventoryItem:
    ...:     '''Class for keeping track of an item in inventory.'''
    ...:     name: str
    ...:     unit_price: float
    ...:     quantity_on_hand: int = 0
    ...: 
    ...:     def total_cost(self) -> float:
    ...:         return self.unit_price * self.quantity_on_hand
    ...:    

Demo:

In [23]: II = InventoryItem('bisc', 2000)

In [24]: II
Out[24]: InventoryItem(name='bisc', unit_price=2000, quantity_on_hand=0)

In [25]: II.name = 'choco'

In [26]: II.name
Out[26]: 'choco'

In [27]: 

In [27]: II.unit_price *= 3

In [28]: II.unit_price
Out[28]: 6000

In [29]: II
Out[29]: InventoryItem(name='choco', unit_price=6000, quantity_on_hand=0)

回答 3

截至2016 1月11日,最新的namedlist 1.7通过了Python 2.7和Python 3.5的所有测试它是纯python实现,recordclassC是C扩展。当然,是否需要C扩展名取决于您的要求。

您的测试(也请参见下面的注释):

from __future__ import print_function
import pickle
import sys
from namedlist import namedlist

Point = namedlist('Point', 'x y')
p = Point(x=1, y=2)

print('1. Mutation of field values')
p.x *= 10
p.y += 10
print('p: {}, {}\n'.format(p.x, p.y))

print('2. String')
print('p: {}\n'.format(p))

print('3. Representation')
print(repr(p), '\n')

print('4. Sizeof')
print('size of p:', sys.getsizeof(p), '\n')

print('5. Access by name of field')
print('p: {}, {}\n'.format(p.x, p.y))

print('6. Access by index')
print('p: {}, {}\n'.format(p[0], p[1]))

print('7. Iterative unpacking')
x, y = p
print('p: {}, {}\n'.format(x, y))

print('8. Iteration')
print('p: {}\n'.format([v for v in p]))

print('9. Ordered Dict')
print('p: {}\n'.format(p._asdict()))

print('10. Inplace replacement (update?)')
p._update(x=100, y=200)
print('p: {}\n'.format(p))

print('11. Pickle and Unpickle')
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
assert p == unpickled
print('Pickled successfully\n')

print('12. Fields\n')
print('p: {}\n'.format(p._fields))

print('13. Slots')
print('p: {}\n'.format(p.__slots__))

在Python 2.7上输出

1.字段值的突变  
p:10、12

2.字符串  
p:点(x = 10,y = 12)

3.陈述  
点(x = 10,y = 12) 

4. Sizeof  
p的大小:64 

5.按字段名称访问  
p:10、12

6.按索引访问  
p:10、12

7.迭代拆包  
p:10、12

8.迭代  
p:[10、12]

9.有序词典  
p:OrderedDict([['x',10),('y',12)])

10.就地更换(更新?)  
p:点(x = 100,y = 200)

11.泡菜和腌菜  
腌制成功

12.领域  
p:('x','y')

13.插槽  
p:('x','y')

与Python 3.5的唯一区别是namedlist变得更小,大小为56(Python 2.7报告64)。

请注意,我已将您的测试10更改为就地更换。namedlist_replace()哪些做了浅拷贝的方法,这使我感觉良好,因为namedtuple在标准库的工作方式。更改_replace()方法的语义会造成混乱。我认为该_update()方法应用于就地更新。还是我无法理解您的测试10的意图?

The latest namedlist 1.7 passes all of your tests with both Python 2.7 and Python 3.5 as of Jan 11, 2016. It is a pure python implementation whereas the recordclass is a C extension. Of course, it depends on your requirements whether a C extension is preferred or not.

Your tests (but also see the note below):

from __future__ import print_function
import pickle
import sys
from namedlist import namedlist

Point = namedlist('Point', 'x y')
p = Point(x=1, y=2)

print('1. Mutation of field values')
p.x *= 10
p.y += 10
print('p: {}, {}\n'.format(p.x, p.y))

print('2. String')
print('p: {}\n'.format(p))

print('3. Representation')
print(repr(p), '\n')

print('4. Sizeof')
print('size of p:', sys.getsizeof(p), '\n')

print('5. Access by name of field')
print('p: {}, {}\n'.format(p.x, p.y))

print('6. Access by index')
print('p: {}, {}\n'.format(p[0], p[1]))

print('7. Iterative unpacking')
x, y = p
print('p: {}, {}\n'.format(x, y))

print('8. Iteration')
print('p: {}\n'.format([v for v in p]))

print('9. Ordered Dict')
print('p: {}\n'.format(p._asdict()))

print('10. Inplace replacement (update?)')
p._update(x=100, y=200)
print('p: {}\n'.format(p))

print('11. Pickle and Unpickle')
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
assert p == unpickled
print('Pickled successfully\n')

print('12. Fields\n')
print('p: {}\n'.format(p._fields))

print('13. Slots')
print('p: {}\n'.format(p.__slots__))

Output on Python 2.7

1. Mutation of field values  
p: 10, 12

2. String  
p: Point(x=10, y=12)

3. Representation  
Point(x=10, y=12) 

4. Sizeof  
size of p: 64 

5. Access by name of field  
p: 10, 12

6. Access by index  
p: 10, 12

7. Iterative unpacking  
p: 10, 12

8. Iteration  
p: [10, 12]

9. Ordered Dict  
p: OrderedDict([('x', 10), ('y', 12)])

10. Inplace replacement (update?)  
p: Point(x=100, y=200)

11. Pickle and Unpickle  
Pickled successfully

12. Fields  
p: ('x', 'y')

13. Slots  
p: ('x', 'y')

The only difference with Python 3.5 is that the namedlist has become smaller, the size is 56 (Python 2.7 reports 64).

Note that I have changed your test 10 for in-place replacement. The namedlist has a _replace() method which does a shallow copy, and that makes perfect sense to me because the namedtuple in the standard library behaves the same way. Changing the semantics of the _replace() method would be confusing. In my opinion the _update() method should be used for in-place updates. Or maybe I failed to understand the intent of your test 10?


回答 4

看来这个问题的答案是否定的。

下面的内容非常接近,但从技术上讲并不是可变的。这将创建一个namedtuple()具有更新的x值的新实例:

Point = namedtuple('Point', ['x', 'y'])
p = Point(0, 0)
p = p._replace(x=10) 

另一方面,您可以使用创建一个简单的类__slots__,该类应该可以很好地用于频繁更新类实例属性:

class Point:
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        self.x = x
        self.y = y

为了补充这个答案,我认为__slots__在这里很好用,因为当您创建许多类实例时,它的内存使用效率很高。唯一的缺点是您不能创建新的类属性。

这是一个说明内存效率的相关线程 -Dictionary vs Object-效率更高,为什么?

该线程答案中引用的内容非常简洁地解释了为什么__slots__内存效率更高-Python插槽

It seems like the answer to this question is no.

Below is pretty close, but it’s not technically mutable. This is creating a new namedtuple() instance with an updated x value:

Point = namedtuple('Point', ['x', 'y'])
p = Point(0, 0)
p = p._replace(x=10) 

On the other hand, you can create a simple class using __slots__ that should work well for frequently updating class instance attributes:

class Point:
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        self.x = x
        self.y = y

To add to this answer, I think __slots__ is good use here because it’s memory efficient when you create lots of class instances. The only downside is that you can’t create new class attributes.

Here’s one relevant thread that illustrates the memory efficiency – Dictionary vs Object – which is more efficient and why?

The quoted content in the answer of this thread is a very succinct explanation why __slots__ is more memory efficient – Python slots


回答 5

以下是适用于Python 3的良好解决方案:最小类使用__slots__Sequence抽象基类;不会执行类似的错误检测,但它可以工作,并且其行为基本上类似于可变元组(类型检查除外)。

from collections import Sequence

class NamedMutableSequence(Sequence):
    __slots__ = ()

    def __init__(self, *a, **kw):
        slots = self.__slots__
        for k in slots:
            setattr(self, k, kw.get(k))

        if a:
            for k, v in zip(slots, a):
                setattr(self, k, v)

    def __str__(self):
        clsname = self.__class__.__name__
        values = ', '.join('%s=%r' % (k, getattr(self, k))
                           for k in self.__slots__)
        return '%s(%s)' % (clsname, values)

    __repr__ = __str__

    def __getitem__(self, item):
        return getattr(self, self.__slots__[item])

    def __setitem__(self, item, value):
        return setattr(self, self.__slots__[item], value)

    def __len__(self):
        return len(self.__slots__)

class Point(NamedMutableSequence):
    __slots__ = ('x', 'y')

例:

>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)

如果需要,您也可以使用一种方法来创建类(尽管使用显式类更为透明):

def namedgroup(name, members):
    if isinstance(members, str):
        members = members.split()
    members = tuple(members)
    return type(name, (NamedMutableSequence,), {'__slots__': members})

例:

>>> Point = namedgroup('Point', ['x', 'y'])
>>> Point(6, 42)
Point(x=6, y=42)

在Python 2中,您需要稍作调整-如果您从继承Sequence,则该类将具有__dict____slots__将停止工作。

Python 2中的解决方案是不继承Sequence,而是继承object。如果isinstance(Point, Sequence) == True需要,您需要将NamedMutableSequence作为基本类注册 到Sequence

Sequence.register(NamedMutableSequence)

The following is a good solution for Python 3: A minimal class using __slots__ and Sequence abstract base class; does not do fancy error detection or such, but it works, and behaves mostly like a mutable tuple (except for typecheck).

from collections import Sequence

class NamedMutableSequence(Sequence):
    __slots__ = ()

    def __init__(self, *a, **kw):
        slots = self.__slots__
        for k in slots:
            setattr(self, k, kw.get(k))

        if a:
            for k, v in zip(slots, a):
                setattr(self, k, v)

    def __str__(self):
        clsname = self.__class__.__name__
        values = ', '.join('%s=%r' % (k, getattr(self, k))
                           for k in self.__slots__)
        return '%s(%s)' % (clsname, values)

    __repr__ = __str__

    def __getitem__(self, item):
        return getattr(self, self.__slots__[item])

    def __setitem__(self, item, value):
        return setattr(self, self.__slots__[item], value)

    def __len__(self):
        return len(self.__slots__)

class Point(NamedMutableSequence):
    __slots__ = ('x', 'y')

Example:

>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)

If you want, you can have a method to create the class too (though using an explicit class is more transparent):

def namedgroup(name, members):
    if isinstance(members, str):
        members = members.split()
    members = tuple(members)
    return type(name, (NamedMutableSequence,), {'__slots__': members})

Example:

>>> Point = namedgroup('Point', ['x', 'y'])
>>> Point(6, 42)
Point(x=6, y=42)

In Python 2 you need to adjust it slightly – if you inherit from Sequence, the class will have a __dict__ and the __slots__ will stop from working.

The solution in Python 2 is to not inherit from Sequence, but object. If isinstance(Point, Sequence) == True is desired, you need to register the NamedMutableSequence as a base class to Sequence:

Sequence.register(NamedMutableSequence)

回答 6

让我们通过动态类型创建来实现这一点:

import copy
def namedgroup(typename, fieldnames):

    def init(self, **kwargs): 
        attrs = {k: None for k in self._attrs_}
        for k in kwargs:
            if k in self._attrs_:
                attrs[k] = kwargs[k]
            else:
                raise AttributeError('Invalid Field')
        self.__dict__.update(attrs)

    def getattribute(self, attr):
        if attr.startswith("_") or attr in self._attrs_:
            return object.__getattribute__(self, attr)
        else:
            raise AttributeError('Invalid Field')

    def setattr(self, attr, value):
        if attr in self._attrs_:
            object.__setattr__(self, attr, value)
        else:
            raise AttributeError('Invalid Field')

    def rep(self):
         d = ["{}={}".format(v,self.__dict__[v]) for v in self._attrs_]
         return self._typename_ + '(' + ', '.join(d) + ')'

    def iterate(self):
        for x in self._attrs_:
            yield self.__dict__[x]
        raise StopIteration()

    def setitem(self, *args, **kwargs):
        return self.__dict__.__setitem__(*args, **kwargs)

    def getitem(self, *args, **kwargs):
        return self.__dict__.__getitem__(*args, **kwargs)

    attrs = {"__init__": init,
                "__setattr__": setattr,
                "__getattribute__": getattribute,
                "_attrs_": copy.deepcopy(fieldnames),
                "_typename_": str(typename),
                "__str__": rep,
                "__repr__": rep,
                "__len__": lambda self: len(fieldnames),
                "__iter__": iterate,
                "__setitem__": setitem,
                "__getitem__": getitem,
                }

    return type(typename, (object,), attrs)

这将在允许操作继续之前检查属性以查看它们是否有效。

那么,这是可腌制的吗?是(且仅当您执行以下操作时):

>>> import pickle
>>> Point = namedgroup("Point", ["x", "y"])
>>> p = Point(x=100, y=200)
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.x
100
>>> p2.y
200
>>> id(p) != id(p2)
True

该定义必须在您的命名空间中,并且必须存在足够长的时间,以便pickle可以找到它。因此,如果您将其定义在包中,则应该可以使用。

Point = namedgroup("Point", ["x", "y"])

如果您执行以下操作,或者将定义设为临时定义,则Pickle将失败(例如,函数结束时超出范围):

some_point = namedgroup("Point", ["x", "y"])

是的,它确实保留了类型创建中列出的字段的顺序。

Let’s implement this with dynamic type creation:

import copy
def namedgroup(typename, fieldnames):

    def init(self, **kwargs): 
        attrs = {k: None for k in self._attrs_}
        for k in kwargs:
            if k in self._attrs_:
                attrs[k] = kwargs[k]
            else:
                raise AttributeError('Invalid Field')
        self.__dict__.update(attrs)

    def getattribute(self, attr):
        if attr.startswith("_") or attr in self._attrs_:
            return object.__getattribute__(self, attr)
        else:
            raise AttributeError('Invalid Field')

    def setattr(self, attr, value):
        if attr in self._attrs_:
            object.__setattr__(self, attr, value)
        else:
            raise AttributeError('Invalid Field')

    def rep(self):
         d = ["{}={}".format(v,self.__dict__[v]) for v in self._attrs_]
         return self._typename_ + '(' + ', '.join(d) + ')'

    def iterate(self):
        for x in self._attrs_:
            yield self.__dict__[x]
        raise StopIteration()

    def setitem(self, *args, **kwargs):
        return self.__dict__.__setitem__(*args, **kwargs)

    def getitem(self, *args, **kwargs):
        return self.__dict__.__getitem__(*args, **kwargs)

    attrs = {"__init__": init,
                "__setattr__": setattr,
                "__getattribute__": getattribute,
                "_attrs_": copy.deepcopy(fieldnames),
                "_typename_": str(typename),
                "__str__": rep,
                "__repr__": rep,
                "__len__": lambda self: len(fieldnames),
                "__iter__": iterate,
                "__setitem__": setitem,
                "__getitem__": getitem,
                }

    return type(typename, (object,), attrs)

This checks the attributes to see if they are valid before allowing the operation to continue.

So is this pickleable? Yes if (and only if) you do the following:

>>> import pickle
>>> Point = namedgroup("Point", ["x", "y"])
>>> p = Point(x=100, y=200)
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.x
100
>>> p2.y
200
>>> id(p) != id(p2)
True

The definition has to be in your namespace, and must exist long enough for pickle to find it. So if you define this to be in your package, it should work.

Point = namedgroup("Point", ["x", "y"])

Pickle will fail if you do the following, or make the definition temporary (goes out of scope when the function ends, say):

some_point = namedgroup("Point", ["x", "y"])

And yes, it does preserve the order of the fields listed in the type creation.


回答 7

根据定义,元组是不可变的。

但是,您可以创建一个字典子类,在其中可以使用点符号访问属性。

In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class AttrDict(dict):
:
:    def __getattr__(self, name):
:        return self[name]
:
:    def __setattr__(self, name, value):
:        self[name] = value
:--

In [2]: test = AttrDict()

In [3]: test.a = 1

In [4]: test.b = True

In [5]: test
Out[5]: {'a': 1, 'b': True}

Tuples are by definition immutable.

You can however make a dictionary subclass where you can access the attributes with dot-notation;

In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class AttrDict(dict):
:
:    def __getattr__(self, name):
:        return self[name]
:
:    def __setattr__(self, name, value):
:        self[name] = value
:--

In [2]: test = AttrDict()

In [3]: test.a = 1

In [4]: test.b = True

In [5]: test
Out[5]: {'a': 1, 'b': True}

回答 8

如果您想要与namedtuples类似的行为但可变,请尝试namedlist

注意,为了可变,它不能是元组。

If you want similar behavior as namedtuples but mutable try namedlist

Note that in order to be mutable it cannot be a tuple.


回答 9

如果性能并不重要,则可以使用如下愚蠢的方法:

from collection import namedtuple

Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])

Provided performance is of little importance, one could use a silly hack like:

from collection import namedtuple

Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])

不可变与可变类型

问题:不可变与可变类型

我对什么是不可变类型感到困惑。我知道该float对象被认为是不可变的,在我的书中有这样的例子:

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2))

由于类的结构/层次结构,这是否被认为是不可变的?意思float是在类的顶部,是它自己的方法调用。类似于此类示例(即使我的书说的dict是可变的):

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

可变的东西在类内部具有方法,例如以下类型:

class SortedKeyDict_a(dict):
    def example(self):
        return self.keys()

另外,对于最后一个class(SortedKeyDict_a),如果我将这种类型的set传递给它:

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

不调用该example方法,它返回一个字典。在SortedKeyDict__new__其标记为错误。我尝试使用将整数传递给RoundFloat类,__new__并且它未标记任何错误。

I’m confused on what an immutable type is. I know the float object is considered to be immutable, with this type of example from my book:

class RoundFloat(float):
    def __new__(cls, val):
        return float.__new__(cls, round(val, 2))

Is this considered to be immutable because of the class structure / hierarchy?, meaning float is at the top of the class and is its own method call. Similar to this type of example (even though my book says dict is mutable):

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

Whereas something mutable has methods inside the class, with this type of example:

class SortedKeyDict_a(dict):
    def example(self):
        return self.keys()

Also, for the last class(SortedKeyDict_a), if I pass this type of set to it:

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

without calling the example method, it returns a dictionary. The SortedKeyDict with __new__ flags it as an error. I tried passing integers to the RoundFloat class with __new__ and it flagged no errors.


回答 0

什么?浮游物是一成不变的吗?但是我不能

x = 5.0
x += 7.0
print x # 12.0

那不是“ mut” x吗?

好吧,您同意字符串是不可变的,对吗?但是您可以做同样的事情。

s = 'foo'
s += 'bar'
print s # foobar

变量的值会更改,但是会通过更改变量引用的内容来更改。一个可变的类型可以改变这种方式,它可 “到位”而改变。

这是区别。

x = something # immutable type
print x
func(x)
print x # prints the same thing

x = something # mutable type
print x
func(x)
print x # might print something different

x = something # immutable type
y = x
print x
# some statement that operates on y
print x # prints the same thing

x = something # mutable type
y = x
print x
# some statement that operates on y
print x # might print something different

具体例子

x = 'foo'
y = x
print x # foo
y += 'bar'
print x # foo

x = [1, 2, 3]
y = x
print x # [1, 2, 3]
y += [3, 2, 1]
print x # [1, 2, 3, 3, 2, 1]

def func(val):
    val += 'bar'

x = 'foo'
print x # foo
func(x)
print x # foo

def func(val):
    val += [3, 2, 1]

x = [1, 2, 3]
print x # [1, 2, 3]
func(x)
print x # [1, 2, 3, 3, 2, 1]

What? Floats are immutable? But can’t I do

x = 5.0
x += 7.0
print x # 12.0

Doesn’t that “mut” x?

Well you agree strings are immutable right? But you can do the same thing.

s = 'foo'
s += 'bar'
print s # foobar

The value of the variable changes, but it changes by changing what the variable refers to. A mutable type can change that way, and it can also change “in place”.

Here is the difference.

x = something # immutable type
print x
func(x)
print x # prints the same thing

x = something # mutable type
print x
func(x)
print x # might print something different

x = something # immutable type
y = x
print x
# some statement that operates on y
print x # prints the same thing

x = something # mutable type
y = x
print x
# some statement that operates on y
print x # might print something different

Concrete examples

x = 'foo'
y = x
print x # foo
y += 'bar'
print x # foo

x = [1, 2, 3]
y = x
print x # [1, 2, 3]
y += [3, 2, 1]
print x # [1, 2, 3, 3, 2, 1]

def func(val):
    val += 'bar'

x = 'foo'
print x # foo
func(x)
print x # foo

def func(val):
    val += [3, 2, 1]

x = [1, 2, 3]
print x # [1, 2, 3]
func(x)
print x # [1, 2, 3, 3, 2, 1]

回答 1

您必须了解Python将其所有数据表示为对象。其中一些对象(例如列表和字典)是可变的,这意味着您可以更改其内容而无需更改其标识。其他对象(例如整数,浮点数,字符串和元组)是无法更改的对象。一种简单的理解方法是查看对象ID。

在下面,您将看到一个不可变的字符串。您无法更改其内容。TypeError如果您尝试更改它,它将引发一个。同样,如果我们分配新内容,则会创建一个新对象,而不是修改内容。

>>> s = "abc"
>>>id(s)
4702124
>>> s[0] 
'a'
>>> s[0] = "o"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s = "xyz"
>>>id(s)
4800100
>>> s += "uvw"
>>>id(s)
4800500

您可以使用列表来执行此操作,并且不会更改对象标识

>>> i = [1,2,3]
>>>id(i)
2146718700
>>> i[0] 
1
>>> i[0] = 7
>>> id(i)
2146718700

要阅读有关Python数据模型的更多信息,可以查看Python语言参考:

You have to understand that Python represents all its data as objects. Some of these objects like lists and dictionaries are mutable, meaning you can change their content without changing their identity. Other objects like integers, floats, strings and tuples are objects that can not be changed. An easy way to understand that is if you have a look at an objects ID.

Below you see a string that is immutable. You can not change its content. It will raise a TypeError if you try to change it. Also, if we assign new content, a new object is created instead of the contents being modified.

>>> s = "abc"
>>>id(s)
4702124
>>> s[0] 
'a'
>>> s[0] = "o"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> s = "xyz"
>>>id(s)
4800100
>>> s += "uvw"
>>>id(s)
4800500

You can do that with a list and it will not change the objects identity

>>> i = [1,2,3]
>>>id(i)
2146718700
>>> i[0] 
1
>>> i[0] = 7
>>> id(i)
2146718700

To read more about Python’s data model you could have a look at the Python language reference:


回答 2

常见的不可变类型:

  1. 数字:int()float()complex()
  2. 不可变的序列:str()tuple()frozenset()bytes()

常见的可变类型(几乎所有其他类型):

  1. 可变序列:list()bytearray()
  2. 设置类型: set()
  3. 映射类型: dict()
  4. 类,类实例
  5. 等等

快速测试类型是否可变的一种技巧是使用id()内置函数。

例如,在整数上使用

>>> i = 1
>>> id(i)
***704
>>> i += 1
>>> i
2
>>> id(i)
***736 (different from ***704)

使用清单上的

>>> a = [1]
>>> id(a)
***416
>>> a.append(2)
>>> a
[1, 2]
>>> id(a)
***416 (same with the above id)

Common immutable type:

  1. numbers: int(), float(), complex()
  2. immutable sequences: str(), tuple(), frozenset(), bytes()

Common mutable type (almost everything else):

  1. mutable sequences: list(), bytearray()
  2. set type: set()
  3. mapping type: dict()
  4. classes, class instances
  5. etc.

One trick to quickly test if a type is mutable or not, is to use id() built-in function.

Examples, using on integer,

>>> i = 1
>>> id(i)
***704
>>> i += 1
>>> i
2
>>> id(i)
***736 (different from ***704)

using on list,

>>> a = [1]
>>> id(a)
***416
>>> a.append(2)
>>> a
[1, 2]
>>> id(a)
***416 (same with the above id)

回答 3

首先,一个类是否具有方法或它的类结构与可变性无关。

ints和floats是不可变的。如果我做

a = 1
a += 5

它在第一行中将名称指向内存a中的1某个位置。在第二行,它查找的是1,添加5,获取6,然后点a那个6在记忆-它并没有改变1一个6以任何方式。使用其他不可变类型的相同逻辑适用于以下示例:

b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')

对于可变类型,我可以做一些改变其存储在内存中的值的事情。带有:

d = [1, 2, 3]

我创建的位置的列表12以及3在内存中。如果我那么做

e = d

我只是点e相同list d的点。然后,我可以这样做:

e += [4, 5]

并且指向ed指向的列表也将更新为也具有45在内存中的位置。

如果我返回一个不可变的类型,并使用tuple

f = (1, 2, 3)
g = f
g += (4, 5)

然后f仍然只指向原始的tuple -您指向g了一个全新的tuple

现在,以您的示例

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

你通过的地方

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

(这是一个tupletuples)的val,你得到一个错误,因为tuple都不具备的一个.clear()方法-你必须通过dict(d)val它工作,在这种情况下,你会得到一个空的SortedKeyDict结果。

First of all, whether a class has methods or what it’s class structure is has nothing to do with mutability.

ints and floats are immutable. If I do

a = 1
a += 5

It points the name a at a 1 somewhere in memory on the first line. On the second line, it looks up that 1, adds 5, gets 6, then points a at that 6 in memory — it didn’t change the 1 to a 6 in any way. The same logic applies to the following examples, using other immutable types:

b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')

For mutable types, I can do thing that actallly change the value where it’s stored in memory. With:

d = [1, 2, 3]

I’ve created a list of the locations of 1, 2, and 3 in memory. If I then do

e = d

I just point e to the same list d points at. I can then do:

e += [4, 5]

And the list that both e and d points at will be updated to also have the locations of 4 and 5 in memory.

If I go back to an immutable type and do that with a tuple:

f = (1, 2, 3)
g = f
g += (4, 5)

Then f still only points to the original tuple — you’ve pointed g at an entirely new tuple.

Now, with your example of

class SortedKeyDict(dict):
    def __new__(cls, val):
        return dict.__new__(cls, val.clear())

Where you pass

d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))

(which is a tuple of tuples) as val, you’re getting an error because tuples don’t have a .clear() method — you’d have to pass dict(d) as val for it to work, in which case you’ll get an empty SortedKeyDict as a result.


回答 4

如果您是从另一种语言来学习Python(除了非常像Python的一种语言,例如Ruby),并且坚持要用另一种语言来理解它,那么人们通常会感到困惑:

>>> a = 1
>>> a = 2 # I thought int was immutable, but I just changed it?!

在Python中,分配不是Python中的变异。

在C ++中,如果您编写a = 2,则在调用a.operator=(2),这将使存储在中的对象发生突变a。(如果有没有存储在对象a,这是一个错误。)

在Python中,a = 2对存储在其中的任何内容均不执行任何操作a;它只是意味着2现在存储在其中a。(如果有没有存储在对象a,这很好。)


最终,这是更深层次区别的一部分。

像C ++这样的语言中的变量是内存中的键入位置。如果aint,则表示编译器知道应将其解释为的4个字节int。因此,执行此操作后a = 2,它将存储在这4个字节的内存中的内容从0, 0, 0, 1更改为0, 0, 0, 2。如果其他地方有另一个int变量,则它有自己的4个字节。

像Python这样的语言中的变量是具有自己生命的对象的名称。有一个数字对象1,另一个有数字对象2。而且a不是4个字节的内存以表示int,它只是指向1对象的名称。a = 2将数字1转换为数字2 是没有意义的(这会给任何Python程序员太多的力量来改变宇宙的基本工作原理);相反,它所做的只是a忘了1对象,2而是指向对象。


那么,如果分配不是突变,什么突变?

  • 调用已记录为变异的方法,例如a.append(b)。(请注意,这些方法几乎总是会返回None)。不可变类型没有任何此类方法,可变类型通常具有。
  • 分配给对象的一部分,例如a.spam = ba[0] = b。不可变类型不允许分配属性或元素,可变类型通常允许一个或另一个。
  • 有时使用增强分配,例如a += b,有时不使用。可变类型通常会改变值;不可变的类型永远不会做,而是给您一个副本(它们是计算a + b,然后将结果分配给a)。

但是,如果分配不是突变,那么如何分配给对象突变的一部分呢?那就是棘手的地方。a[0] = b确实发生变异a[0](再次,与C ++),但它确实发生变异a(不像C ++,除了间接地)。

所有这些就是为什么最好不要尝试将Python的语义放在您惯用的语言上,而是按照自己的术语学习Python的语义。

If you’re coming to Python from another language (except one that’s a lot like Python, like Ruby), and insist on understanding it in terms of that other language, here’s where people usually get confused:

>>> a = 1
>>> a = 2 # I thought int was immutable, but I just changed it?!

In Python, assignment is not mutation in Python.

In C++, if you write a = 2, you’re calling a.operator=(2), which will mutate the object stored in a. (And if there was no object stored in a, that’s an error.)

In Python, a = 2 does nothing to whatever was stored in a; it just means that 2 is now stored in a instead. (And if there was no object stored in a, that’s fine.)


Ultimately, this is part of an even deeper distinction.

A variable in a language like C++ is a typed location in memory. If a is an int, that means it’s 4 bytes somewhere that the compiler knows is supposed to be interpreted as an int. So, when you do a = 2, it changes what’s stored in those 4 bytes of memory from 0, 0, 0, 1 to 0, 0, 0, 2. If there’s another int variable somewhere else, it has its own 4 bytes.

A variable in a language like Python is a name for an object that has a life of its own. There’s an object for the number 1, and another object for the number 2. And a isn’t 4 bytes of memory that are represented as an int, it’s just a name that points at the 1 object. It doesn’t make sense for a = 2 to turn the number 1 into the number 2 (that would give any Python programmer way too much power to change the fundamental workings of the universe); what it does instead is just make a forget the 1 object and point at the 2 object instead.


So, if assignment isn’t a mutation, what is a mutation?

  • Calling a method that’s documented to mutate, like a.append(b). (Note that these methods almost always return None). Immutable types do not have any such methods, mutable types usually do.
  • Assigning to a part of the object, like a.spam = b or a[0] = b. Immutable types do not allow assignment to attributes or elements, mutable types usually allow one or the other.
  • Sometimes using augmented assignment, like a += b, sometimes not. Mutable types usually mutate the value; immutable types never do, and give you a copy instead (they calculate a + b, then assign the result to a).

But if assignment isn’t mutation, how is assigning to part of the object mutation? That’s where it gets tricky. a[0] = b does not mutate a[0] (again, unlike C++), but it does mutate a (unlike C++, except indirectly).

All of this is why it’s probably better not to try to put Python’s semantics in terms of a language you’re used to, and instead learn Python’s semantics on their own terms.


回答 5

对象是否可变取决于其类型。这不取决于它是否具有某些方法,也不取决于类层次结构的结构。

用户定义的类型(即类)通常是可变的。有一些exceptions,例如不可变类型的简单子类。其他不变类型包括一些内置的类型,如intfloattuplestr,如C实现以及一些Python类

来自《 Python语言参考》中“数据模型”一章的一般说明:

某些对象的值可以更改。价值可以改变的对象被认为是可变的。创建后其值不可更改的对象称为不可变的。

(当更改可变对象的值时,包含对可变对象的引用的不可变容器对象的值可以更改;但是该容器仍然被认为是不可变的,因为它所包含的对象的集合无法更改。因此,不可变性并不是严格意义上的与具有不变的值一样,它更加微妙。)

对象的可变性取决于其类型。例如,数字,字符串和元组是不可变的,而字典和列表则是可变的。

Whether an object is mutable or not depends on its type. This doesn’t depend on whether or not it has certain methods, nor on the structure of the class hierarchy.

User-defined types (i.e. classes) are generally mutable. There are some exceptions, such as simple sub-classes of an immutable type. Other immutable types include some built-in types such as int, float, tuple and str, as well as some Python classes implemented in C.

A general explanation from the “Data Model” chapter in the Python Language Reference”:

The value of some objects can change. Objects whose value can change are said to be mutable; objects whose value is unchangeable once they are created are called immutable.

(The value of an immutable container object that contains a reference to a mutable object can change when the latter’s value is changed; however the container is still considered immutable, because the collection of objects it contains cannot be changed. So, immutability is not strictly the same as having an unchangeable value, it is more subtle.)

An object’s mutability is determined by its type; for instance, numbers, strings and tuples are immutable, while dictionaries and lists are mutable.


回答 6

可变对象与不可变对象之间的区别

定义

可变对象:创建后可以更改的对象。
不可变的对象:创建后无法更改的对象。

在python中,它将尝试更改不可变对象的值,它将赋予新对象。

可变对象

这是python中可变类型的列表对象:

  1. list
  2. Dictionary
  3. Set
  4. bytearray
  5. user defined classes

不变的对象

这是python中不可变类型的列表对象:

  1. int
  2. float
  3. decimal
  4. complex
  5. bool
  6. string
  7. tuple
  8. range
  9. frozenset
  10. bytes

一些未回答的问题

问题字符串是不可变类型吗?
的,但是您可以解释一下: 证明1

a = "Hello"
a +=" World"
print a

输出量

"Hello World"

在上面的示例中,一次创建为“ Hello”的字符串最终更改为“ Hello World”。这意味着字符串是可变类型。但是我们不能检查它的身份并检查它是否是可变类型。

a = "Hello"
identity_a = id(a)
a += " World"
new_identity_a = id(a)
if identity_a != new_identity_a:
    print "String is Immutable"

输出量

String is Immutable

证明2

a = "Hello World"
a[0] = "M"

输出量

TypeError 'str' object does not support item assignment

问题元组是不可变的类型吗?
是的, 这是 证明1

tuple_a = (1,)
tuple_a[0] = (2,)
print a

输出量

'tuple' object does not support item assignment

Difference between Mutable and Immutable objects

Definitions

Mutable object: Object that can be changed after creating it.
Immutable object: Object that cannot be changed after creating it.

In python if you change the value of the immutable object it will create a new object.

Mutable Objects

Here are the objects in Python that are of mutable type:

  1. list
  2. Dictionary
  3. Set
  4. bytearray
  5. user defined classes

Immutable Objects

Here are the objects in Python that are of immutable type:

  1. int
  2. float
  3. decimal
  4. complex
  5. bool
  6. string
  7. tuple
  8. range
  9. frozenset
  10. bytes

Some Unanswered Questions

Questions: Is string an immutable type?
Answer: yes it is, but can you explain this: Proof 1:

a = "Hello"
a +=" World"
print a

Output

"Hello World"

In the above example the string got once created as “Hello” then changed to “Hello World”. This implies that the string is of the mutable type. But it is not when we check its identity to see whether it is of a mutable type or not.

a = "Hello"
identity_a = id(a)
a += " World"
new_identity_a = id(a)
if identity_a != new_identity_a:
    print "String is Immutable"

Output

String is Immutable

Proof 2:

a = "Hello World"
a[0] = "M"

Output

TypeError 'str' object does not support item assignment

Questions: Is Tuple an immutable type?
Answer: yes, it is. Proof 1:

tuple_a = (1,)
tuple_a[0] = (2,)
print a

Output

'tuple' object does not support item assignment

回答 7

可变对象必须至少具有一种能够使该对象变异的方法。例如,list对象具有append方法,该方法实际上将使对象变异:

>>> a = [1,2,3]
>>> a.append('hello') # `a` has mutated but is still the same object
>>> a
[1, 2, 3, 'hello']

但是该类float没有方法来更改float对象。你可以做:

>>> b = 5.0 
>>> b = b + 0.1
>>> b
5.1

但是=操作数不是方法。它只是在变量和变量右边之间建立了绑定,没有别的。它永远不会改变或创建对象。从现在开始,它是变量将指向的内容的声明。

当您执行b = b + 0.1=操作数时,将变量绑定到新的float时,将创建,结果为 5 + 0.1

将变量分配给存在的对象(可变对象或不可变对象)时,=操作数会将变量绑定到该对象。再也没有发生

无论哪种情况,=都只需要进行绑定即可。它不会更改或创建对象。

当您这样做时a = 1.0=操作数不是创建浮点数,而是创建1.0行的一部分。实际上,在编写时,1.0这是float(1.0)返回浮点对象的构造函数调用的简写形式。(这就是为什么您键入1.0并按Enter时会在1.0下面显示“ echo”的原因;这是您调用的构造函数的返回值)

现在,如果b是float和分配a = b,这两个变量都指向同一个对象,但实际上变量不能comunicate betweem自己,因为对象是inmutable,如果你这样做b += 1,现在b指向一个新的对象,a是仍指向老人,不知道要b指向什么。

但是,如果c是的话,假设是a list,并且您a = c现在分配,a并且c可以“通信”,因为它list是可变的,如果这样做c.append('msg'),则只需检查a您是否收到消息即可。

(顺便说一句,每个对象都有一个唯一的ID号,可以与之关联id(x)。因此,您可以检查一个对象是否相同或不检查其唯一ID是否已更改。)

A mutable object has to have at least a method able to mutate the object. For example, the list object has the append method, which will actually mutate the object:

>>> a = [1,2,3]
>>> a.append('hello') # `a` has mutated but is still the same object
>>> a
[1, 2, 3, 'hello']

but the class float has no method to mutate a float object. You can do:

>>> b = 5.0 
>>> b = b + 0.1
>>> b
5.1

but the = operand is not a method. It just make a bind between the variable and whatever is to the right of it, nothing else. It never changes or creates objects. It is a declaration of what the variable will point to, since now on.

When you do b = b + 0.1 the = operand binds the variable to a new float, wich is created with te result of 5 + 0.1.

When you assign a variable to an existent object, mutable or not, the = operand binds the variable to that object. And nothing more happens

In either case, the = just make the bind. It doesn’t change or create objects.

When you do a = 1.0, the = operand is not wich create the float, but the 1.0 part of the line. Actually when you write 1.0 it is a shorthand for float(1.0) a constructor call returning a float object. (That is the reason why if you type 1.0 and press enter you get the “echo” 1.0 printed below; that is the return value of the constructor function you called)

Now, if b is a float and you assign a = b, both variables are pointing to the same object, but actually the variables can’t comunicate betweem themselves, because the object is inmutable, and if you do b += 1, now b point to a new object, and a is still pointing to the oldone and cannot know what b is pointing to.

but if c is, let’s say, a list, and you assign a = c, now a and c can “comunicate”, because list is mutable, and if you do c.append('msg'), then just checking a you get the message.

(By the way, every object has an unique id number asociated to, wich you can get with id(x). So you can check if an object is the same or not checking if its unique id has changed.)


回答 8

如果该类的每个对象在实例化时具有固定值而不能随后更改,则该类是不可变的

换句话说,更改该变量的整个值(name)或不理会它。

例:

my_string = "Hello world" 
my_string[0] = "h"
print my_string 

您希望它可以正常工作并打印问候世界,但这将引发以下错误:

Traceback (most recent call last):
File "test.py", line 4, in <module>
my_string[0] = "h"
TypeError: 'str' object does not support item assignment

口译员说:我无法更改此字符串的第一个字符

您必须更改整体string以使其起作用:

my_string = "Hello World" 
my_string = "hello world"
print my_string #hello world

检查此表:

资源

A class is immutable if each object of that class has a fixed value upon instantiation that cannot SUBSEQUENTLY be changed

In another word change the entire value of that variable (name) or leave it alone.

Example:

my_string = "Hello world" 
my_string[0] = "h"
print my_string 

you expected this to work and print hello world but this will throw the following error:

Traceback (most recent call last):
File "test.py", line 4, in <module>
my_string[0] = "h"
TypeError: 'str' object does not support item assignment

The interpreter is saying : i can’t change the first character of this string

you will have to change the whole string in order to make it works:

my_string = "Hello World" 
my_string = "hello world"
print my_string #hello world

check this table:

source


回答 9

在我看来,您正在与可变/不可变实际上意味着什么的问题作斗争。所以这是一个简单的例子:

首先,我们需要一个基础来进行解释。

因此,请考虑将您编程为虚拟对象的任何事物,以二进制序列形式存储在计算机内存中的事物。(不过,不要试图想像的太难。^^)现在,在大多数计算机语言中,您不会直接使用这些二进制数,而是更多地使用二进制数的解释。

例如,您不考虑数字0x110、0xaf0278297319或类似数字,而是考虑诸如6的数字或诸如“ Hello,world”之类的字符串。这些数字或字符串永远都不能解释计算机内存中的二进制数。对于变量的任何值也是如此。

简而言之:我们使用实际值编程,而是使用实际二进制值的解释。

现在,我们确实有一些为了逻辑和其他“精巧的东西”而不能更改的解释,而有些解释很可能会更改。例如,考虑一个城市的模拟,换句话说就是一个程序,其中有许多虚拟对象,其中一些是房屋。现在可以更改这些虚拟对象(房屋),并且仍然可以将它们视为相同的房屋吗?当然可以。因此它们是可变的:可以对其进行更改而不会成为“完全”不同的对象。

现在考虑整数:它们也是虚拟对象(计算机内存中的二进制数的序列)。因此,如果我们更改其中之一,就像将值六增加一,它仍然是六吗?当然不是。因此,任何整数都是不可变的。

因此:如果虚拟对象中的任何更改意味着它实际上变成了另一个虚拟对象,则称为不可变。

最后说明:

(1)切勿将使用可变语言和不变语言的真实世界经验与某种语言的编程混为一谈:

每种编程语言都有自己的定义,哪些对象可以被静音,哪些对象不能被静音。

因此,尽管您现在可能已经理解了含义上的差异,但是您仍然必须学习每种编程语言的实际实现。…确实,某种语言的目的可能是将6静音而变成7。那么这又将是相当疯狂或有趣的事情,例如并行宇宙的模拟。^^

(2)这种解释当然不是科学的,它是为了帮助您掌握可变和不变之间的区别。

It would seem to me that you are fighting with the question what mutable/immutable actually means. So here is a simple explenation:

First we need a foundation to base the explenation on.

So think of anything that you program as a virtual object, something that is saved in a computers memory as a sequence of binary numbers. (Don’t try to imagine this too hard, though.^^) Now in most computer languages you will not work with these binary numbers directly, but rather more you use an interpretation of binary numbers.

E.g. you do not think about numbers like 0x110, 0xaf0278297319 or similar, but instead you think about numbers like 6 or Strings like “Hello, world”. Never the less theses numbers or Strings are an interpretation of a binary number in the computers memory. The same is true for any value of a variable.

In short: We do not program with actual values but with interpretations of actual binary values.

Now we do have interpretations that must not be changed for the sake of logic and other “neat stuff” while there are interpretations that may well be changed. For example think of the simulation of a city, in other words a program where there are many virtual objects and some of these are houses. Now may these virtual objects (the houses) be changed and can they still be considered to be the same houses? Well of course they can. Thus they are mutable: They can be changed without becoming a “completely” different object.

Now think of integers: These also are virtual objects (sequences of binary numbers in a computers memory). So if we change one of them, like incrementing the value six by one, is it still a six? Well of course not. Thus any integer is immutable.

So: If any change in a virtual object means that it actually becomes another virtual object, then it is called immutable.

Final remarks:

(1) Never mix up your real-world experience of mutable and immutable with programming in a certain language:

Every programming language has a definition of its own on which objects may be muted and which ones may not.

So while you may now understand the difference in meaning, you still have to learn the actual implementation for each programming language. … Indeed there might be a purpose of a language where a 6 may be muted to become a 7. Then again this would be quite some crazy or interesting stuff, like simulations of parallel universes.^^

(2) This explenation is certainly not scientific, it is meant to help you to grasp the difference between mutable and immutable.


回答 10

这个答案的目标是创建一个地方,以找到所有好的主意,这些主意是关于如何分辨是否正在处理变异/非变异(不可变/可变),以及在可能的情况下该怎么做?有时候,突变是不受欢迎的,而python在这方面的行为可能与其他语言的编码人员的直觉相反。

根据@ mina-gabriel的有用文章:

分析以上内容并结合@arrakëën的帖子:

什么不能意外改变?

  • 标量(存储单个值的变量类型)不会意外更改
    • 数值示例:int(),float(),complex()
  • 有一些“可变序列”:
    • str(),tuple(),frozenset(),bytes()

什么可以?

  • 列出类似对象(列表,字典,集合,bytearray())
  • 此处的帖子还说了类和类实例,但这可能取决于类继承自什么和/或其构建方式。

“意外”是指其他语言的程序员可能不会期望这种行为(Ruby或Ruby,以及其他一些“ Python一样”的语言除外)。

添加到此讨论中:

当它防止您不小心用占用内存的大型数据结构的多个副本填充代码时,此行为是一个优点。但是,当这种情况不受欢迎时,我们如何解决呢?

使用列表,简单的解决方案是像这样构建一个新的列表:

list2 =列表(list1)

与其他结构一起使用…解决方案可能会更加棘手。一种方法是遍历元素并将其添加到新的(相同类型的)空数据结构中。

当您传入可变结构时,函数会改变原始函数。怎么说呢?

  • 在此线程的其他注释上进行了一些测试,但随后有注释指示这些测试不是完全证据
  • object.function()是原始对象的方法,但其中只有一部分会发生变化。如果他们什么都不返回,那么他们可能会这样做。有人希望.append()能够在不测试给定名称的情况下进行更改。.union()返回set1.union(set2)的并集,并且不会突变。不确定时,可以检查该函数的返回值。如果return = None,它不会突变。
  • 在某些情况下,sorted()可能是一种解决方法。由于它返回了原始文档的排序版本,因此它可以让您在开始以其他方式处理原始文档之前存储未变异的副本。但是,此选项假定您不关心原始元素的顺序(如果这样做,则需要寻找其他方法)。相比之下,.sort()会改变原始的(正如人们所期望的那样)。

非标准方法(在有帮助的情况下):在MIT许可下发布的github上找到了此方法:

  • github资料库位于:tobgu,名称:pyrsistent
  • 含义:编写Python持久数据结构代码,以在不希望发生突变时代替核心数据结构使用

对于自定义类,@ semicolon建议检查是否存在__hash__函数,因为可变对象通常不应具有__hash__()函数。

这是我目前在该主题上积累的全部内容。欢迎其他想法,更正等。谢谢。

The goal of this answer is to create a single place to find all the good ideas about how to tell if you are dealing with mutating/nonmutating (immutable/mutable), and where possible, what to do about it? There are times when mutation is undesirable and python’s behavior in this regard can feel counter-intuitive to coders coming into it from other languages.

As per a useful post by @mina-gabriel:

Analyzing the above and combining w/ a post by @arrakëën:

What cannot change unexpectedly?

  • scalars (variable types storing a single value) do not change unexpectedly
    • numeric examples: int(), float(), complex()
  • there are some “mutable sequences”:
    • str(), tuple(), frozenset(), bytes()

What can?

  • list like objects (lists, dictionaries, sets, bytearray())
  • a post on here also says classes and class instances but this may depend on what the class inherits from and/or how its built.

by “unexpectedly” I mean that programmers from other languages might not expect this behavior (with the exception or Ruby, and maybe a few other “Python like” languages).

Adding to this discussion:

This behavior is an advantage when it prevents you from accidentally populating your code with mutliple copies of memory-eating large data structures. But when this is undesirable, how do we get around it?

With lists, the simple solution is to build a new one like so:

list2 = list(list1)

with other structures … the solution can be trickier. One way is to loop through the elements and add them to a new empty data structure (of the same type).

functions can mutate the original when you pass in mutable structures. How to tell?

  • There are some tests given on other comments on this thread but then there are comments indicating these tests are not full proof
  • object.function() is a method of the original object but only some of these mutate. If they return nothing, they probably do. One would expect .append() to mutate without testing it given its name. .union() returns the union of set1.union(set2) and does not mutate. When in doubt, the function can be checked for a return value. If return = None, it does not mutate.
  • sorted() might be a workaround in some cases. Since it returns a sorted version of the original, it can allow you to store a non-mutated copy before you start working on the original in other ways. However, this option assumes you don’t care about the order of the original elements (if you do, you need to find another way). In contrast .sort() mutates the original (as one might expect).

Non-standard Approaches (in case helpful): Found this on github published under an MIT license:

  • github repository under: tobgu named: pyrsistent
  • What it is: Python persistent data structure code written to be used in place of core data structures when mutation is undesirable

For custom classes, @semicolon suggests checking if there is a __hash__ function because mutable objects should generally not have a __hash__() function.

This is all I have amassed on this topic for now. Other ideas, corrections, etc. are welcome. Thanks.


回答 11

思考差异的一种方法:

可以将对python中不可变对象的分配视为深拷贝,而对可变对象的分配则较浅

One way of thinking of the difference:

Assignments to immutable objects in python can be thought of as deep copies, whereas assignments to mutable objects are shallow


回答 12

最简单的答案:

可变变量是其值可能会在适当位置更改的变量,而在不可变变量中,值不会在适当的位置更改。修改不可变变量将重建相同的变量。

例:

>>>x = 5

将创建一个x引用的值5

x-> 5

>>>y = x

该语句使y引用x的5

x ————-> 5 <———– y

>>>x = x + y

由于x是整数(不可变类型),因此已重建。

在该语句中,RHS上的表达式将得出值10,将其分配给LHS(x)时,x将重建为10。所以现在

x ———> 10

y ———> 5

The simplest answer:

A mutable variable is one whose value may change in place, whereas in an immutable variable change of value will not happen in place. Modifying an immutable variable will rebuild the same variable.

Example:

>>>x = 5

Will create a value 5 referenced by x

x -> 5

>>>y = x

This statement will make y refer to 5 of x

x ————-> 5 <———–y

>>>x = x + y

As x being an integer (immutable type) has been rebuild.

In the statement, the expression on RHS will result into value 10 and when this is assigned to LHS (x), x will rebuild to 10. So now

x———>10

y———>5


回答 13

我还没有阅读所有答案,但是所选答案并不正确,我认为作者的想法是,能够重新分配变量意味着任何数据类型都是可变的。事实并非如此。可变性与按引用传递而不是按值传递有关。

假设您创建了一个列表

a = [1,2]

如果您要说:

b = a
b[1] = 3

即使您在B上重新分配了值,它也将在a上重新分配了值。这是因为当您分配“ b = a”时。您正在将“引用”传递给对象,而不是值的副本。字符串,浮点数等不是这种情况。这会使列表,字典和类似变量变得可变,但布尔值,浮点数等是不可变的。

I haven’t read all the answers, but the selected answer is not correct and I think the author has an idea that being able to reassign a variable means that whatever datatype is mutable. That is not the case. Mutability has to do with passing by reference rather than passing by value.

Lets say you created a List

a = [1,2]

If you were to say:

b = a
b[1] = 3

Even though you reassigned a value on B, it will also reassign the value on a. Its because when you assign “b = a”. You are passing the “Reference” to the object rather than a copy of the value. This is not the case with strings, floats etc. This makes list, dictionaries and the likes mutable, but booleans, floats etc immutable.


回答 14

例如,对于不可变的对象,分配会创建值的新副本。

x=7
y=x
print(x,y)
x=10 # so for immutable objects this creates a new copy so that it doesnot 
#effect the value of y
print(x,y)

对于可变对象,分配不会创建值的另一个副本。例如,

x=[1,2,3,4]
print(x)
y=x #for immutable objects assignment doesn't create new copy 
x[2]=5
print(x,y) # both x&y holds the same list

For immutable objects, assignment creates a new copy of values, for example.

x=7
y=x
print(x,y)
x=10 # so for immutable objects this creates a new copy so that it doesnot 
#effect the value of y
print(x,y)

For mutable objects, the assignment doesn’t create another copy of values. For example,

x=[1,2,3,4]
print(x)
y=x #for immutable objects assignment doesn't create new copy 
x[2]=5
print(x,y) # both x&y holds the same list

回答 15

在Python中,有一种简单的方法可以知道:

不变的:

    >>> s='asd'
    >>> s is 'asd'
    True
    >>> s=None
    >>> s is None
    True
    >>> s=123
    >>> s is 123
    True

可变的:

>>> s={}
>>> s is {}
False
>>> {} is {}
Flase
>>> s=[1,2]
>>> s is [1,2]
False
>>> s=(1,2)
>>> s is (1,2)
False

和:

>>> s=abs
>>> s is abs
True

因此,我认为内置函数在Python中也是不可变的。

但是我真的不明白float是如何工作的:

>>> s=12.3
>>> s is 12.3
False
>>> 12.3 is 12.3
True
>>> s == 12.3
True
>>> id(12.3)
140241478380112
>>> id(s)
140241478380256
>>> s=12.3
>>> id(s)
140241478380112
>>> id(12.3)
140241478380256
>>> id(12.3)
140241478380256

好奇怪

In Python, there’s a easy way to know:

Immutable:

    >>> s='asd'
    >>> s is 'asd'
    True
    >>> s=None
    >>> s is None
    True
    >>> s=123
    >>> s is 123
    True

Mutable:

>>> s={}
>>> s is {}
False
>>> {} is {}
Flase
>>> s=[1,2]
>>> s is [1,2]
False
>>> s=(1,2)
>>> s is (1,2)
False

And:

>>> s=abs
>>> s is abs
True

So I think built-in function is also immutable in Python.

But I really don’t understand how float works:

>>> s=12.3
>>> s is 12.3
False
>>> 12.3 is 12.3
True
>>> s == 12.3
True
>>> id(12.3)
140241478380112
>>> id(s)
140241478380256
>>> s=12.3
>>> id(s)
140241478380112
>>> id(12.3)
140241478380256
>>> id(12.3)
140241478380256

It’s so weird.


列表更改列表意外地反映在子列表中

问题:列表更改列表意外地反映在子列表中

我需要在Python中创建列表列表,因此输入了以下内容:

myList = [[1] * 4] * 3

该列表如下所示:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  

然后,我更改了最内在的值之一:

myList[0][0] = 5

现在我的列表如下所示:

[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  

这不是我想要或期望的。有人可以解释发生了什么,以及如何解决吗?

I needed to create a list of lists in Python, so I typed the following:

myList = [[1] * 4] * 3

The list looked like this:

[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]  

Then I changed one of the innermost values:

myList[0][0] = 5

Now my list looks like this:

[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]  

which is not what I wanted or expected. Can someone please explain what’s going on, and how to get around it?


回答 0

当您编写时,[x]*3您基本上得到了list [x, x, x]。也就是说,具有3个对same的引用的列表x。然后,当您修改此单曲时x,通过对它的所有三个引用可以看到它:

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}\n"
    f"id(l[1]): {id(l[1])}\n"
    f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

要解决此问题,您需要确保在每个位置都创建一个新列表。一种方法是

[[1]*4 for _ in range(3)]

它将重新评估[1]*4每次而不是一次评估并对1个列表进行3次引用。


您可能想知道为什么*不能像列表理解那样创建独立的对象。这是因为乘法运算符*对对象进行操作,而没有看到表达式。当您使用*乘以[[1] * 4]3时,*只会看到1元素列表的[[1] * 4]计算结果,而不是[[1] * 4表达式文本。*不知道如何制作该元素的副本,不知道如何重新评估[[1] * 4],甚至不想要复制,而且一般来说,甚至没有办法复制该元素。

唯一的选择*是对现有子列表进行新引用,而不是尝试创建新子列表。其他所有内容将不一致或需要对基础语言设计决策进行重大重新设计。

相反,列表推导会在每次迭代时重新评估元素表达式。[[1] * 4 for n in range(3)]重新评估[1] * 4出于同样的原因,每次[x**2 for x in range(3)]重新评估x**2每一次。的每次评估都会[1] * 4生成一个新列表,因此列表理解功能可以满足您的需求。

顺便说一句,[1] * 4也不会复制的元素[1],但这并不重要,因为整数是不可变的。您不能做类似的事情1.value = 2,将1变成2。

When you write [x]*3 you get, essentially, the list [x, x, x]. That is, a list with 3 references to the same x. When you then modify this single x it is visible via all three references to it:

x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
    f"id(l[0]): {id(l[0])}\n"
    f"id(l[1]): {id(l[1])}\n"
    f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048

x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]

To fix it, you need to make sure that you create a new list at each position. One way to do it is

[[1]*4 for _ in range(3)]

which will reevaluate [1]*4 each time instead of evaluating it once and making 3 references to 1 list.


You might wonder why * can’t make independent objects the way the list comprehension does. That’s because the multiplication operator * operates on objects, without seeing expressions. When you use * to multiply [[1] * 4] by 3, * only sees the 1-element list [[1] * 4] evaluates to, not the [[1] * 4 expression text. * has no idea how to make copies of that element, no idea how to reevaluate [[1] * 4], and no idea you even want copies, and in general, there might not even be a way to copy the element.

The only option * has is to make new references to the existing sublist instead of trying to make new sublists. Anything else would be inconsistent or require major redesigning of fundamental language design decisions.

In contrast, a list comprehension reevaluates the element expression on every iteration. [[1] * 4 for n in range(3)] reevaluates [1] * 4 every time for the same reason [x**2 for x in range(3)] reevaluates x**2 every time. Every evaluation of [1] * 4 generates a new list, so the list comprehension does what you wanted.

Incidentally, [1] * 4 also doesn’t copy the elements of [1], but that doesn’t matter, since integers are immutable. You can’t do something like 1.value = 2 and turn a 1 into a 2.


回答 1

size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

实时Python导师可视化

size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Live Python Tutor Visualize


回答 2

实际上,这正是您所期望的。让我们分解一下这里发生的事情:

你写

lst = [[1] * 4] * 3

这等效于:

lst1 = [1]*4
lst = [lst1]*3

这意味着lst一个包含3个元素的列表lst1。这意味着以下两行是等效的:

lst[0][0] = 5
lst1[0] = 5

至于lst[0]是什么,但lst1

要获得所需的行为,可以使用列表理解:

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

在这种情况下,将对每个n重新计算表达式,从而得出不同的列表。

Actually, this is exactly what you would expect. Let’s decompose what is happening here:

You write

lst = [[1] * 4] * 3

This is equivalent to:

lst1 = [1]*4
lst = [lst1]*3

This means lst is a list with 3 elements all pointing to lst1. This means the two following lines are equivalent:

lst[0][0] = 5
lst1[0] = 5

As lst[0] is nothing but lst1.

To obtain the desired behavior, you can use list comprehension:

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

In this case, the expression is re-evaluated for each n, leading to a different list.


回答 3

[[1] * 4] * 3

甚至:

[[1, 1, 1, 1]] * 3

创建一个引用内部[1,1,1,1]3次的列表-而不是内部列表的3个副本,因此,每次修改列表(在任何位置)时,都会看到3次更改。

与此示例相同:

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

可能不那么令人惊讶。

[[1] * 4] * 3

or even:

[[1, 1, 1, 1]] * 3

Creates a list that references the internal [1,1,1,1] 3 times – not three copies of the inner list, so any time you modify the list (in any position), you’ll see the change three times.

It’s the same as this example:

>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]

where it’s probably a little less surprising.


回答 4

除了可以正确解释问题的公认答案之外,在列表理解范围内,如果您使用的是python-2.x,则使用return xrange()可以返回更高效的生成器(range()在python 3中执行相同的工作),_而不是throwaway变量n

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

另外,作为一种Python方式,您可以itertools.repeat()用来创建重复元素的迭代器对象:

>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

PS使用numpy的,如果你只是想创建1或0,你可以使用数组np.onesnp.zeros和/或其他使用次数np.repeat()

In [1]: import numpy as np

In [2]: 

In [2]: np.ones(4)
Out[2]: array([ 1.,  1.,  1.,  1.])

In [3]: np.ones((4, 2))
Out[3]: 
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.]])

In [4]: np.zeros((4, 2))
Out[4]: 
array([[ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.]])

In [5]: np.repeat([7], 10)
Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])

Alongside the accepted answer that explained the problem correctly, within your list comprehension, if You are using python-2.x use xrange() that returns a generator which is more efficient (range() in python 3 does the same job) _ instead of the throwaway variable n:

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

Also, as a much more Pythonic way you can use itertools.repeat() to create an iterator object of repeated elements :

>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

P.S. Using numpy, if you only want to create an array of ones or zeroes you can use np.ones and np.zeros and/or for other number use np.repeat():

In [1]: import numpy as np

In [2]: 

In [2]: np.ones(4)
Out[2]: array([ 1.,  1.,  1.,  1.])

In [3]: np.ones((4, 2))
Out[3]: 
array([[ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.],
       [ 1.,  1.]])

In [4]: np.zeros((4, 2))
Out[4]: 
array([[ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.],
       [ 0.,  0.]])

In [5]: np.repeat([7], 10)
Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])

回答 5

Python容器包含对其他对象的引用。请参阅以下示例:

>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

在此b列表中包含一个项目,该项目是对list的引用a。该列表a是可变的。

将列表乘以整数等于将列表多次添加到自身(请参阅常见序列操作)。因此,继续下面的示例:

>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

我们可以看到列表c现在包含两个对list的引用,a它们等效于c = b * 2

Python FAQ也包含此行为的解释:如何创建多维列表?

Python containers contain references to other objects. See this example:

>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

In this b is a list that contains one item that is a reference to list a. The list a is mutable.

The multiplication of a list by an integer is equivalent to adding the list to itself multiple times (see common sequence operations). So continuing with the example:

>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

We can see that the list c now contains two references to list a which is equivalent to c = b * 2.

Python FAQ also contains explanation of this behavior: How do I create a multidimensional list?


回答 6

myList = [[1]*4] * 3[1,1,1,1]在内存中创建一个列表对象,然后将其引用复制3次。这等效于obj = [1,1,1,1]; myList = [obj]*3。列表中引用的obj任何地方,对的任何修改都会在三个地方反映出来obj。正确的声明是:

myList = [[1]*4 for _ in range(3)]

要么

myList = [[1 for __ in range(4)] for _ in range(3)]

这里要注意的重要一点是,*运算符通常用于创建文字列表。虽然1是一成不变的,obj =[1]*4但仍将创建1重复4遍以上的列表[1,1,1,1]。但是,如果对不可变对象进行了任何引用,则该对象将被新对象覆盖。

这意味着,如果我们这样做obj[1]=42,那么obj它将变得[1,42,1,1] 不像 [42,42,42,42]某些人想象的那样。这也可以验证:

>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440

>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440

myList = [[1]*4] * 3 creates one list object [1,1,1,1] in memory and copies its reference 3 times over. This is equivalent to obj = [1,1,1,1]; myList = [obj]*3. Any modification to obj will be reflected at three places, wherever obj is referenced in the list. The right statement would be:

myList = [[1]*4 for _ in range(3)]

or

myList = [[1 for __ in range(4)] for _ in range(3)]

Important thing to note here is that * operator is mostly used to create a list of literals. Although 1 is immutable, obj =[1]*4 will still create a list of 1 repeated 4 times over to form [1,1,1,1]. But if any reference to an immutable object is made, the object is overwritten with a new one.

This means if we do obj[1]=42, then obj will become [1,42,1,1] not [42,42,42,42] as some may assume. This can also be verified:

>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440

>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]

>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440

回答 7

简而言之,这是因为在python中,所有内容都可以通过引用来工作,因此,当您以这种方式创建列表时,基本上就可以解决此类问题。

要解决您的问题,您可以执行以下任一操作:1.将numpy数组文档用于numpy.empty。2 .将列表追加到列表中。3.您也可以使用字典

In simple words this is happening because in python everything works by reference, so when you create a list of list that way you basically end up with such problems.

To solve your issue you can do either one of them: 1. Use numpy array documentation for numpy.empty 2. Append the list as you get to a list. 3. You can also use dictionary if you want


回答 8

让我们以以下方式重写您的代码:

x = 1
y = [x]
z = y * 4

myList = [z] * 3

然后,运行以下代码使所有内容更加清晰。该代码所做的基本上是打印id获得的对象的,

返回对象的“身份”

并将帮助我们识别它们并分析发生的情况:

print("myList:")
for i, subList in enumerate(myList):
    print("\t[{}]: {}".format(i, id(subList)))
    for j, elem in enumerate(subList):
        print("\t\t[{}]: {}".format(j, id(elem)))

您将获得以下输出:

x: 1
y: [1]
z: [1, 1, 1, 1]
myList:
    [0]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [1]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [2]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528

现在让我们逐步进行。你有x哪些是1和一个单一的元素列表y包含x。第一步是y * 4将获得一个z基本上为的新列表,[x, x, x, x]即创建一个包含4个元素的新列表,这些元素是对初始x对象的引用。净步骤非常相似。基本上z * 3,您要做的是[[x, x, x, x]] * 3和返回[[x, x, x, x], [x, x, x, x], [x, x, x, x]],其原因与第一步相同。

Let us rewrite your code in the following way:

x = 1
y = [x]
z = y * 4

myList = [z] * 3

Then having this, run the following code to make everything more clear. What the code does is basically print the ids of the obtained objects, which

Return the “identity” of an object

and will help us identify them and analyse what happens:

print("myList:")
for i, subList in enumerate(myList):
    print("\t[{}]: {}".format(i, id(subList)))
    for j, elem in enumerate(subList):
        print("\t\t[{}]: {}".format(j, id(elem)))

And you will get the following output:

x: 1
y: [1]
z: [1, 1, 1, 1]
myList:
    [0]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [1]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528
    [2]: 4300763792
        [0]: 4298171528
        [1]: 4298171528
        [2]: 4298171528
        [3]: 4298171528

So now let us go step-by-step. You have x which is 1, and a single element list y containing x. Your first step is y * 4 which will get you a new list z, which is basically [x, x, x, x], i.e. it creates a new list which will have 4 elements, which are references to the initial x object. The net step is pretty similar. You basically do z * 3, which is [[x, x, x, x]] * 3 and returns [[x, x, x, x], [x, x, x, x], [x, x, x, x]], for the same reason as for the first step.


回答 9

我想每个人都解释发生了什么。我建议一种解决方法:

myList = [[1 for i in range(4)] for j in range(3)]

myList[0][0] = 5

print myList

然后您有:

[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

I guess everybody explain what is happening. I suggest one way to solve it:

myList = [[1 for i in range(4)] for j in range(3)]

myList[0][0] = 5

print myList

And then you have:

[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

回答 10

试图以更具描述性的方式进行解释,

操作一:

x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]

x[0][0] = 1
print(x) # [[1, 0], [0, 0]]

操作2:

y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [1, 0]]

注意为什么不修改第一个列表的第一个元素而不修改每个列表的第二个元素?那是因为[0] * 2实际上是两个数字的列表,并且不能修改对0的引用。

如果要创建克隆副本,请尝试操作3:

import copy
y = [0] * 2   
print(y)   # [0, 0]

y = [y, copy.deepcopy(y)]  
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [0, 0]]

创建克隆副本的另一种有趣方式是操作4:

import copy
y = [0] * 2
print(y) # [0, 0]

y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]

y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]

Trying to explain it more descriptively,

Operation 1:

x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]

x[0][0] = 1
print(x) # [[1, 0], [0, 0]]

Operation 2:

y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [1, 0]]

Noticed why doesn’t modifying the first element of the first list didn’t modify the second element of each list? That’s because [0] * 2 really is a list of two numbers, and a reference to 0 cannot be modified.

If you want to create clone copies, try Operation 3:

import copy
y = [0] * 2   
print(y)   # [0, 0]

y = [y, copy.deepcopy(y)]  
print(y) # [[0, 0], [0, 0]]

y[0][0] = 1
print(y) # [[1, 0], [0, 0]]

another interesting way to create clone copies, Operation 4:

import copy
y = [0] * 2
print(y) # [0, 0]

y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]

y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]

回答 11

@spelchekr来自Python的列表乘法:[[…]] * 3使得3个列表在修改后会相互镜像,我也有一个相同的问题:“为什么只有外部* 3创建更多引用,而内部* 3却没有创建更多引用为什么不是全1?”

li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]

ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

尝试上面的代码后,这是我的解释:

  • 内部*3也创建引用,但是它的引用是不可变的,例如[&0, &0, &0],然后,当要更改时li[0],您不能更改const int的任何基础引用0,因此您只需将引用地址更改为新的引用地址即可&1
  • while ma=[&li, &li, &li]limutable是可变的,因此当您调用时ma[0][0]=1,ma [0] [0]等于to &li[0],因此所有&li实例都将其第一个地址更改为&1

@spelchekr from Python list multiplication: [[…]]*3 makes 3 lists which mirror each other when modified and I had the same question about “Why does only the outer *3 create more references while the inner one doesn’t? Why isn’t it all 1s?”

li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]

ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

Here is my explanation after trying the code above:

  • The inner *3 also creates references, but it’s references are immutable, something like [&0, &0, &0], then when to change li[0], you can’t change any underlying reference of const int 0, so you can just change the reference address into the new one &1;
  • while ma=[&li, &li, &li] and li is mutable, so when you call ma[0][0]=1, ma[0][0] is equally to &li[0], so all the &li instances will change its 1st address into &1.

回答 12

通过使用内置列表功能,您可以像这样

a
out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#Displaying the list

a.remove(a[0])
out:[[1, 1, 1, 1], [1, 1, 1, 1]]
# Removed the first element of the list in which you want altered number

a.append([5,1,1,1])
out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
# append the element in the list but the appended element as you can see is appended in last but you want that in starting

a.reverse()
out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#So at last reverse the whole list to get the desired list

By using the inbuilt list function you can do like this

a
out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#Displaying the list

a.remove(a[0])
out:[[1, 1, 1, 1], [1, 1, 1, 1]]
# Removed the first element of the list in which you want altered number

a.append([5,1,1,1])
out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
# append the element in the list but the appended element as you can see is appended in last but you want that in starting

a.reverse()
out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#So at last reverse the whole list to get the desired list