考虑此code:
a = {...} # a is an dict with arbitrary contents
b = a.copy()
如何检查类型在Python中是否可变?
1)键不能是可变的,除非您有一个用户定义的类,该类是可哈希的,但也是可变的。这就是强迫您执行的所有操作。 但是,使用可哈希的可变对象作为dict键可能不是一个好主意。
2)不共享两个字典之间的值。共享密钥是可以的,因为它们必须是不可变的。从copy
模块的意义上讲,复制字典绝对是安全的。在这里调用dict构造函数也可以:b = dict(a)
。您也可以使用不可变值。
3)所有内置的不可变类型都是可哈希的。所有内置的可变类型均不可哈希。为了使对象可哈希化,即使对象被突变,它在整个生命周期中也必须具有相同的哈希值。
4)并不是我所知道的;我正在描述2.x。
如果类型是不可变的,则该类型是可变的。如果类型是内置的不可变类型,则它是不可变的:str
,int
,long
,bool
,float
,tuple
,可能还有其他几个我忘记的类型。用户定义的类型始终是可变的。
如果对象是不可变的,则该对象是可变的。如果对象仅由不可变类型的子对象递归组成,则它是不可变的。因此,列表的元组是可变的;您不能替换元组的元素,但是可以通过列表界面修改它们,从而更改整体数据。
在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)
,而忽略了其他任何属性。[最常见的用法是缓存和备注:缓存属性或不缓存属性在逻辑上不会更改对象,通常对相等性没有影响。((我要在这里停止,请不要一次问五个问题。)
issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))
如果要在用户定义的类型上使用此类型,则该类型必须从其中一种类型继承或注册为虚拟子类。
class x(MutableSequence): ...
或
class x: ... abc.ABCMeta.register(MutableSequence,x)
保证可以哈希的类型也是不可变的,但是至少,正确实现__hash__
要求该类型相对于其自身的哈希和相等性是不可变的。这不是以任何特定方式强制执行的。
__hash__
是不明智的。粗略地说,这可以归结为说,如果实际上可以将一种类型用作字典键,那么就打算以这种方式使用它。如果您要查找的是[[like
字典,但也是不可变的,那么namedtuple
可能是标准库中的最佳选择。诚然,这不是一个很好的近似值,但这只是一个开始。hash值。 dict values可能是可变的,也可能不是可变的;但是,如果它们易变,则会影响您的第二个问题。
namedtuple
我认为这是前两个答案所解释的。
这方面我不知道。
一些其他想法:
要了解keys的行为,需要了解两点主要知识:键必须为class T(object):
def __init__(self, v):
self.v = v
t1 = T(5)
d1 = {'a': t1}
d2 = d1.copy()
d2['a'].v = 7
d1['a'].v # = 7
d2['a'] = T(2)
d2['a'].v # = 2
d1['a'].v # = 7
import copy
d3 = copy.deepcopy(d2) # perform a "deep copy"
d3['a'].v = 12
d3['a'].v # = 12
d2['a'].v # = 2
(这意味着它们实现了hashable),并且还必须是“可比较的”(这意味着它们实现了类似的操作) object.__hash__(self)
)。文档中的一项重要内容:默认情况下,用户定义对象的哈希函数返回object.__hash__(self)
。
考虑此示例:
object.__cmp__(self)
Values更容易理解,字典存储对对象的引用。阅读有关散列的章节。诸如字符串之类的东西是不可变的,如果您“更改”它们,则在其中对其进行更改的字典现在将引用一个新对象。易变的对象可以“就地更改”,因此两个字典的值都将更改。
object.__cmp__(self)
无论如何,这是所有这些要点:
对于
id()
和比较(id()
或class K(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return self.x + self.y
k1 = K(1, 2)
d1 = {k1: 3}
d1[k1] # outputs 3
k1.x = 5
d1[k1] # KeyError! The key's hash has changed!
k2 = K(2, 1)
d1[k2] # KeyError! The key's hash is right, but the keys aren't equal.
k1.x = 1
d1[k1] # outputs 3
class NewK(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return self.x + self.y
def __cmp__(self, other):
return self.x - other.x
nk1 = NewK(3, 4)
nd1 = {nk1: 5}
nd1[nk1] # outputs 5
nk2 = NewK(3, 7)
nk1 == nk2 # True!
nd1[nk2] # KeyError! The keys' hashes differ.
hash(nk1) == hash(nk2) # False
nk2.y = 4
nd1[nk2] # outputs 5
# Where this can cause issues:
nd1.keys()[0].x = 5
nd1[nk1] # KeyError! nk1 is no longer in the dict!
id(nd1.keys()[0]) == id(nk1) # Yikes. True?!
nd1.keys()[0].x = 3
nd1[nk1] # outputs 5
id(nd1.keys()[0]) == id(nk1) # True!
)功能就足够了。同样,您可以通过某种方式“检查”对象的d1 = {1: 'a'}
d2 = d1.copy()
id(d1[1]) == id(d2[1]) # True
d2[1] = 'z'
id(d1[1]) == id(d2[1]) # False
# the examples in section 2 above have more examples of this.
方法以确定它是否可变。__eq__
另一方面,值可以是任何对象。如果您需要检查对象是否不可变,则可以使用__cmp__
。