我对什么是不可变类型感到困惑。我知道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()

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

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

不调用示例方法,它将返回一个字典。带有__new__的SortedKeyDict将其标记为错误。我尝试用__new__将整数传递给RoundFloat类,它没有标记错误。


当前回答

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

定义

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

在Python中,如果你改变了不可变对象的值,它会创建一个新对象。

可变的对象

下面是Python中可变类型的对象:

列表 字典 集 中bytearray 用户定义的类

不可变对象

以下是Python中不可变类型的对象:

int 浮动 小数 复杂的 保龄球 字符串 元组 范围 frozenset 字节

一些悬而未决的问题

问:字符串是不可变类型吗? 回答:是的,但你能解释一下吗? 证据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

其他回答

常见的不可变类型:

数字:int(), float(), complex() 不可变序列:str(), tuple(), frozenset(), bytes()

通用可变类型(几乎所有其他类型):

可变序列:list(), bytearray() 设置类型:Set () 映射类型:dict() 类,类实例 等。

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

例如,使用on integer,

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

使用on list,

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

如果你是从另一种语言(除了一种非常像Python的语言,比如Ruby)学习Python的,并且坚持用另一种语言来理解它,下面是人们通常会感到困惑的地方:

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

在Python中,赋值不是突变。

在c++中,如果你写a = 2,你是在调用a.operator=(2),这将改变存储在a中的对象(如果a中没有存储对象,这是一个错误)。

在Python中,a = 2对存储在a;它只是意味着2现在存储在a中。(如果a中没有存储对象,也没关系。)


归根结底,这是更深层次区别的一部分。

在c++这样的语言中,变量是内存中的类型化位置。如果a是int型,这意味着它有4个字节,编译器知道它应该被解释为int型。当你令a = 2时,它改变了存储在这4个字节内存中的内容从0,0,0,1变成了0,0,0,2。如果在其他地方有另一个int变量,它有自己的4个字节。

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.


如果赋值不是突变,那么什么是突变呢?

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).

但如果赋值不是突变,那么对象的一部分赋值是如何突变的呢?这就是棘手的地方。a[0] = b不会改变[0](再次,不像c++),但它会改变a(不像c++,除非是间接的)。

这就是为什么最好不要尝试按照您习惯的语言来理解Python的语义,而是根据Python的语义来学习Python的语义。

每次我们改变一个不可变变量的值时,它基本上会破坏之前的实例并创建一个变量类的新实例

var = 2 #Immutable data
print(id(var))
var += 4
print(id(var))

list_a = [1,2,3] #Mutable data
print(id(list_a))
list_a[0]= 4
print(id(list_a))

输出:

9789024
9789088
140010877705856
140010877705856

注意:可变变量memory_location在改变值时为change

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

定义

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

在Python中,如果你改变了不可变对象的值,它会创建一个新对象。

可变的对象

下面是Python中可变类型的对象:

列表 字典 集 中bytearray 用户定义的类

不可变对象

以下是Python中不可变类型的对象:

int 浮动 小数 复杂的 保龄球 字符串 元组 范围 frozenset 字节

一些悬而未决的问题

问:字符串是不可变类型吗? 回答:是的,但你能解释一下吗? 证据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

可变对象必须至少有一个方法能够改变对象。例如,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时,=操作数将变量绑定到一个新的浮点数,其创建结果为5 + 0.1。

当你将一个变量赋值给一个存在的对象时,不管是否是可变的,=操作数将变量绑定到该对象。没有别的事情发生

在任何一种情况下,=都只是进行绑定。它不改变或创建对象。

当执行= 1.0时,=操作数创建的不是浮点数,而是该行的1.0部分。实际上,当您编写1.0时,它是float(1.0)的简写,一个返回float对象的构造函数调用。(这就是为什么如果你输入1.0并按enter键,你会得到下面打印的“echo”1.0;这是你调用的构造函数的返回值)

现在,如果b是一个浮点数,你赋值a = b,两个变量都指向同一个对象,但实际上变量之间不能通信,因为对象是不可变的,如果你做b += 1,现在b指向一个新对象,而a仍然指向旧对象,不知道b指向什么。

但如果c是,假设,一个列表,你赋值a = c,现在a和c可以“通信”,因为list是可变的,如果你执行c.append('msg'),然后检查a,你得到消息。

(顺便说一下,每个对象都有一个唯一的关联id号,你可以通过id(x)获得。所以你可以检查一个对象是否相同,或者不检查它的唯一id是否改变了。)