【问题标题】:Immutable vs Mutable types不可变与可变类型
【发布时间】:2026-01-02 06:50:01
【问题描述】:

我对什么是不可变类型感到困惑。我知道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),如果我将这种类型的集合传递给它:

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

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

【问题讨论】:

标签: python types immutability mutable


【解决方案1】:

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

定义

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

在 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

一些未解决的问题

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

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

输出

“你好世界”

在上面的示例中,字符串一旦创建为“Hello”,然后更改为“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

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

输出

TypeError 'str' 对象不支持项分配

问题元组是不可变类型吗?
答案是的,是的。 证明 1

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

输出

'tuple' 对象不支持项分配

【讨论】:

  • In [46]: a =“Hello” In [47]: id(a) Out[47]: 140071263880128 In [48]: a = a.replace("H","g ") In [49]: a Out[49]: 'gello' In [50]: id(a) Out[50]: 140071263881040
  • 您是否愿意根据我上面给出的示例证明您的项目分配问题
  • 项目分配在不可变类型中不是问题。在您的情况下,您正在更改字符串 a 但在内存中将其分配给一个新变量。在我的情况下,项目分配不会像列表或字典那样改变变量的内存。如果您正在替换,则您正在创建一个新变量而不是修改现有变量
  • @ArgusMalware 在您的情况下,两个 id 相等,因为第一个被 GC 回收,所以第二个重新使用内存。
【解决方案2】:

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

【讨论】:

  • +1 获取 Python 文档的链接。然而,我花了一些时间才意识到今天你需要区分 Python 2 和 3 - 我更新了答案以强调这一点。
【解决方案3】:

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

有些 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() 要快得多,因为它是字符串对象的一部分。

