什么对象都保证有不同的身份呢?(What objects are guaranteed to hav

2019-09-16 14:32发布

原来的问题:

(我的问题适用于Python的3.2以上版本,但我怀疑这是由于Python 2.7这种情况已经改变。)

假设我用的是我们通常希望创建一个对象的表达式。 实施例: [1,2,3] ; 42 ; 'abc' ; range(10) ; True ; open('readme.txt') ; MyClass() ; lambda x : 2 * x ; 等等

假设两个这样的表达式在不同的时间执行,并且“评价为相同的值的”(即,具有相同的类型,并且比较结果为相等)。 在什么情况下的Python提供我所说的不同的对象保证两个表达式实际创建两个不同的对象(即x is y的计算结果为False ,假设两个对象绑定到xy ,无一不是在范围上的同时)?

我明白,任何可变类型的对象时,“不同的对象保证”认为:

x = [1,2]
y = [1,2]
assert x is not y # guaranteed to pass 

我也知道某些稳定的类型( strint )保证不成立; 而对于某些其它恒定类型( boolNoneType ),相反的保证成立:

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谢谢你的讨论,到目前为止答案。 这里的最后一个问题,我目前还不清楚有:

是否有任何机会,在现有对象的再利用创建的用户定义类型的结果对象的?

如果这是可能的,我想知道我可以验证我是否可能会表现出这样的行为,任何工作类。

这是我的理解。 创建用户定义的类的对象的任何时间,类__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是我的使用情况非常糟糕的主意(我强烈怀疑它有没有有效的用例的话)。

Answer 1:

是否有任何机会,在现有对象的再利用创建的用户定义类型的结果对象的?

这会发生,如果,且仅当用户定义类型是明确设计做到这一点。 随着__new__()或一些元类。

我想知道我可以验证我是否可能会表现出这样的行为,任何工作类。

使用源,卢克。

当谈到int ,小整数是预分配的,并且无论你创建一个整数计算,这些预分配的整数使用。 当你做你无法得到这个工作MyInt(1) is MyInt(1)因为你有什么有不是整数。 然而:

>>> MyInt(1) + MyInt(1) is 2
True

这是因为,当然敏(1)+敏(1)不返回敏。 它返回一个int,因为那是什么__add__整数回报(而这正是发生了预分配的整数检查为好)。 这如果有的话只是表明,在一般的子类int是不是特别有用。 :-)

这是否意味着语言提供“不同的对象保障”为不覆盖新的用户定义的类,或者是它只是一个任意实施的行为吗?

它并不能保证它,因为没有这样做的必要。 默认行为是创建一个新的对象。 你必须覆盖它,如果你不希望这样的事情发生。 有一个保证是没有意义的。



Answer 2:

Python的引用, 第3节,数据模型 :

恒定类型,即计算新值的操作可能实际上的基准返回到任何现有的对象具有相同的类型和值,而对于可变对象,这是不允许的

(着重。)

在实践中,似乎只有CPython的缓存空的元组:

>>> 1 is 1
True
>>> (1,) is (1,)
False
>>> () is ()
True


Answer 3:

如果元组在不同的时间所创建的Python的保证是不同的对象,我可以简单地子tuple重新确定的平等是指身份。

你似乎混淆了子类是如何工作的:如果B子类A ,那么B到达使用所有的A的方法[1] -但A方法,将工作的情况下, B不是的, A 。 这甚至适用于__new__

--> class Node(tuple):
...   def __new__(cls):
...     obj = tuple.__new__(cls)
...     print(type(obj))
...     return obj
...
--> n = Node()
<class '__main__.Node'>

作为@larsman中指出Python的参考 :

为不可改变的类型,操作该计算新值可以参考实际上返回到任何现有的对象具有相同的类型和值,而对于可变对象,这是不允许

但是,请记住这一段讲的Python的内置类型,而不是用户定义类型(可以去疯狂几乎任何他们喜欢的方式)。


我理解了上面摘录,以保证Python将不会返回一个新的可变对象,它是与现有的对象,而且是用户定义和Python代码创建的类本质上是可变的(再次,见上面注有关疯狂的用户定义的类)。

更完整的Node类(注意,您不需要明确refererence tuple.__hash__ ):

class Node(tuple):
    __slots__ = tuple()
    __hash__ = tuple.__hash__
    def __eq__(self, other):
        return self is other
    def __ne__(self, other):
        return self is not other

--> n1 = Node()
--> n2 = Node()
--> n1 is n2
False
--> n1 == n2
False
--> n1 != n2
True

--> n1 <= n2
True
--> n1 < n2
False

你可以从最后两个比较看,你可能也想重写__le____ge__方法。

[1]我所知道的唯一的例外是__hash__ -如果__eq__是在子类中定义,但该子类要为父类的__hash__它必须明确这样说(这是一个Python 3的变化)。



文章来源: What objects are guaranteed to have different identity?