python - 属性与前面的属性赋值操作的右侧值不同
问题描述
这个问题可能表面上看起来像是重复的,但它不是关于列表通常的可变惊喜。
稍后会有更多细节,但简而言之,就在之后
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
可以在此问题中找到快照和重现步骤。
一旦扩展以检查确实发生变化的字段,两者__eq__
并保持相等的元素(在 的意义上),足够好,但不完全相同,这仍然很奇怪。facts
self.facts
__eq__
那是已知的吗?这是我的python版本:
Python 3.6.5 (default, Mar 31 2018, 19:45:04) [GCC] on linux
on openSUSE Leap 15.0
会发生什么?
解决方案
这种行为可能是由许多事情引起的,所有这些都控制着如何访问对象的属性。从这个意义上说self.x = y
与x = y
. 前者尝试
在对象上设置属性"x"
,而后者将名称绑定到本地范围内的对象。y
self
"x"
y
描述符
描述符__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__
是否有任何基类定义了此特殊方法。这里值得注意的是,如果上述任何方法都定义在方法解析顺序中的任何类(__mro__
即父类)上,则它也可以工作。因此,您需要检查例如:
any('__getattr__' in vars(cls) for cls in type(obj).__mro__)
OP的具体例子
您链接的类继承自
此类,该类定义
__setattr__
的方式是检查是否与现有值相等(==
;而不是身份( )),如果它们比较相等则返回。is
这意味着特别是如果您有两个比较相等的列表,即 ie 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.
推荐阅读
- oauth-2.0 - 在javascript存储(cookie,本地存储)中存储OAuth2状态参数是否安全?
- php - 如何在 ORM Eloquent Laravel 6.2 中创建复合键
- file - 批处理文件中的 delims、DIR、SET
- angular - Angular httpHeaders post menthod 无法设置参数
- python - 具有梯度裁剪的对抗性文本生成,仍然面临梯度爆炸
- asp.net - 未捕获的 RangeError:超出最大调用堆栈大小 | ExtJs
- python - 为什么从文件中读取会在行中添加额外的空间?
- postgresql - 当一台服务器不可用时如何克服 dblink 问题
- dashboard - 如何根据 Sugar crm 中的用户角色隐藏默认列表视图仪表板?
- c# - 如何处理 ClosedXML 中的 StackOverflowException