我对什么是不可变类型感到困惑。我知道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类,它没有标记错误。
可变对象必须至少有一个方法能够改变对象。例如,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是否改变了。)
如果你是从另一种语言(除了一种非常像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的语义。
首先,一个类是否有方法或者它的类结构是什么与可变性无关。
int和float是不可变的。如果我这样做
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]
我已经在内存中创建了1、2和3的位置列表。如果我这样做
e = d
我只是把e指向d指向的同一个列表。然后我可以这样做:
e += [4, 5]
e和d所在的列表也会被更新到内存中的位置为4和5。
如果我回到一个不可变类型,用一个元组来做:
f = (1, 2, 3)
g = f
g += (4, 5)
那么f仍然只指向原来的元组——你已经把g指向了一个全新的元组。
现在,用你的例子
class SortedKeyDict(dict):
def __new__(cls, val):
return dict.__new__(cls, val.clear())
你经过的地方
d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))
(这是一个元组的元组)作为val,你会得到一个错误,因为元组没有.clear()方法-你必须传递dict(d)作为val为它工作,在这种情况下,你会得到一个空的SortedKeyDict作为结果。