我对什么是不可变类型感到困惑。我知道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类,它没有标记错误。
首先,一个类是否有方法或者它的类结构是什么与可变性无关。
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作为结果。
对象是否可变取决于它的类型。这与它是否具有某些方法无关,也与类层次结构无关。
用户定义的类型(即类)通常是可变的。有一些例外,比如不可变类型的简单子类。其他不可变类型包括一些内置类型,如int、float、tuple和str,以及一些用C实现的Python类。
《Python语言参考》中“数据模型”一章的一般解释:
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.
这个答案的目标是创建一个单独的地方来找到所有关于如何判断您正在处理的是突变/非突变(不可变/可变)的好想法,以及在可能的情况下如何处理它?有些时候,突变是不受欢迎的,python在这方面的行为可能会让来自其他语言的编码人员感到违反直觉。
根据@mina-gabriel的一篇有用的文章:
可能会有帮助的书籍:《Python中的数据结构和算法》
摘自那本书,列出了可变/不可变类型:
可变/不可变类型图像
分析以上并结合@arrakëën的文章:
什么不会意外改变?
标量(存储单个值的变量类型)不会意外变化
数值示例:int(), float(), complex()
有一些“可变序列”:
Str (), tuple(), frozenset(), bytes()
可以什么?
类似对象的列表(lists, dictionary, sets, bytearray())
这里的一篇文章也提到了类和类实例,但这可能取决于类继承了什么和/或它是如何构建的。
我所说的“意外”是指来自其他语言的程序员可能没有预料到这种行为(除了Ruby和其他一些“类似Python”的语言)。
在这个讨论中补充:
这种行为是一种优势,因为它可以防止您意外地用多个占用内存的大型数据结构的副本填充代码。但当这种情况不受欢迎时,我们该如何解决呢?
对于列表,简单的解决方案是构建一个新的列表,如下所示:
列表 2 = 列表(列表 1)
对于其他结构……解决方案可能更加棘手。一种方法是遍历元素并将它们添加到新的空数据结构(相同类型)。
当传入可变结构时,函数可以改变原始值。如何分辨?
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).
非标准方法(以防有用):
在github上发现了这个,在MIT许可下发布:
Github仓库下:tobgu命名为:pyrsistent
它是什么:Python持久化数据结构代码,用于在不希望发生变化时代替核心数据结构
对于自定义类,@分号建议检查是否有__hash__函数,因为可变对象通常不应该有__hash__()函数。
这就是我目前在这个话题上所收集到的全部信息。欢迎提出其他意见、纠正意见等。谢谢。
可变对象必须至少有一个方法能够改变对象。例如,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的语义。