不可变与可变类型

时间:2011-11-08 19:41:53

标签: python immutability mutable

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

而mutable中有一些方法在类中有这种类型的例子:

class SortedKeyDict_a(dict):
    def example(self):
        return self.keys()

此外,对于最后一个class(SortedKeyDict_a),如果我将此类型的集合传递给它:

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

不调用example方法,它返回一个字典。 SortedKeyDict __new__将其标记为错误。我尝试使用RoundFloat将整数传递到__new__类,并且标记没有错误。

17 个答案:

答案 0 :(得分:218)

什么?浮动是不可改变的?但我不能这样做

x = 5.0
x += 7.0
print x # 12.0

那不是“mut”x?

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

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

变量的值会发生变化,但会通过更改变量引用的内容来更改。可变类型可以改变这种方式,它也可以 更改“就地”。

这是区别。

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]

答案 1 :(得分:175)

您必须了解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语言参考:

答案 2 :(得分:99)

常见的不可变类型:

  1. 数字:int()float()complex()
  2. 不可变序列:str()tuple()frozenset()bytes()
  3. 常见的可变类型(几乎所有其他类型):

    1. 可变序列:list()bytearray()
    2. 设置类型:set()
    3. 映射类型:dict()
    4. 类,类实例
    5. 快速测试类型是否可变的一个技巧是使用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)
      

答案 3 :(得分:33)

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

intfloat不可变的。如果我做

a = 1
a += 5

它将名称a指向第一行内存中1的某个位置。在第二行,它会查找1,添加5,获取6,然后将a指向内存中的6 - 它没有' t 以任何方式将1更改为6。相同的逻辑适用于以下示例,使用其他不可变类型:

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

对于 mutable 类型,我可以做的事情更改存储在内存中的值。用:

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

tuple的{​​{1}})为tuples,您收到错误,因为val没有tuple方法 - 您必须将.clear()作为dict(d)传递给它才能正常工作,在这种情况下,您将获得一个空的val

答案 4 :(得分:18)

对象是否可变取决于其类型。这不取决于它是否具有某些方法,也不依赖于类层次结构。

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

the "Data Model" chapter in the Python Language Reference"的一般性解释:

  

某些对象的值可能会发生变化。值可以更改的对象   据说是可变的;一旦它们的值不可改变的对象   创建的名称称为不可变。

     

(不可变容器的值   包含对可变对象的引用的对象可以在何时更改   后者的价值发生了变化;但容器仍然是   被认为是不可变的,因为它包含的对象集合   无法改变。因此,不变性与严格不一样   一个不可改变的价值,它更加微妙。)

     

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

答案 5 :(得分:18)

如果你是从另一种语言(除了像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 = 2a中存储的内容不起任何作用;它只是意味着2现在存储在a中。 (如果 没有对象存储在a中,那很好。)


最终,这是更深层次区分的一部分。

C ++等语言中的变量是内存中的类型位置。如果aint,这意味着编译器知道的某个地方的4个字节应该被解释为int。因此,当您执行a = 2时,它会将存储在这些4字节内存中的内容从0, 0, 0, 1更改为0, 0, 0, 2。如果在其他地方有另一个int变量,它有自己的4个字节。

像Python这样的语言中的变量是具有自己生命的对象的名称。数字1有一个对象,数字2有另一个对象。并且a不是4个字节的内存,表示为int,它只是一个指向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的语义。

答案 6 :(得分:11)

一个可变对象必须至少有一个能够改变对象的方法。例如,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)构造函数调用返回浮点对象的简写。 (这就是为什么如果你输入1.0然后按回车键,你会得到下面打印的“echo”1.0;这是你调用的构造函数的返回值)

现在,如果b是一个浮点数并且您指定了a = b,则两个变量都指向同一个对象,但实际上变量无法通过它们自己进行通信,因为该对象是不可变的,并且如果您执行b += 1,现在b指向一个新对象,而a仍然指向旧版,并且无法知道b指向的内容。

但是,如果clist,您指定a = c,现在ac可以“通信”,因为{ {1}}是可变的,如果您执行list,那么只需检查c.append('msg')即可获得该消息。

