不可变与可变类型

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

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

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

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

276809 次浏览

首先,一个类是否有方法或者它的类结构是什么与可变性无关。

__abc0和__abc1是不可变的。如果我这样做

a = 1
a += 5

它将名字a指向第一行内存中的某个地方的1。在第二行,它查找1,添加5,得到6,然后将a指向内存中的6——它没有以任何方式改变1指向6。同样的逻辑适用于下面的例子,使用其他10类型:

b = 'some string'
b += 'some other string'
c = ('some', 'tuple')
c += ('some', 'other', 'tuple')

对于可变的类型,我可以做的事情实际上是更改该值在内存中的存储位置。:

d = [1, 2, 3]

我已经在内存中创建了123的位置列表。如果我这样做

e = d

我只是将e指向同样list d所指向的。然后我可以这样做:

e += [4, 5]

并且ed所指向的列表将被更新为内存中也有45的位置。

如果我返回到不可变的类型并使用tuple:

f = (1, 2, 3)
g = f
g += (4, 5)

那么f仍然只指向原始tuple——你已经将g指向了全新的tuple

现在,用你的例子

class SortedKeyDict(dict):
def __new__(cls, val):
return dict.__new__(cls, val.clear())

你经过的地方

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

(它是tuplestuple)作为val,你会得到一个错误,因为tuple没有.clear()方法——你必须将dict(d)作为val来传递,在这种情况下,你会得到一个空的SortedKeyDict

对象是否可变取决于它的类型。这与它是否具有某些方法无关,也与类层次结构无关。

用户定义的类型(即类)通常是可变的。有一些例外,比如不可变类型的简单子类。其他不可变类型包括一些内置类型,如intfloattuplestr,以及一些用C实现的Python类。

“数据模型”;Python语言参考手册中的章节;的一般解释:

某些对象的值可以改变。可以更改值的对象 都是可变的;一旦它们的值是不可改变的对象

(一个不可变容器的值 包含对可变对象的引用的对象可以在 后者的值改变了;然而容器是静止的 被认为是不可变的,因为它包含的对象的集合 不可更改。所以,不变性并不完全等同于拥有 一个不可改变的值,它更微妙)

对象的可变性为 类型的:由类型决定的;例如,数字、字符串和元组 不可变,而字典和列表是可变的

你必须明白Python将所有数据表示为对象。其中一些对象(如列表和字典)是可变的,这意味着您可以在不改变其标识的情况下更改其内容。其他对象,如整数、浮点数、字符串和元组是不能更改的对象。 一个简单的理解方法是,如果你看一下对象ID

下面是一个不可变的字符串。你不能改变它的内容。如果你试图改变它,它将引发TypeError。同样,如果我们分配新内容,则创建一个新对象,而不是修改内容。

>>> 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语言参考:

什么?浮点数是不可变的?但我做不到

x = 5.0
x += 7.0
print x # 12.0

那不是"mut" x吗?

你同意字符串是不可变的,对吧?但你可以做同样的事情。

s = 'foo'
s += 'bar'
print s # foobar

变量的值会改变,但改变的方式是改变变量所指向的对象。可变类型可以这样改变,并且它可以“in place”改变。

区别就在这里。

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中,对不可变对象的赋值可以被认为是深度拷贝, 而对可变对象的赋值是浅的

可变对象必须至少有一个方法能够改变对象。例如,list对象有append方法,它实际上会改变对象:

>>> a = [1,2,3]
>>> a.append('hello') # `a` has mutated but is still the same object
>>> a
[1, 2, 3, 'hello']

但是float类没有改变浮点对象的方法。你可以:

>>> b = 5.0
>>> b = b + 0.1
>>> b
5.1

但是=操作数不是一个方法。它只是在变量和它右边的东西之间做了一个绑定,没有别的。它从不改变或创建对象。它声明了变量将指向什么,从现在开始。

当执行b = b + 0.1时,=操作数将变量绑定到一个新的浮点数,该浮点数是用5 + 0.1的结果创建的。

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

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

当你执行a = 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是,比如说,一个list,并且你赋值了a = c,现在ac可以“通信”,因为list是可变的,如果你执行c.append('msg'),那么只检查a就可以得到消息。

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

常见的不可变类型:

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

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

  1. 可变序列:list()bytearray()
  2. 设置类型:set()
  3. 映射类型:dict()
  4. 类,类实例
  5. 等。

快速测试类型是否可变的一个技巧是使用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,这意味着它在编译器知道应该被解释为int的某处有4个字节。因此,当你执行a = 2时,它会将存储在这4个字节内存中的内容从0, 0, 0, 1更改为0, 0, 0, 2。如果在其他地方有另一个int变量,它有自己的4个字节。

