Immutable vs Mutable types
我对什么是不变类型感到困惑。我知道
1 2 3 | class RoundFloat(float): def __new__(cls, val): return float.__new__(cls, round(val, 2)) |
由于类结构/层次结构,这是否被认为是不可变的?,表示
1 2 3 | class SortedKeyDict(dict): def __new__(cls, val): return dict.__new__(cls, val.clear()) |
虽然可变的东西在类内有方法,但是使用这种类型的示例:
1 2 3 | class SortedKeyDict_a(dict): def example(self): return self.keys() |
另外,对于最后一个
1 | d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2)) |
不调用
什么?浮点数是不变的?但我不能这样做
1 2 3 | x = 5.0 x += 7.0 print x # 12.0 |
那不是"mut"x吗?
你同意字符串是不变的,对吗?但你也可以做同样的事情。
1 2 3 | s = 'foo' s += 'bar' print s # foobar |
变量的值会改变,但它会通过改变变量所指的内容而改变。一个可变的类型可以这样改变,它也可以"就地"改变。
这就是区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
具体实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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] |
您必须理解,python将其所有数据表示为对象。其中一些对象(如列表和字典)是可变的,这意味着您可以在不更改其标识的情况下更改其内容。其他对象如整数、浮点数、字符串和元组是不能更改的对象。理解这一点的一个简单方法是查看对象ID。
下面是一个不可变的字符串。您不能更改其内容。如果你试图改变它,它将提高一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> 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 |
你可以用一个列表来做,它不会改变对象的标识。
1 2 3 4 5 6 7 8 | >>> i = [1,2,3] >>>id(i) 2146718700 >>> i[0] 1 >>> i[0] = 7 >>> id(i) 2146718700 |
要了解更多关于Python数据模型的信息,您可以查看Python语言参考:
- python 2数据模型
- python 3数据模型
通用不可变类型:
常见的可变类型(几乎所有其他类型):
快速测试类型是否可变的一个技巧是使用
示例,使用整数,
1 2 3 4 5 6 7 8 | >>> i = 1 >>> id(i) ***704 >>> i += 1 >>> i 2 >>> id(i) ***736 (different from ***704) |
使用列表,
1 2 3 4 5 6 7 8 | >>> a = [1] >>> id(a) ***416 >>> a.append(2) >>> a [1, 2] >>> id(a) ***416 (same with the above id) |
首先,一个类是否有方法或者它的类结构与可变性无关。
1 2 | a = 1 a += 5 |
它将名称
1 2 3 4 | b = 'some string' b += 'some other string' c = ('some', 'tuple') c += ('some', 'other', 'tuple') |
对于可变类型,我可以做一些实际更改存储在内存中的值的事情。用:
1 | d = [1, 2, 3] |
我在内存中创建了
1 | e = d |
我只是把
1 | e += [4, 5] |
同时更新
如果我回到一个不变的类型,用一个
1 2 3 | f = (1, 2, 3) g = f g += (4, 5) |
那么,
现在,举个例子
1 2 3 | class SortedKeyDict(dict): def __new__(cls, val): return dict.__new__(cls, val.clear()) |
你经过的地方
1 | d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2)) |
(这是
对象是否可变取决于其类型。这不取决于它是否有某些方法,也不取决于类层次结构的结构。
用户定义的类型(即类)通常是可变的。有一些异常,例如不可变类型的简单子类。其他不可变类型包括一些内置类型,如
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的语言,比如ruby)来到python,并且坚持用另一种语言来理解它,那么人们通常会在这里感到困惑:
1 2 | >>> a = 1 >>> a = 2 # I thought int was immutable, but I just changed it?! |
在python中,赋值不是python中的突变。
在C++中,如果你写
在python中,
最终,这是一个更深层次的区别的一部分。
像C++这样的语言中的变量是内存中的一个类型化的位置。如果
像python这样的语言中的变量是一个拥有自己生命的对象的名称。有一个对象用于数字
所以,如果任务不是突变,那么什么是突变?
- 调用一个记录为突变的方法,比如
a.append(b) 。(注意,这些方法几乎总是返回None )。不可变类型没有任何此类方法,可变类型通常有。 - 分配给对象的一部分,如
a.spam = b 或a[0] = b 。不可变类型不允许为属性或元素赋值,可变类型通常允许一个或另一个。 - 有时使用增强赋值,如
a += b ,有时不使用。可变类型通常会改变值;不可变类型永远不会改变,而是给您一个副本(它们计算a + b ,然后将结果赋给a )。
但是,如果赋值不是突变,那么如何赋值给对象的一部分突变呢?这就是问题的症结所在。
所有这一切都是为什么最好不要把Python的语义放在一种你习惯的语言上,而是用它们自己的术语来学习Python的语义。
可变对象必须至少有一个方法能够改变对象。例如,
1 2 3 4 | >>> a = [1,2,3] >>> a.append('hello') # `a` has mutated but is still the same object >>> a [1, 2, 3, 'hello'] |
但是类
1 2 3 4 | >>> b = 5.0 >>> b = b + 0.1 >>> b 5.1 |
但
当执行
当您将一个变量赋给一个现存的对象时,无论变量是否可变,
在这两种情况下,
当执行
现在,如果
但是如果
(顺便说一句,每个对象都有一个唯一的ID号,它与
可变对象和不可变对象的区别定义
可变对象:创建后可以更改的对象。不可变对象:创建后不能更改的对象。
在python中,将尝试更改它将给新对象的不可变对象的值。
可变物件下面是python中可变类型的列表对象:
不可变对象
以下是Python中不可变类型的列表对象:
一些悬而未决的问题
问题:字符串是不可变类型吗?答:是的,但你能解释一下吗?证据1:
1 2 3 | a ="Hello" a +=" World" print a |
产量
1 | "Hello World" |
在上面的示例中,字符串一度被创建为"hello",最后改为"hello world"。这意味着字符串属于可变类型。但不是这样的,我们可以检查它的身份,检查它是否是可变类型。
1 2 3 4 5 6 | a ="Hello" identity_a = id(a) a +=" World" new_identity_a = id(a) if identity_a != new_identity_a: print"String is Immutable" |
产量
1 | String is Immutable |
证据2:
1 2 | a ="Hello World" a[0] ="M" |
产量
1 | TypeError 'str' object does not support item assignment |
问题:元组是不变的类型吗?答:是的。证据1:
1 2 3 | tuple_a = (1,) tuple_a[0] = (2,) print a |
产量
1 | 'tuple' object does not support item assignment |
在我看来,你在和一个问题斗争,那就是可变/不变实际上意味着什么。下面是一个简单的解释:
首先,我们需要一个基础来解释。
所以把你编程的任何东西想象成一个虚拟对象,一个保存在计算机内存中的二进制数字序列。(不过,不要太难想象。^^)现在在大多数计算机语言中,您将不会直接使用这些二进制数,而是更多地使用二进制数的解释。
例如,您不考虑像0x110、0xAF0278297319或类似的数字,而是考虑像6这样的数字或像"hello,world"这样的字符串。这些数字或字符串绝不是计算机内存中二进制数字的一种解释。变量的任何值也是如此。
简而言之:我们不使用实际值编程,而是使用实际二进制值的解释。
现在我们确实有了不能为了逻辑和其他"整洁的东西"而改变的解释,而有些解释很可能会被改变。例如,考虑一个城市的模拟,换言之,一个程序中有许多虚拟对象,其中一些是房屋。现在,这些虚拟物体(房屋)可以被改变吗?它们还能被认为是相同的房屋吗?当然可以。因此,它们是可变的:它们可以在不成为"完全"不同对象的情况下进行更改。
现在想想整数:它们也是虚拟对象(计算机内存中二进制数的序列)。所以,如果我们改变其中的一个,比如将值6增加1,它仍然是6吗?当然不是。因此,任何整数都是不可变的。
所以:如果虚拟对象中的任何更改意味着它实际上成为另一个虚拟对象,那么它就称为不可变。
最后备注:
(1)不要把你的易变和不变的现实经验与用某种语言编程混淆在一起:
每种编程语言都有自己的定义,对象可以在其中静音,而对象不能静音。
因此,虽然您现在可以理解意义上的差异,但是您仍然需要学习每种编程语言的实际实现。…事实上,语言的目的可能是使6变为7。然后这又是一些非常疯狂或有趣的东西,比如对平行宇宙的模拟。^^
(2)这个解释当然不科学,它是为了帮助你理解易变和不易变之间的区别。
这个答案的目标是创建一个单一的地方来找到关于如何判断你是否正在处理变异/不变异(不可变/可变)的所有好主意,并且在可能的情况下,如何处理它?有时,突变是不可取的,在这方面,对于来自其他语言的编码人员来说,python的行为可能会感觉到违反直觉。
根据@mina gabriel的有用帖子:
- 阅读这本书可能会有所帮助:"Python中的数据结构和算法"
- 摘自那本列出可变/不变类型的书:可变/可变类型图像
分析以上内容并结合@arrak的w/a post?n:
什么不能意外地改变?
- scalars(存储单个值的变量类型)不会意外更改
- 数字示例:int()、float()、complex())
- 有一些"可变序列":
- str(),tuple(),frozenset(),bytes()。
什么可以?
- 类列表对象(列表、字典、集合、bytearray())
- 这里的帖子还提到类和类实例,但这可能取决于类继承自什么和/或如何构建。
"出乎意料"的意思是,来自其他语言的程序员可能不会期望这种行为(有异常或Ruby,可能还有其他一些"类似于Python"的语言)。
添加到此讨论:
这种行为是一种优势,当它防止您意外地用占用大量数据结构的多个内存副本填充代码时。但当这是不可取的,我们如何绕过它呢?
使用列表,简单的解决方案是创建一个这样的新列表:
list2=列表(list1)
其他结构…解决方案可能更棘手。一种方法是循环元素,并将它们添加到新的空数据结构(同一类型)中。
当传入可变结构时,函数可以改变原始值。怎么说?
- 在这个线程的其他注释上有一些测试,但是有一些注释表明这些测试不是完全证明。
- object.function()是原始对象的一个方法,但其中只有一些会发生变化。如果他们什么也不归还,他们可能会这么做。人们会期望.append()在不测试给定名称的情况下发生变异。.union()返回set1.union(set2)的并集,并且不会发生变化。如果有疑问,可以检查函数的返回值。如果返回=无,则不会发生变化。
- 在某些情况下,sorted()可能是解决方法。因为它返回原始版本的排序版本,所以它可以允许您在以其他方式开始处理原始版本之前存储一个未变异的副本。但是,此选项假定您不关心原始元素的顺序(如果这样做,则需要找到其他方法)。相反,.sort()会改变原始文件(正如人们所期望的那样)。
非标准方法(如有帮助):在麻省理工学院许可证下发布的Github上找到了这个:
- Github存储库位于:tobgu下,名为:pyrsistant
- 它是什么:当不希望发生突变时,编写用于代替核心数据结构的python持久数据结构代码
对于自定义类,@分号建议检查是否有
这是我目前在这个话题上积累的全部内容。欢迎其他意见、更正等。谢谢。
A class is immutable if each object of that class has a fixed value upon instantiation that cannot SUBSEQUENTLY be changed
换言之,更改该变量
例子:
1 2 3 | my_string ="Hello world" my_string[0] ="h" print my_string |
您希望此操作正常并打印Hello World,但这将引发以下错误:
1 2 3 4 | Traceback (most recent call last): File"test.py", line 4, in <module> my_string[0] ="h" TypeError: 'str' object does not support item assignment |
解释器说:我不能更改这个字符串的第一个字符
您必须更改整个
1 2 3 | my_string ="Hello World" my_string ="hello world" print my_string #hello world |
检查此表:
来源
区别的一种思考方式:
在python中对不可变对象的赋值可以被认为是深度复制,而对可变对象的赋值是浅的
最简单的答案是:
可变变量是其值可能在适当位置发生变化的变量,而在不可变变量中,值的变化不会在适当位置发生。修改不可变变量将重新生成同一个变量。
例子:
1 | >>>x = 5 |
将创建X引用的值5
X>5
1 | >>>y = x |
此语句将使y引用x中的5
X---------->5<------------Y
1 | >>>x = x + y |
因为x是一个整数(不可变类型),所以已经重新生成。
在语句中,rhs上的表达式将生成值10,当它被分配给lhs(x)时,x将重新生成为10。所以现在
X----> 10
Y---->>5
例如,对于不可变对象,赋值将创建值的新副本。
1 2 3 4 5 6 | 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) |
对于可变对象,赋值不会创建值的另一个副本。例如,
1 2 3 4 5 | 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 |
在python中,有一个简单的方法可以知道:
不变的:
1 2 3 4 5 6 7 8 9 | >>> s='asd' >>> s is 'asd' True >>> s=None >>> s is None True >>> s=123 >>> s is 123 True |
Mutable:
1 2 3 4 5 6 7 8 9 10 11 | >>> s={} >>> s is {} False >>> {} is {} Flase >>> s=[1,2] >>> s is [1,2] False >>> s=(1,2) >>> s is (1,2) False |
还有:
1 2 3 | >>> s=abs >>> s is abs True |
所以我认为内置函数在Python中也是不可变的。
但我真的不明白浮动是如何工作的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | >>> 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 |
真奇怪。
我没有阅读所有的答案,但是所选的答案是不正确的,我认为作者有一个想法,能够重新分配一个变量意味着任何数据类型都是可变的。事实并非如此。可变性与通过引用传递有关,而不是通过值传递。
假设你创建了一个列表
1 | a = [1,2] |
如果你说:
1 2 | b = a b[1] = 3 |
即使您重新分配了B上的值,它也会重新分配A上的值。这是因为当您分配"B=A"时。您正在将"引用"传递给对象,而不是值的副本。这不是字符串、浮点数等的情况。这会使列表、字典和类似内容可变,但布尔值、浮点数等不可变。