检查Python中的可变性?

问题描述 投票:30回答:6

考虑此code

a = {...} # a is an dict with arbitrary contents
b = a.copy()
  1. 可变性在字典的键和值中起什么作用?
  2. 如何确保对一个字典的键或值的更改不会在另一个字典中反映出来?
  3. 这与字典键的可散列constraint有什么关系?
  4. [Python 2.x和Python 3.x之间在行为上是否有差异?

如何检查类型在Python中是否可变?

python python-3.x immutability python-2.x hashable
6个回答
19
投票

1)键不能是可变的,除非您有一个用户定义的类,该类是可哈希的,但也是可变的。这就是强迫您执行的所有操作。 但是,使用可哈希的可变对象作为dict键可能不是一个好主意。

2)不共享两个字典之间的值。共享密钥是可以的,因为它们必须是不可变的。从copy模块的意义上讲,复制字典绝对是安全的。在这里调用dict构造函数也可以:b = dict(a)。您也可以使用不可变值。

3)所有内置的不可变类型都是可哈希的。所有内置的可变类型均不可哈希。为了使对象可哈希化,即使对象被突变,它在整个生命周期中也必须具有相同的哈希值。

4)并不是我所知道的;我正在描述2.x。

如果类型是不可变的,则该类型是可变的。如果类型是内置的不可变类型,则它是不可变的:strintlongboolfloattuple,可能还有其他几个我忘记的类型。用户定义的类型始终是可变的。

如果对象是不可变的,则该对象是可变的。如果对象仅由不可变类型的子对象递归组成,则它是不可变的。因此,列表的元组是可变的;您不能替换元组的元素,但是可以通过列表界面修改它们,从而更改整体数据。


10
投票

在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),而忽略了其他任何属性。[最常见的用法是缓存和备注:缓存属性或不缓存属性在逻辑上不会更改对象,通常对相等性没有影响。

((我要在这里停止,请不要一次问五个问题。)


4
投票
collections模块中有MutableSequence,MutableSet,MutableMapping。可以用来检查预制类型的可变性。

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

如果要在用户定义的类型上使用此类型,则该类型必须从其中一种类型继承或注册为虚拟子类。

class x(MutableSequence): ...

class x: ... abc.ABCMeta.register(MutableSequence,x)


3
投票
确实没有

保证可以哈希的类型也是不可变的,但是至少,正确实现__hash__要求该类型相对于其自身的哈希和相等性是不可变的。这不是以任何特定方式强制执行的。

但是,我们都是成年人。除非确实需要,否则实施__hash__是不明智的。粗略地说,这可以归结为说,如果实际上可以将一种类型用作字典键,那么就打算以这种方式使用它。

如果您要查找的是[[like

字典,但也是不可变的,那么namedtuple可能是标准库中的最佳选择。诚然,这不是一个很好的近似值,但这只是一个开始。

2
投票
    dict
  1. keys必须是可哈希的,这意味着它们具有不变的

    hash值。 dict values可能是可变的,也可能不是可变的;但是,如果它们易变,则会影响您的第二个问题。

  2. “键的更改”将不会反映在两个字典之间。对不可变值(例如字符串)的更改也不会反映出来。对可变对象(例如用户定义的类)的更改将得到反映,因为该对象是通过ID(即引用)存储的。
  3. namedtuple
  4. 我认为这是前两个答案所解释的。
  5. 这方面我不知道。

  • 一些其他想法:

    要了解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. 方法以确定它是否可变。

    -2
    投票
    __eq__

    另一方面,值可以是任何对象。如果您需要检查对象是否不可变,则可以使用__cmp__

    © www.soinside.com 2019 - 2024. All rights reserved.