在像Python这样的语言中,变量是具有自己生命的对象的名称。有一个对象用于数字1,另一个对象用于数字2。而且a不是表示为int的4个字节的内存,它只是指向1对象的一个名称。a = 2把数字1变成数字2是没有意义的(这将给任何Python程序员太多的权力来改变宇宙的基本运作);它所做的只是让a忘记1对象,而是指向2对象。


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

  • 调用一个被记录为变异的方法,如a.append(b)。(注意,这些方法几乎总是返回None)。不可变类型没有任何这样的方法,可变类型通常有。
  • 赋值给对象的一部分,如a.spam = ba[0] = b。不可变类型不允许对属性或元素赋值,可变类型通常只允许其中之一。
  • 有时使用增强赋值,如a += b,有时不使用。可变类型通常会改变值;不可变类型从不这样做,而是给你一个副本(它们计算a + b,然后将结果赋值给a)。

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

这就是为什么可能更好的方法是尝试用你习惯的语言来表达Python的语义,而不是用它们自己的术语来学习Python的语义。

一个类是不可变的,如果该类的每个对象在实例化时都有一个固定的值,并且不能随后< em > < / em >被更改

换句话说,要么改变变量(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才能使它工作:

my_string = "Hello World"
my_string = "hello world"
print my_string #hello world

查看这个表格:

enter image description here

source

在我看来,你在纠结可变/不可变到底意味着什么。下面是一个简单的解释:

首先,我们需要一个解释的基础。

把你编程的任何东西都想象成一个虚拟对象,一个以二进制数字序列的形式保存在计算机内存中的东西。(不过,不要试图把这个想象得太困难。^^)现在在大多数计算机语言中,你不会直接处理这些二进制数,而是更多地使用二进制数的解释。

例如,你不会想到像0x110, 0xaf0278297319或类似的数字,而是你会想到像6这样的数字或像“Hello, world”这样的字符串。然而,这些数字或字符串是计算机内存中二进制数的一种解释。对于变量的任何值都是如此。

简而言之:< em >我们不是程序与的实际值,而是带有实际二进制值的解释。

现在我们确实有一些解释是为了逻辑和其他“整齐的东西”而不能改变的,但也有一些解释是可以改变的。例如,想象一个城市的模拟,换句话说,一个程序中有许多虚拟物体,其中一些是房子。现在这些虚拟物体(房子)可以被改变吗?它们仍然可以被认为是相同的房子吗?他们当然可以。因此它们是可变的:它们可以被改变而不会变成一个“完全”不同的对象。

现在想想整数:它们也是虚拟对象(计算机内存中的二进制数序列)。如果我们改变其中一个,比如把6的值加1,它还是6吗?当然不是。因此任何整数都是不可变的。

所以:如果虚拟对象中的任何变化意味着它实际上变成了另一个虚拟对象,那么它被称为不可变

最后的话:

(1)永远不要把你在现实世界中可变和不可变的经验与某种语言的编程混淆在一起:

每一种编程语言都有自己的定义,关于哪些对象可以静音,哪些对象不可以静音

因此,虽然您现在可能理解了含义上的差异,但仍然需要学习每种编程语言的实际实现. ...的确,一种语言可能有一个目的,即6可能被削弱为7。然后,这将是相当疯狂或有趣的东西,就像平行宇宙的模拟

(2)这个解释当然是不科学的,它是为了帮助你掌握可变和不可变的区别。

在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中也是不可变的。

但我真的不明白float是如何工作的:

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

太奇怪了。

最简单的答案:

可变变量的值可能会在某个位置发生变化,而不可变变量的值不会在某个位置发生变化。修改不可变变量将重新构建相同的变量。

例子:

>>>x = 5

是否会创建一个由x引用的值5

X ->

>>>y = x

这个表述使y = 5 (x

X -------------> 5 <-----------y

>>>x = x + y

由于x是一个整数(不可变类型)已重新构建。

在语句中,RHS上的表达式将得到值10,当它被赋值给LHS (x)时,x将重新构造为10。所以现在

x ---------> 10

y ---------> 5

这个答案的目标是创建一个单独的地方来找到所有关于如何判断您正在处理的是突变/非突变(不可变/可变)的好想法,以及在可能的情况下如何处理它?有些时候,突变是不受欢迎的,python在这方面的行为可能会让来自其他语言的编码人员感到违反直觉。

根据@mina-gabriel的一篇有用的文章:

分析以上并结合@arrakëën的文章:

什么不会意外改变?

  • 标量(存储单个值的变量类型)不会意外变化
    • 数值示例:int(), float(), complex()
    • 李< / ul > < / > 这里有一些“可变序列”:
      • Str (), tuple(), frozenset(), bytes()
      • 李< / ul > < / >

      可以什么?

      • 类似对象的列表(lists, dictionary, sets, bytearray())
      • 这里的一篇文章也提到了类和类实例,但这可能取决于类继承了什么和/或它是如何构建的。

      我所说的“意外”是指来自其他语言的程序员可能没有预料到这种行为(除了Ruby和其他一些“类似Python”的语言)。

      在这个讨论中补充:

      这种行为是一种优势,因为它可以防止您意外地用多个占用内存的大型数据结构的副本填充代码。但当这种情况不受欢迎时,我们该如何解决呢?

      对于列表,简单的解决方案是构建一个新的列表,如下所示:

      List2 = list(list1)

      对于其他结构……解决方案可能更加棘手。一种方法是遍历元素并将它们添加到新的空数据结构(相同类型)。

      当传入可变结构时,函数可以改变原始值。如何分辨?

      • 在这个帖子的其他评论中有一些测试,但有评论表明这些测试不是完全的证明
      • object.function()是原始对象的一个方法,但只有其中一些会发生变化。如果它们不返回,它们可能会返回。人们会期望.append()在不测试其名称的情况下发生变化。.union()返回set1.union(set2)的并集,并且不会发生变化。当有疑问时,可以检查函数的返回值。如果return = None,则不会发生变化。
      • Sorted()在某些情况下可能是一种变通方法。由于它返回原始版本的排序版本,因此可以允许您在开始以其他方式处理原始版本之前存储一个未突变的副本。但是,这个选项假设您不关心原始元素的顺序(如果您关心,则需要找到另一种方法)。相反,.sort()会改变原始数据(正如人们所期望的那样)。

      非标准方法(以防有用): 在github上发现这个在MIT许可下发布:

      • Github仓库下:tobgu命名为:pyrsistent
      • 它是什么:Python持久化数据结构代码,用于在不希望发生变化时代替核心数据结构

      对于自定义类,@分号建议检查是否有__hash__函数,因为可变对象通常不应该有__hash__()函数。

      这就是我目前在这个话题上所收集到的全部信息。欢迎提出其他意见、纠正意见等。谢谢。

我没有阅读所有的答案,但所选的答案是不正确的,我认为作者有一个想法,即能够重新分配变量意味着任何数据类型都是可变的。事实并非如此。可变性与通过引用传递而不是通过值传递有关。

假设您创建了一个List

a = [1,2]

如果你说:

b = a
b[1] = 3

即使你在B上重新分配了一个值,它也会重新分配a上的值。这是因为当你分配“B = a”时。您传递给对象的是“Reference”,而不是值的副本。而字符串、浮点数等则不是这样。这使得列表、字典和类似的东西是可变的,但是布尔值、浮点数等是不可变的。

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

定义

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

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

可变的对象

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

  1. list
  2. Dictionary
  3. Set
  4. bytearray
  5. user defined classes

不可变对象

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

  1. int
  2. float
  3. decimal
  4. complex
  5. bool
  6. string
  7. tuple
  8. range
  9. frozenset
  10. bytes

一些悬而未决的问题

< p > 问题: 字符串是不可变类型吗? < br > 回答: 是的是,但你能解释一下吗: 证据1 < /强>:< br >

a = "Hello"
a +=" World"
print a

输出

"Hello World"

In the above example the string got once created as "Hello" then changed to "Hello World". This implies that the string is of the mutable type. But it is not when we check its identity to see whether it is of a mutable type or not.

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

Proof 2:

a = "Hello World"
a[0] = "M"

输出

TypeError 'str' object does not support item assignment

Question: Is Tuple an immutable type?
Answer: yes, it is. Proof 1:

tuple_a = (1,)
tuple_a[0] = (2,)
print a

输出

'tuple' object does not support item assignment

例如,对于不可变对象,赋值会创建一个新的值副本。

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)

对于可变对象,赋值操作不会创建值的另一个副本。例如,

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数据类型是可变的,有些则不是。

让我们来看看哪些类型适合每个类别,并看一些例子。


可变的

在Python中有各种可变类型:

  • < p >列表

  • < p > dict类型

  • < p >设置

让我们看看下面lists的例子。

list = [1, 2, 3, 4, 5]

如果我执行以下操作来更改第一个元素

list[0] = '!'
#['!', '2', '3', '4', '5']

它工作得很好,因为列表是可变的。

如果我们考虑这个列表,它被改变了,然后给它赋值一个变量

y = list

如果我们改变列表中的一个元素,比如

list[0] = 'Hello'
#['Hello', '2', '3', '4', '5']

如果打印y,它会给出

['Hello', '2', '3', '4', '5']

因为listy都引用同一个列表,所以我们修改了列表。


不可变的

在一些编程语言中,可以定义一个常量,如下所示

const a = 10

如果调用,它会给出一个错误

a = 20

然而,这在Python中不存在。

然而,在Python中,有各种不可变类型:

  • < p >没有

  • < p > bool

  • < p > int

  • < p >浮

  • < p > str

  • < p >元组

让我们看看下面strings的例子。

获取字符串a

a = 'abcd'

我们可以得到第一个元素

a[0]
#'a'

如果试图给第一个位置的元素赋一个新值

a[0] = '!'

它会给出一个错误

“str”对象不支持项赋值

当对一个字符串使用+=时,例如

a += 'e'
#'abcde'

它不会给出错误,因为它将a指向一个不同的字符串。

这和下面一样

a = a + 'f'

不改变字符串。

不可变的优点和缺点

•内存中的空间从一开始就知道。它不需要额外的空间。

•通常,它会让事情更有效率。例如,查找字符串的len()要快得多,因为它是字符串对象的一部分。

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

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

输出:< br >

9789024
9789088
140010877705856
140010877705856

注意:当我们改变值
时,可变变量memory_location是change