如何检查Python中的可变性?
7 个回答
在Python的一个叫做collections的模块里,有一些东西叫做MutableSequence、MutableSet和MutableMapping。这些东西可以用来检查一些现成类型是否是可变的。
issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))
如果你想在自己定义的类型上使用这个功能,那么这个类型必须要么是从这些类型中继承的,要么是注册为一个虚拟子类。
class x(MutableSequence):
...
或者
class x:
...
abc.ABCMeta.register(MutableSequence,x)
在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)
,不管其他属性如何。
这种情况最常见的用法是缓存和记忆化:一个属性是否被缓存并不会在逻辑上改变对象,通常也不会影响相等性。
(我就说到这里——请不要一次问五个问题。)
键必须是可哈希的——这就是你需要遵守的规则。特别是,你可以有一个用户自定义的类,它的实例是可哈希的,但也可以是可变的——不过这通常不是个好主意。
通过不在两个字典之间共享值来实现。这通常可以共享键,因为它们应该是可变的(内置类型就是如此)。复制字典,使用
copy
标准库模块的方式,绝对是安全的。这里调用字典构造函数也可以:b = dict(a)
。你也可以使用不可变的值。所有内置的不可变类型都是可哈希的。所有内置的可变类型都不是可哈希的。字典键的限制只是要求内置的
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 ...>
这就是为什么试图创建一个可变的、可哈希的类型是不明智的:哈希值应该保持不变,但也应该反映对象的状态。
不。
如果一个类型不是不可变的,那它就是可变的。如果一个类型是内置的不可变类型,比如str
、int
、long
、bool
、float
、tuple
,还有可能有其他我忘记的类型,那它就是不可变的。用户自定义的类型总是可变的。
如果一个对象不是不可变的,那它就是可变的。如果一个对象递归地只由不可变类型的子对象组成,那它就是不可变的。因此,一个包含列表的元组是可变的;你不能替换元组中的元素,但你可以通过列表接口修改它们,从而改变整体数据。