不可变与可变types
我很困惑什么是不可变的types。 我知道float
对象被认为是不可变的,从我的书中有这样的例子:
class RoundFloat(float): def __new__(cls, val): return float.__new__(cls, round(val, 2))
这被认为是不可变的,因为类结构/层次?,这意味着float
是在类的顶部,并且是它自己的方法调用。 类似于这种types的例子(即使我的书说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)
,如果我将这种types的集合传递给它:
d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))
不调用example
方法,它将返回一个字典。 带有__new__
的SortedKeyDict
将__new__
标记为错误。 我试着用__new__
将整数传递给RoundFloat
类,并且标记为没有错误。
什么? 花车是不可改变的? 但我不能这样做
x = 5.0 x += 7.0 print x # 12.0
不是那个“mut”x?
那么你认为string是不可改变的权利? 但是你可以做同样的事情。
s = 'foo' s += 'bar' print s # foobar
variables的值会发生变化,但它会通过更改variables引用而发生变化。 一个可变types可以改变这种方式,它也可以改变“就地”。
这是区别。
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]
你必须明白Python将所有数据表示为对象。 列表和字典中的某些对象是可变的,这意味着您可以在不改变其身份的情况下更改其内容。 像整数,浮点数,string和元组等其他对象是无法更改的对象。 一个简单的方法来理解,就是如果你看一个对象的ID。
在下面你看到一个不可变的string。 你不能改变它的内容。 如果您尝试更改它将TypeError
。 另外,如果我们分配新内容,则会创build一个新的对象,而不是被修改的内容。
>>> 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语言参考:
- Python 2数据模型
- Python 3数据模型
常见的不可变types:
- 数字:
int()
,float()
,complex()
- 不变的序列:
str()
,tuple()
,frozenset()
,bytes()
常见的可变types(几乎所有其他):
- 可变序列:
list()
,bytearray()
- 设置types:
set()
- 映射types:
dict()
- 类,类实例
- 等等
快速testingtypes是否可变的一个技巧是使用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)
首先,一个类是否有方法,什么是类结构,与可变性无关。
int
s和float
s是不可变的 。 如果我做
a = 1 a += 5
它在第一行的记忆中指出名字a
在1
处。 在第二行中,查找1
,加上5
,得到6
,然后在内存中指向6
,这并没有以任何方式将1
改为6
。 使用其他不可变types的逻辑同样适用于以下示例:
b = 'some string' b += 'some other string' c = ('some', 'tuple') c += ('some', 'other', 'tuple')
对于可变types,我可以做一些事情, 改变存储在内存中的值 。 附:
d = [1, 2, 3]
我在内存中创build了2
和3
的位置列表。 如果我那么做
e = d
我只是指出e
在相同的list
d
点。 我可以这样做:
e += [4, 5]
并且e
和d
指向的列表将被更新为在内存中也具有4
和5
的位置。
如果我回到一个不可变的types,并用tuple
来做:
f = (1, 2, 3) g = f g += (4, 5)
那么f
仍然只指向原始tuple
– 你已经指向了一个全新的tuple
。
现在,用你的例子
class SortedKeyDict(dict): def __new__(cls, val): return dict.__new__(cls, val.clear())
你通过的地方
d = (('zheng-cai', 67), ('hui-jun', 68),('xin-yi', 2))
(这是一个tuple
的tuples
),因为tuple
没有.clear()
方法,所以你得到一个错误 – 你必须把dict(d)
作为val
传递给它,这种情况下,您将得到一个空的SortedKeyDict
。
一个对象是否是可变的取决于它的types。 这并不取决于它是否具有某些方法,也不取决于类层次结构。
用户定义的types(即类)通常是可变的。 有一些例外,例如不可变types的简单子类。 其他不可变的types包括一些内置的types,如int
, float
, tuple
和str
,以及一些在C中实现的Python类。
Python语言参考“数据模型”一章中的一般性解释:
一些对象的值可能会改变。 价值可以改变的对象被认为是可变的; 一旦创build它们的值不可更改的对象称为不可变的。
(包含对可变对象的引用的不可变容器对象的值在后者的值发生更改时可能会发生变化,但容器仍然被视为不可变的,因为它所包含的对象的集合是不能更改的,所以不可变性是不严格的就像具有不变的价值一样,这是更微妙的。)
一个对象的可变性由其types决定; 例如,数字,string和元组是不可变的,而字典和列表是可变的。
如果你是从另一种语言(除了像Python那样的Python以外)来到Python,并坚持用其他语言来理解Python,那么人们通常会感到困惑:
>>> 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 ++这样的语言中的variables是内存中的一个types化的位置。 如果a
是一个int
,那就意味着它的4个字节在编译器知道的地方应该被解释为一个int
。 所以,当你做a = 2
,它将存储在这4个字节的内存中的内容从0, 0, 0, 1
改为0, 0, 0, 2
。 如果在别的地方还有另一个intvariables,它有它自己的4个字节。
像Python这样的语言中的variables是具有自己的生命的对象的名称。 数字1
有一个对象,数字2
另一个对象。 而且a
不是以int
表示的4字节内存,只是指向1
对象的名称。 把数字1变成数字2是没有意义的(这会给任何Python程序员太多的力量来改变宇宙的基本运作)。 它所做的只不过是把a
对象忘了,而是指向2
对象。
那么,如果分配不是一个突变,什么是突变?
- 调用一个被logging为mutate的方法,比如
a.append(b)
。 (请注意,这些方法几乎总是返回None
)。 不可变的types没有任何这样的方法,可变types通常是这样做的。 - 分配给对象的一部分,如
a.spam = b
或a[0] = b
。 不可变types不允许赋值给属性或元素,可变types通常允许一个或另一个。 - 有时使用增强赋值,如
a += b
,有时不使用。 可变types通常会改变值; 不可变的types永远不会做,并给你一个副本,而不是(他们计算a + b
,然后将结果分配给a
)。
但如果赋值不是突变,那么如何赋值给对象变异的一部分呢? 这就是棘手的地方。 a[0] = b
不会改变a[0]
(与C ++不同),但它会改变(不同于C ++,除非是间接的)。
所有这些都是为什么最好不要试图用你习惯的语言来expressionPython的语义,而是要根据自己的条件来学习Python的语义。
一个可变对象必须至less有一个能够改变对象的方法。 例如, 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
但=
操作数不是一个方法。 它只是在variables和它的权利之间做一个绑定,没有别的。 它永远不会改变或创build对象。 从现在开始,这是variables指向的一个声明。
当你做b = b + 0.1
, =
操作数将variables绑定到一个新的浮点数上,这个浮点数的结果是5 + 0.1
。
当您将一个variables赋值给一个存在的对象时,可变或不可变, =
操作数将该variables绑定到该对象。 而没有更多的发生
在任何一种情况下, =
只是做绑定。 它不会更改或创build对象。
当你做a = 1.0
, =
操作数不是创build浮点数,而是线的1.0
部分。 实际上当你写1.0
它是float(1.0)
构造函数调用返回float对象的简写。 (这就是为什么如果你input1.0
并按下回车键就会得到下面打印的“echo” 1.0
;这就是你调用的构造函数的返回值)
现在,如果b
是一个浮点数,并且赋值a = b
,那么这两个variables都指向同一个对象,但实际上这些variables之间不能交换,因为对象是不可变的,如果你现在做b += 1
, b
指向一个新的对象, a
仍然指向老人,不知道b
指向什么。
但如果c
是,我们说,一个list
,而你分配a = c
,现在a
和c
可以“comunicate”,因为list
是可变的,如果你做c.append('msg')
,那么只是检查你得到消息。
(顺便说一下,每个对象都有一个唯一的ID号,可以用id(x)
来获得,所以你可以检查一个对象是否相同,或者不检查它的唯一id是否已经改变。
一个类是不可变的,如果该类的每个对象在实例化时有一个固定的值,不能随意改变
换句话说,改变这个variables(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的第一个字符
你将不得不改变整个string
,以使其工作:
my_string = "Hello World" my_string = "hello world" print my_string #hello world
检查这个表格:
资源
在我看来, 你正在与这个问题争论什么是可变的/不可变的 。 所以这里是一个简单的消除:
首先,我们需要一个基础来build立基础。
所以想想你编写的一个虚拟对象的东西,它是作为一个二进制数字序列保存在计算机内存中的。 (不要试图想象这太难了,但是^^)现在在大多数计算机语言中,你将不能直接使用这些二进制数字,而是更多地使用二进制数字的解释。
例如,你不要考虑像0x110,0xaf0278297319或类似的数字,而是考虑数字如6或string,如“你好,世界”。 从来没有更less的论文数字或string是计算机内存中的二进制数的解释。 对于variables的任何值也是如此。
简而言之: 我们不是用实际值编程,而是用实际二进制值的解释。
现在我们确实有一些解释不能为了逻辑和其他“整齐的东西”而改变,而有些解释可能会改变。 例如想象一个城市的模拟,换句话说就是一个有许多虚拟物体的程序,其中有些是房屋。 现在这些虚拟物体(房子)可以改变吗?它们还可以被认为是同一个房屋吗? 当然他们可以。 因此它们是可变的:它们可以被改变而不会成为“完全”不同的对象。
现在想想整数:这些也是虚拟对象(在计算机内存中的二进制数的序列)。 所以如果我们改变其中的一个,比如像增加一个一个的价值那么它还是六个? 那当然不是。 因此任何整数都是不变的。
所以: 如果虚拟对象的任何变化意味着它实际上变成另一个虚拟对象,那么它被称为不可变的。
最后的评论:
(1)永远不要混淆使用某种语言编程的可变和不可变实际经验:
每种编程语言都有自己的定义,哪些对象可能被静音,哪些不可以。
所以,虽然你现在可以理解意义上的不同,但你仍然需要学习每种编程语言的实际实现。 …确实可能有一个语言的目的,一个6可能被静音成为7.然后,这将是相当一些疯狂或有趣的东西,如平行宇宙的模拟。
(2)这种消除肯定是不科学的,它是为了帮助你把握可变和不可变的区别。
一种思考差异的方法:
对python中的不可变对象的赋值可以被认为是深度拷贝,而赋值给可变对象的是浅层的
这个答案的目标是创build一个单一的地方,find所有关于如何判断是否正在处理mutating / nonmutating(不可变/可变)的好主意,以及在可能的情况下如何处理? 有些时候,突变是不可取的,python在这方面的行为可能会让其他语言的编程者感到反感。
根据@ mina-gabriel的一篇有用的文章:
- 要阅读的书可能会有所帮助:“ Python中的数据结构和algorithm ”
- 摘自该书的列出了可变/不可变types: 可变/不可变types的图像
分析上面的内容,结合@arrakëën的post:
什么不能意外改变?
- 标量(存储单个值的variablestypes)不会意外更改
- 数字示例:int(),float(),complex()
- 有一些“可变序列”:
- str(),tuple(),frozenset(),bytes()
什么可以?
- 列表像对象(列表,字典,集合,bytearray())
- 在这里的post也说类和类的实例,但这可能取决于类inheritance和/或如何构build。
意思是说其他语言的程序员可能不会期待这种行为(除了例外或Ruby,也可能是其他一些“类似Python的”语言)。
增加这个讨论:
这种行为是一个优点,它可以防止您意外地使用大量的内存数据结构的副本来填充代码。 但是,当这是不可取的,我们如何解决它?
使用列表,简单的解决scheme是build立一个新的像这样:
list2 = list(list1)
与其他结构…解决scheme可能会更棘手。 一种方法是遍历元素,并将它们添加到一个新的空白数据结构(相同types)。
当你传入可变结构时,函数可以改变原来的东西。 如何判断?
- 在这个线程的其他评论上有一些testing,但是有评论指出这些testing并不完全certificate
- object.function()是原始对象的一个方法,但只有一些mutate。 如果他们什么都不返回,他们可能会做。 人们会预期.append()会在没有testing它的名字的情况下进行变异。 .union()返回set1.union(set2)的联合,不会变异。 如果有疑问,可以检查函数的返回值。 如果return = None,它不会变异。
- sorting()可能是在某些情况下的解决方法。 由于它返回原始文件的sorting版本,因此可以允许您在以其他方式开始处理原始文件之前存储非变异的副本。 但是,这个选项假设你不关心原始元素的顺序(如果你这样做,你需要find另一种方式)。 相反,.sort()会改变原来的(正如人们所期望的)。
非标准方法(在有帮助的情况下):根据MIT许可证发布在github上:
- github存储库下:tobgu命名为:pyrsistent
- 它是什么:当不希望发生突变时,编写用于代替核心数据结构的Python持久数据结构代码
对于自定义类,@ semicolonbuild议检查是否存在__hash__
函数,因为可变对象通常不会有__hash__()
函数。
这就是我现在积累的关于这个话题的全部内容。 其他想法,更正等是受欢迎的。 谢谢。
最简单的答案是:
一个可变的variables是其值可能会改变的variables,而在一个不可变的variables中,值不会发生变化。 修改一个不可变的variables将重build相同的variables。
例:
>>>x = 5
将创build一个由x引用的值5
x – > 5
>>>y = x
这个声明将使y指5中的x
x ————-> 5 <———– y
>>>x = x + y
由于x是一个整数(不可变types)已被重build。
在声明中,RHS上的expression式将导致值10,当它被分配给LHS(x)时,x将重build为10.因此,现在
X ———> 10
ÿ———> 5
我没有阅读所有的答案,但select的答案是不正确的,我认为作者有一个想法,即能够重新分配一个variables意味着无论数据types是可变的。 事实并非如此。 可变性与通过引用而不是通过价值传递有关。
比方说你创build了一个List
a = [1,2]
如果你说:
b = a b[1] = 3
即使您重新分配了B的值,它也会重新分配a的值。 因为当你分配“b = a”。 您将“引用”传递给对象,而不是该值的副本。 这不是string,浮动等情况。这使得列表,字典和类似的可变,但布尔,浮动等不可变的。
可变与不可变对象的区别
python中的通用定义是:
可变对象 :创build后可以更改的对象。
不可变对象 :创build后无法更改的对象。
在python会尝试改变不可变对象的值,它会给新的对象。
可变对象
这里是python中的可变types的列表对象:
-
list
-
Dictionary
-
Set
-
bytearray
-
user defined classes
不可变的对象
下面是python中不可变types的列表对象:
-
int
-
float
-
decimal
-
complex
-
bool
-
string
-
tuple
-
range
-
frozenset
-
bytes
一些未答复的问题
问题 : string是不可变的types吗?
答 : 是的,但你能解释一下: certificate1 :
a = "Hello" a +=" World" print a
产量
"Hello World"
在上面的例子中,string被创build为“Hello”,最后变成了“Hello World”。 这意味着string是可变types的。 但是我们不能检查它的身份,并检查它是否是可变types的。
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
certificate2 :
a = "Hello World" a[0] = "M"
产量
TypeError 'str' object does not support item assignment
问题 : Tuple是不可变的types吗?
答 : 是的 , certificate1 :
tuple_a = (1,) tuple_a[0] = (2,) print a
产量
'tuple' object does not support item assignment
在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中也是不可变的。
但我真的不明白浮动是如何工作的:
>>> 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
太奇怪了