检查Python中的可变性吗?
-
09-10-2019 - |
解决方案
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)
, ,忽略任何其他属性。
最常见的用途是缓存和回忆:在逻辑上没有属性缓存或不改变对象,通常对平等没有影响。
(我要在这里停下来 - 请不要一次问五个问题。)
模块中有mutablesequence,mutableset,mutablepapping 收藏. 。可用于检查预制类型的突变性。
issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))
如果您想在用户定义的类型上使用它,则该类型必须从其中一种继承或注册为虚拟子类。
class x(MutableSequence):
...
或者
class x:
...
abc.ABCMeta.register(MutableSequence,x)
真的没有 保证 可见的类型也是不可变的,但至少,正确实施 __hash__
要求类型是不变的,就其自己的哈希和平等而言。这不会以任何特定的方式执行。
但是,我们都是成年人。实施是不明智的 __hash__
除非您真的意味着它。粗略地说,这只是说,如果实际上可以将一种类型用作字典键,那么它旨在以这种方式使用。
如果您正在寻找的东西 喜欢 一个命令,但也不可变,然后 namedtuple
从标准库中的内容可能是您最好的选择。诚然,这不是一个很好的近似值,但这是一个开始。
dict 钥匙 必须是可观的,这意味着他们有一个不变的 哈希 价值。 dict 值 可能是或可能不可变的;但是,如果它们是可变的,这会影响您的第二个问题。
“钥匙的更改”将不会在两个命令之间反映。不变值的变化也不会反映出字符串。由于对象由ID存储(IE参考)存储,因此将反映对可变对象的更改,例如用户定义的类。
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
我认为这是由前两个答案解释的。
在这方面,我不知道。
一些其他想法:
有两件事要了解,以了解 钥匙: :键必须是 可用 (这意味着他们实施 object.__hash__(self)
)而且它们也必须“可比”(这意味着他们实施类似 object.__cmp__(self)
)。文档中的一项重要收入:默认情况下,用户定义的对象的哈希函数返回 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!
值 更容易理解,dict存储对对象的引用。阅读有关Hashable的部分。诸如字符串之类的东西是不变的,如果您“更改”它们,那么您现在将其更改为“ dict”,现在引用一个新对象。可变的对象可以“就地更改”,因此两个dicts的价值将会改变。
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.
无论如何,这是所有这一切的要点:
- 为了 钥匙, ,可能不是 可变性, , 反而 可比性和可比性, ,你在乎。
- 您关心值的值,因为根据定义,可以更改可变对象的值而不更改对其的引用。
我认为没有一种检验这两个要点的一般方法。适用性的测试将取决于您的用例。例如,检查对象是否实现或未实现 __hash__
和比较(__eq__
或者 __cmp__
) 功能。就像在 __setattr__
以某种方式确定它是否可变的方法。
dicts是无序的键:值对。钥匙必须是不变的,因此可以使用。为了确定对象是否可以使用,您可以使用 hash()
功能:
>>> hash(1)
1
>>> hash('a')
12416037344
>>> hash([1,2,3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> hash((1,2,3))
2528502973977326415
>>> hash({1: 1})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
另一方面,值可以是任何对象。如果您需要检查对象是否不变,那么我会使用 hash()
.