【讨论】:

    【解决方案4】:

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

    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
    

    【讨论】:

    • 绝对不正确。分配从不创建副本。请阅读nedbatchelder.com/text/names.html 在第一种情况下,x=10 只是另一个赋值,而x[2] = 5 调用一个mutator 方法。 int对象只是缺少mutator方法,但是python赋值的语义不依赖于类型
    【解决方案5】:

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

    假设您创建了一个列表

    a = [1,2]
    

    如果你说:

    b = a
    b[1] = 3
    

    即使您重新分配了 B 的值,它也会重新分配 a 的值。这是因为当您分配“b = a”时。您将“引用”传递给对象,而不是值的副本。字符串、浮点数等不是这种情况。这使得列表、字典等是可变的,但布尔值、浮点数等是不可变的。

    【讨论】:

      【解决方案6】:

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

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

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

      什么不能意外改变?

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

      什么可以?

      • 列表类对象(列表、字典、集合、bytearray())
      • 此处的帖子还提到了类和类实例,但这可能取决于类继承自什么和/或它的构建方式。

      “意外”是指其他语言的程序员可能不会想到这种行为(Ruby 除外,可能还有一些其他“类似 Python”的语言)。

      加入本次讨论:

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

      使用列表,简单的解决方案是像这样构建一个新的:

      list2 = list(list1)

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

      当你传入可变结构时,函数可以改变原始的。怎么分辨?

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

      非标准方法(如果有帮助): 在 MIT 许可下发布的 github 上找到了这个:

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

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

      这就是我目前在这个主题上积累的全部内容。欢迎其他想法,更正等。谢谢。

      【讨论】:

        【解决方案7】:

        最简单的答案:

        可变变量是其值可能在原地改变的变量,而在不可变变量中,值的改变不会原地发生。修改不可变变量将重建相同的变量。

        示例:

        >>>x = 5
        

        将创建一个由 x 引用的值 5

        x -> 5

        >>>y = x
        

        此语句将使 y 引用 x 中的 5 个

        x -------------> 5

        >>>x = x + y
        

        因为 x 是一个整数(不可变类型)已被重建。

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

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

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

        【讨论】:

          【解决方案8】:

          在 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 = (1, 2),然后尝试改变x,这是不可能的。我发现检查可变性的一种方法是hash,它至少适用于内置对象。 hash(1)hash('a')hash((1, 2))hash(True)都可以,hash([])hash({})hash({1, 2})都不行。
          • @semicolon 对于用户定义的类,如果对象定义了__hash__() 方法,则hash() 将起作用,即使用户定义的类通常是可变的。
          • @augurar 我的意思是是的,但是 Python 中没有任何东西可以保证任何事情,因为 Python 没有真正的静态类型或形式保证。但是hash 方法仍然是一个不错的方法,因为可变对象通常不应该有__hash__() 方法,因为将它们作为字典中的键是很危险的。
          • @augurar 和分号(或其他人,如果他们知道的话):__hash__() 解决方案......自定义类的创建者是否必须添加它才能存在?如果是这样,那么规则是如果存在,则对象应该是不可变的;如果它不存在,我们无法判断,因为如果关闭,创建者可能只是离开了。
          【解决方案9】:

          在我看来,您正在与可变/不可变实际上意味着什么的问题作斗争。所以这里是一个简单的解释:

          首先我们需要一个基础来进行解释。

          因此,将您编程的任何东西都视为虚拟对象,即作为二进制数序列保存在计算机内存中的东西。 (不过,不要想得太难。^^)现在在大多数计算机语言中,您不会直接使用这些二进制数,而是更多地使用二进制数的解释。

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

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

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

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

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

          最后的评论:

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

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

          因此,虽然您现在可能了解含义上的差异,但您仍然需要了解每种编程语言的实际实现。 ...确实可能有一种语言的目的,其中 6 可能会变成 7。然后这又会是一些非常疯狂或有趣的东西,比如平行宇宙的模拟。^^

          (2) 这个解释肯定不科学,只是为了帮助你理解可变和不可变的区别。

          【讨论】:

            【解决方案10】:

            如果该类的每个对象在实例化时都有一个固定值且不能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
            

            检查此表:

            source

            【讨论】:

            • 如何以比您上面展示的更简洁的方式修改 python 字符串的组件?
            • @LukeDavis 你可以做my_string = 'h' + my_string[1:]。这将生成一个名为 my_string 的新字符串,而原来的 my_string 已消失(打印 id(my_string) 以查看此内容)。当然这不是很灵活,对于更一般的情况,您可以转换为列表并返回:l = list(my_string)l[0] = 'h'my_string = ''.join(l)
            【解决方案11】:

            常见的不可变类型:

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

            常见的可变类型(几乎所有其他):

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

            快速测试类型是否可变的一个技巧是使用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)
            

            【讨论】:

            • 很好解释。喜欢id() 的检查概念。 +1。
            • 实际上这里使用id() 是一种误导。给定对象在其生命周期内始终具有相同的 id,但由于垃圾回收,存在于不同时间的不同对象可能具有相同的 id。
            • 如果其他人对@augurar 评论的更多信息感兴趣,这里有一个我发现可能感兴趣的相关主题:*.com/questions/52096582/how-unique-is-pythons-id
            【解决方案12】:

            如果您是从另一种语言(除了与 Python 非常相似的语言,如 Ruby 除外)开始使用 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++ 这样的语言中的变量是内存中的类型化位置。如果aint,这意味着编译器知道应该将其解释为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 的语义。

            【讨论】:

            • 说一个='嗨'。 a[0] = 'f' 将有 'print a' 打印出 'fi' (到目前为止我是对的吗?),所以当你说它不会改变 a[0] 而不是 a,这是什么意思? a[n] 现在是否也有它自己的位置,并且改变它的值会将它指向一个不同的值?
            【解决方案13】:

            一个可变对象必须至少有一个能够改变该对象的方法。例如,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,这两个变量都指向同一个对象,但实际上这些变量不能相互通信,因为对象是不可变的,如果你这样做@ 987654341@,现在b 指向一个新对象,而a 仍然指向旧对象,不知道b 指向什么。

            但是如果c 是一个list,并且你分配a = c,现在ac 可以“通信”,因为list 是可变的,如果你这样做@ 987654351@,然后只需检查a 即可收到消息。

            (顺便说一句,每个对象都有一个唯一的 id 编号,您可以使用 id(x) 获得。因此您可以检查对象是否相同或不检查其唯一 id 是否已更改。)

            【讨论】:

              【解决方案14】:

              什么?浮点数是不可变的?但我不能这样做

              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]
              

              【讨论】:

              • 你解释的对我来说意味着:可变变量通过引用传递,不可变变量通过值传递。这是正确的吗?
              • 几乎,但不完全是。从技术上讲,Python 中的所有变量都是通过引用传递的,但其语义更像是 C 中的按值传递。与您进行类比的反例是 def f(my_list): my_list = [1, 2, 3]。使用 C 中的传递引用,参数的值可以通过调用该函数来改变。在 Python 中,该函数不执行任何操作。 def f(my_list): my_list[:] = [1, 2, 3] 会做点什么。
              • 可以就地更改可变类型。不可变类型不能原地改变。这就是python看待世界的方式。这与变量如何传递给函数无关。
              • Python 的语义和 C++ 的传递引用语义的主要区别在于,赋值在 Python 中不是突变,而是在 C++ 中。 (但当然,增加的赋值,比如a += b 有时 突变这一事​​实使这变得复杂。事实上,分配给较大对象的一部分有时意味着该较大对象的突变,而不是突变部分——例如,a[0] = b 不会改变 a[0],但它可能会改变 a……这就是为什么最好不要尝试用 C++ 来描述事物,而只是描述 Python 做了什么用它自己的话……)
              • 我发现这个答案具有误导性,因为它不使用 id(),这对于理解不可变的含义至关重要。
              【解决方案15】:

              一种思维方式的区别:

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

              【讨论】:

              • 这是不正确的。 Python 中的所有作业都是通过引用完成的。不涉及复制。
              【解决方案16】:

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

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

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

              某些对象的值可以改变。值可以改变的对象 据说是可变的;一旦它们的值是不可改变的对象 被创建的被称为不可变的。

              (不可变容器的值 包含对可变对象的引用的对象可以更改 后者的值改变了;但是容器仍然 被认为是不可变的,因为它包含的对象集合 无法更改。因此,不变性并不严格等同于具有 一个不可改变的值,它更微妙。)

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

              【讨论】:

              • +1 请注意,尽管只有一些扩展类型(您可能想查看您的定义,所有 Python 的内置类型都是用 C 实现的)是不可变的。其他人(我敢说大多数)是完全可变的。
              • @delnan 你叫什么“扩展类型”
              • @eyquem:我在回答中错误地使用了“扩展类型”这个词,而 delnan 指的是那个。在他发表评论后,我修改了我的答案并避免使用这个词。
              【解决方案17】:

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

              ints 和 floats 是不可变的。如果我这样做了

              a = 1
              a += 5
              

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

              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,您会收到错误消息,因为tuples 没有.clear() 方法——您必须通过@987654360 @ as val 让它工作,在这种情况下你会得到一个空的SortedKeyDict 结果。

              【讨论】:

              • 这是很好的解释。喜欢这个问题和很多有趣的(新的)观点来解释它。