【发布时间】:2012-04-28 16:05:24
【问题描述】:
原始问题:
(我的问题适用于 Python 3.2+,但我怀疑自 Python 2.7 以来这已经改变了。)
假设我使用我们通常期望创建对象的表达式。示例:[1,2,3]; 42; 'abc'; range(10); True; open('readme.txt'); MyClass(); lambda x : 2 * x;等等
假设两个这样的表达式在不同的时间执行并且“计算为相同的值”(即,具有相同的类型,并且比较为相等)。在什么条件下,Python 提供了我所谓的distinct object保证,即两个表达式实际上创建了两个不同的对象(即x is y 计算为False,假设这两个对象绑定到@987654331 @ 和y,并且两者同时在范围内)?
我了解,对于任何可变类型的对象,“不同对象保证”都成立:
x = [1,2]
y = [1,2]
assert x is not y # guaranteed to pass
我还知道对于某些不可变类型(str、int),保证不成立;而对于某些其他不可变类型(bool、NoneType),则相反的保证成立:
x = True
y = not not x
assert x is not y # guaranteed to fail
x = 2
y = 3 - 1
assert x is not y # implementation-dependent; likely to fail in CPython
x = 1234567890
y = x + 1 - 1
assert x is not y # implementation-dependent; likely to pass in CPython
但是所有其他不可变类型呢?
特别是,在不同时间创建的两个元组可以具有相同的身份吗?
我对此感兴趣的原因是我将图中的节点表示为int 的元组,并且域模型使得任何两个节点都是不同的(即使它们由具有相同值的元组表示)。我需要创建节点集。如果 Python 保证在不同时间创建的元组是不同的对象,我可以简单地将 tuple 子类化以将相等重新定义为表示身份:
class DistinctTuple(tuple):
__hash__ = tuple.__hash__
def __eq__(self, other):
return self is other
x = (1,2)
y = (1,2)
s = set(x,y)
assert len(s) == 1 # pass; but not what I want
x = DistinctTuple(x)
y = DistinctTuple(y)
s = set(x,y)
assert len(s) == 2 # pass; as desired
但是如果不能保证在不同时间创建的元组是不同的,那么上面的方法是一种糟糕的技术,它隐藏了一个可能随机出现的休眠错误,并且可能很难复制和找到。在这种情况下,子类化将无济于事。我实际上需要向每个元组添加一个额外的元素,一个唯一的 id。或者,我可以将我的元组转换为列表。无论哪种方式,我都会使用更多的内存。显然,除非我原来的子类化解决方案不安全,否则我不希望使用这些替代方案。
我的猜测是 Python 不为不可变类型提供“不同对象保证”,无论是内置的还是用户定义的。但是我在文档中没有找到明确的说明。
更新 1:
@LuperRouch @larsmans 感谢您到目前为止的讨论和回答。这是我仍然不清楚的最后一个问题:
有没有机会创建一个用户定义的对象 type 会导致重用现有对象?
如果可能,我想知道如何验证我使用的任何类是否可能表现出这种行为。
这是我的理解。每当创建用户定义类的对象时,首先调用该类的__new__() 方法。如果重写此方法,则语言中的任何内容都不会阻止程序员返回对现有对象的引用,从而违反了我的“不同对象保证”。显然,我可以通过检查类定义来观察它。
我不确定如果用户定义的类没有覆盖__new__()(或明确依赖基类中的__new__())会发生什么。如果我写
class MyInt(int):
pass
对象创建由int.__new__() 处理。我希望这意味着我有时可能会看到以下断言失败:
x = MyInt(1)
y = MyInt(1)
assert x is not y # may fail, since int.__new__() might return the same object twice?
但在我对 CPython 的实验中,我无法实现这种行为。这是否意味着该语言为不覆盖__new__ 的用户定义类提供了“不同的对象保证”,还是只是一种任意的实现行为?
更新 2:
虽然我的DistinctTuple 被证明是一个非常安全的实现,但我现在明白我使用DistinctTuple 对节点建模的设计理念非常糟糕。
标识运算符已在该语言中可用;让== 的行为方式与is 相同在逻辑上是多余的。
更糟糕的是,如果 == 可以做一些有用的事情,我让它不可用。例如,很可能在我的程序中的某个地方,我想查看两个节点是否由同一对整数表示; == 本来是完美的——事实上,这就是它默认的作用......
更糟糕的是,大多数人实际上确实希望== 比较一些“值”而不是身份——即使对于用户定义的类也是如此。他们会被我的只看身份的覆盖在不知不觉中发现。
最后...我必须重新定义== 的唯一原因是允许具有相同元组表示的多个节点成为集合的一部分。这是错误的做法!需要改变的不是== 行为,而是容器类型!我只需要使用多重集合而不是集合。
简而言之,虽然我的问题可能对其他情况有一些价值,但我绝对相信创建class DistinctTuple 对我的用例来说是一个糟糕的主意(我强烈怀疑它根本没有有效的用例)。
【问题讨论】:
-
您不能创建用户定义的不可变类型 AFAIK。在上面的示例中,两个 DistinctTuple 对象将始终具有不同的 ID。
-
@LuperRouch:你可以,甚至可以通过重载
__new__获得None和bool提供的那种保证,但是对象的不变性不一定由实施。 -
@larsmans:确定你可以在你的
__new__中返回一个元组,但是你没有创建用户定义的对象。 -
@LuperRouch:您可以创建一个
MyTuple对象池并在MyTuple__new__中从中返回对象。 -
@larsmans:我不认为这会让 Python 将这些对象视为不可变的,例如 str 和 tuple (不能保证对象池是不可修改的)。
标签: python object python-3.x object-identity