如何检查Python中的可变性?

43 投票
7 回答
25088 浏览
提问于 2025-04-16 08:09

考虑一下这个代码

a = {...} # a is an dict with arbitrary contents
b = a.copy()
  1. 字典中的键和值的可变性有什么作用?
  2. 我怎么才能确保一个字典的键或值的变化不会影响到另一个字典?
  3. 这和字典键的可哈希限制有什么关系?
  4. 在Python 2.x和Python 3.x之间有没有行为上的区别?

我怎么检查一个类型在Python中是否是可变的?

7 个回答

8

在Python的一个叫做collections的模块里,有一些东西叫做MutableSequence、MutableSet和MutableMapping。这些东西可以用来检查一些现成类型是否是可变的。

issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))

如果你想在自己定义的类型上使用这个功能,那么这个类型必须要么是从这些类型中继承的,要么是注册为一个虚拟子类。

class x(MutableSequence):
    ...

或者

class x:
    ...

abc.ABCMeta.register(MutableSequence,x)
17

在Python的语言层面,其实并没有什么可变性和不可变性。某些对象是无法被改变的(比如字符串和元组),所以它们在实际使用中是“不可变”的,但这只是个概念;在语言层面并没有什么属性来表示这一点,对你的代码和Python本身都是如此。

不可变性对字典其实并不重要;用可变的值作为键是完全可以的。关键在于比较和哈希:对象必须始终和它自己相等。例如:

class example(object):
    def __init__(self, a):
        self.value = a
    def __eq__(self, rhs):
        return self.value == rhs.value
    def __hash__(self):
        return hash(self.value)

a = example(1)
d = {a: "first"}
a.data = 2
print d[example(1)]

在这里,example并不是不可变的;我们通过a.data = 2在修改它。然而,我们在哈希中使用它作为键时没有任何问题。为什么呢?因为我们改变的属性对相等性没有影响:哈希值没有改变,example(1)始终等于example(1),不管其他属性如何。

这种情况最常见的用法是缓存和记忆化:一个属性是否被缓存并不会在逻辑上改变对象,通常也不会影响相等性。

(我就说到这里——请不要一次问五个问题。)

26
  1. 必须是可哈希的——这就是你需要遵守的规则。特别是,你可以有一个用户自定义的类,它的实例是可哈希的,但也可以是可变的——不过这通常不是个好主意

  2. 通过不在两个字典之间共享值来实现。这通常可以共享键,因为它们应该是可变的(内置类型就是如此)。复制字典,使用copy标准库模块的方式,绝对是安全的。这里调用字典构造函数也可以:b = dict(a)。你也可以使用不可变的值。

  3. 所有内置的不可变类型都是可哈希的。所有内置的可变类型都不是可哈希的。字典键的限制只是要求内置的hash函数能在这个键上工作,而这又要求它的类实现__hash__这个特殊方法。

    不过,如果一个对象的哈希值在其生命周期内可能会改变,代码可能会出现微妙或意外的错误。举个极端的例子:

    >>> import random
    >>> class x:
    ...     def __hash__(self): return random.randint(0, 100)
    ... 
    >>> a, b = x(), x()
    >>> c = {a:1, b:2}
    >>> c[a]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    KeyError: <__main__.x object at ...>
    

    这就是为什么试图创建一个可变的、可哈希的类型是不明智的:哈希值应该保持不变,但也应该反映对象的状态。

  4. 不。

如果一个类型不是不可变的,那它就是可变的。如果一个类型是内置的不可变类型,比如strintlongboolfloattuple,还有可能有其他我忘记的类型,那它就是不可变的。用户自定义的类型总是可变的。

如果一个对象不是不可变的,那它就是可变的。如果一个对象递归地只由不可变类型的子对象组成,那它就是不可变的。因此,一个包含列表的元组是可变的;你不能替换元组中的元素,但你可以通过列表接口修改它们,从而改变整体数据。

撰写回答