从表面上看,这个问题看起来像重复的问题, 但这不是通常的带有列表的mutable surprise的。
稍后再详细介绍,简而言之
self.facts = facts
print("facts: ", id(facts))
print("self.facts: ", id(self.facts))
有时self.facts
和facts
并不相同。
该应用是单线程的,区别持续存在, 因此,不是似乎是竞争条件或缓冲区延迟。
我确实清除了缓存:
find . -name "*.pyc" -delete
我试图提出一个mcve, 但无法重现该错误。这是尝试。
class Fact():
def __init__(self, a):
self.a = a
def __eq__(self, other):
return self.a == self.b
facts1 = [Fact(1), Fact(2), Fact(3)]
id(facts1)
facts2 = facts1
id(facts2)
# same as facts1
facts3 = [Fact(1), Fact(2), Fact(3)]
id(facts3)
facts2 = facts3
id(facts2)
# same as fact3, as it should
可以在this issue中找到快照和复制步骤。
一旦__eq__
被expanded还要检查确实发生变化的字段,则facts
和self.facts
都拥有相等的元素(就{{ 1}}),足够好,但不完全相同,这仍然很奇怪。
这是已知的吗?这是我的python版本:
__eq__
会发生什么?
答案 0 :(得分:4)
这种行为可能是由许多事情引起的,所有这些事情控制如何访问对象的属性。
从这个意义上讲,self.x = y
与x = y
有很大的不同。前者尝试使用"x"
设置属性y
在对象self
上,而后者将名称"x"
绑定到本地范围内的对象y
。
Descriptors可以控制属性的方式
通过定义特殊方法__get__
,__set__
,__delete__
处理。一个例子:
from copy import copy
class Descriptor:
def __get__(self, obj, cls):
return copy(self.list_obj) # Return a copy -> id changes.
def __set__(self, obj, val):
self.list_obj = copy(val) # Store a copy -> id changes.
class Foo:
facts = Descriptor()
facts = [1, 2, 3]
obj = Foo()
obj.facts = facts
assert id(obj.facts) != id(facts) # ids are different.
property
可能是最重要的使用场景之一
数据描述符。因此,工作原理非常相似:
from copy import copy
class Foo:
@property
def facts(self):
return copy(self._facts) # Return a copy -> id changes.
@facts.setter
def facts(self, val):
self._facts = copy(val) # Store a copy -> id changes.
facts = [1, 2, 3]
obj = Foo()
obj.facts = facts
assert id(obj.facts) != id(facts) # ids are different.
__getattr__
和__setattr__
通过定义方法__getattr__
和
__setattr__
类可以控制属性访问
其实例。例如:
from copy import copy
class Foo:
def __getattr__(self, name):
return copy(super().__getattr__(name)) # Return a copy -> id changes.
def __setattr__(self, name, val):
super().__setattr__(name, copy(val)) # Store a copy -> id changes.
facts = [1, 2, 3]
obj = Foo()
obj.facts = facts
assert id(obj.facts) != id(facts) # ids are different.
您可以检查type(obj).facts
以确定是否facts
被定义为描述符。
同样,您可以检查type(obj).__(get|set)attr__
以查看基类的 any 是否已定义
这种特殊的方法。值得一提的是,如果定义了上述任何一种方法,也可以使用
在按方法解析顺序排列的任何类上(__mro__
,即父类)。
因此,您需要检查例如:
any('__getattr__' in vars(cls) for cls in type(obj).__mro__)
您链接的类继承自
this class定义
__setattr__
以某种方式检查
等于具有现有值的平等(==
;而不是标识(is
),如果比较相等则返回。
这意味着,如果您有两个比较相等的列表,即self.facts == facts
,则
做self.facts = facts
只会返回而不设置属性。因此没有改变
并且两个对象(self.facts
和facts
)仍然不同。代码基本上可以归结为:
class Foo:
def __setattr__(self, name, val):
if self.__dict__.get(name, None) == val: # Could also use `getattr(self, name, None)`.
return
super().__setattr__(name, val)
facts = [1, 2, 3]
obj = Foo()
obj.facts = [1, 2, 3]
assert obj.facts == facts # The two lists compare equal,
obj.facts = facts # hence nothing will happen here,
assert obj.facts == facts # they still compare equal,
assert id(obj.facts) != id(facts) # but are still two distinct objects.