(顺便说一句,每个对象都有一个唯一的id号,你可以使用a来获取。所以你可以检查一个对象是否相同,或者检查它的唯一id是否已经改变。)

答案 7 :(得分:10)

Mutable和Immutable对象之间的区别

解释

可变对象:创建后可以更改的对象 不可变对象:创建后无法更改的对象。

在python中将尝试更改它将为新对象提供的不可变对象的值。

可变对象

以下是python中可变类型的列表对象:

  1. InetSocketAddress
  2. list
  3. Dictionary
  4. Set
  5. bytearray
  6. 不可变对象

    以下是python中不可变类型的列表对象:

    1. user defined classes
    2. int
    3. float
    4. decimal
    5. complex
    6. bool
    7. string
    8. tuple
    9. range
    10. frozenset
    11. 一些未答复的问题

      问题字符串是不可变类型吗?
      回答它是,但你可以解释一下: 证明1

      bytes

      <强>输出

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

      在上面的示例中,字符串一旦被创建为&#34; Hello&#34;最后改为&#34; Hello World&#34;。这意味着字符串是可变类型。但我们不能检查它的身份并检查它是否具有可变类型。

      "Hello World"
      

      <强>输出

      a = "Hello"
      identity_a = id(a)
      a += " World"
      new_identity_a = id(a)
      if identity_a != new_identity_a:
          print "String is Immutable"
      

      证明2

      String is Immutable
      

      <强>输出

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

      问题元组是不可变类型吗?
      回答确实如此 证明1

      TypeError 'str' object does not support item assignment
      

      <强>输出

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

答案 8 :(得分:4)

  

如果该类的每个对象在实例化时都具有固定值且不能 SUBSEQUENTLY ,那么该类是不可变的

换句话说,改变该变量(name)的整个值或不管它。

示例:

my_string = "Hello world" 
my_string[0] = "h"
print my_string 

你希望这可以工作并打印 hello world ,但这会引发以下错误:

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

答案 9 :(得分:4)

这个答案的目标是创建一个单独的地方,找到关于如何判断你是否正在处理变异/非变异(不可变/可变)的所有好主意,并在可能的情况下,如何处理它?有些时候突变是不受欢迎的,而python在这方面的行为对于从其他语言进入它的编码人员来说可能是违反直觉的。

根据@ mina-gabriel的有用帖子:

分析以上内容并结合@arrakëën的帖子:

什么不能意外改变?

  • 标量(存储单个值的变量类型)不会意外更改
    • 数字示例:int(),float(),complex()
  • 有一些“可变序列”:
    • str(),tuple(),frozenset(),bytes()

什么可以?

  • 列出类似对象(列表,词典,集合,bytearray())
  • 这里的帖子也说了类和类实例,但这可能取决于类继承的内容和/或它的构建方式。

“意外地”我的意思是来自其他语言的程序员可能不会期望这种行为(除了Ruby或其他一些“类似Python”的语言)。

添加到此讨论:

这种行为是一个优势,它可以防止您意外地使用多个内存大数据结构的多个副本填充您的代码。但是当这是不可取的时候,我们如何绕过它呢?

使用列表,简单的解决方案就是建立一个新的:

list2 = list(list1)

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

传递可变结构时,

函数可以改变原始函数。怎么说?

  • 此线程上的其他评论中有一些测试但是有评论表明这些测试不是完全证明
  • object.function()是原始对象的一种方法,但只有其中一些变异。如果他们什么都不返回,他们可能会做。人们会期望.append()在没有测试名称的情况下进行变异。 .union()返回set1.union(set2)的并集,并且不会发生变异。如有疑问,可以检查函数的返回值。如果return = None,则不会发生变异。
  • 在某些情况下,
  • sorted()可能是一种解决方法。由于它返回原始的排序版本,因此它允许您在以其他方式开始处理原始文件之前存储非变异副本。但是,此选项假定您不关心原始元素的顺序(如果这样做,您需要找到另一种方法)。相反,.sort()会改变原文(正如人们所预期的那样)。

非标准方法(如有帮助): 在github上发现这是根据MIT许可证发布的:

  • github存储库位于:tobgu,名为:pyrsistent
  • 它是什么:编写用于代替核心数据结构的Python持久性数据结构代码,当不希望发生突变时

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

这是我现在就这个话题所积累的全部内容。欢迎其他想法,更正等。感谢。

答案 10 :(得分:4)

在我看来,你正在与可变/不可变实际意味着的问题作斗争。所以这是一个简单的explenation:

首先,我们需要一个以explenation为基础的基础。

因此,请考虑将您编程为虚拟对象的任何内容,将某些内容保存在计算机内存中作为二进制数字序列。 (不要试图想象这太难了。^^)现在在大多数计算机语言中,你不会直接使用这些二进制数,而是使用二进制数的解释。

E.g。你不会考虑像0x110,0xaf0278297319或类似的数字,而是考虑像6或字符串这样的数字,如“Hello,world”。从来没有这些数字或字符串是计算机内存中二进制数的解释。对于变量的任何值都是如此。

简而言之: 我们 使用实际值进行编程,但解释实际二进制值。

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

现在想想整数:这些也是虚拟对象(计算机内存中的二进制数序列)。因此,如果我们更改其中一个,比如逐个递增值,它仍然是六个?当然不是。因此任何整数都是不可变的。

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

最后的评论:

(1)永远不要将你在可变和不可变的实际经验与某种语言的编程混淆:

每种编程语言都有自己的定义,哪些对象可能会被静音,哪些可能没有。

因此,虽然您现在可以理解意义上的差异,但您仍然需要学习每种编程语言的实际实现。 ......确实可能存在一种语言的目的,其中6可能被静音成为7.然后,这将是相当疯狂或有趣的东西,如平行宇宙的模拟。^^

(2)这种表达当然不科学,它旨在帮助你把握可变和不可变之间的区别。

答案 11 :(得分:3)

一种思考差异的方法:

在python中对不可变对象的赋值可以被认为是深拷贝, 而对可变对象的分配很浅

答案 12 :(得分:2)

最简单的答案:

可变变量是其值可能在适当位置变化的变量,而在不可变变量中,变量值不会发生。修改不可变变量将重建相同的变量。

示例:

>>>x = 5

将创建x引用的值5

x - &gt; 5

>>>y = x

此陈述将使y引用x中的5

x -------------&gt; 5&lt; ----------- y

>>>x = x + y

由于x是一个整数(不可变类型),因此已经重建。

在语句中,RHS上的表达式将产生值10,当它被分配给LHS(x)时,x将重建为10.所以现在

X ---------→10

ý---------→5

答案 13 :(得分:1)

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

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

答案 14 :(得分:1)

可变意味着它可以改变/变异。不可变的相反。

有些 Python 数据类型是可变的,有些则不是。

让我们找出适合每个类别的类型并查看一些示例。


可变

在 Python 中有各种可变类型:

  • 列表

  • 字典

  • 设置

让我们看看以下 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 中,有各种不可变类型:

  • 布尔

  • int

  • 浮动

  • str

  • 元组

让我们看看以下 strings 的示例。

取字符串 a

a = 'abcd'

我们可以获得第一个元素

a[0]
#'a'

如果试图为第一个位置的元素分配一个新值

a[0] = '!'

会报错

<块引用>

'str' 对象不支持项目赋值

当人们对一个字符串说 += 时,例如

a += 'e'
#'abcde'

它没有给出错误,因为它把 a 指向了一个不同的字符串。

和下面一样

a = a + 'f'

并且不改变字符串。

不可变的一些优点和缺点

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

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

答案 15 :(得分:-1)

在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

太奇怪了。

答案 16 :(得分:-1)

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

假设你创建了一个List

a = [1,2]

如果你要说:

b = a
b[1] = 3

即使您在B上重新分配了值,它也会重新分配a上的值。这是因为当你指定&#34; b = a&#34;。您正在通过&#34;参考&#34;到对象而不是值的副本。这不是字符串,浮点数等的情况。这使得列表,字典和类似的东西变得可变,但是布尔值,浮点数等是不可变的。