首页 > 解决方案 > 属性与前面的属性赋值操作的右侧值不同

问题描述

这个问题可能表面上看起来像是重复的,但它不是关于列表通常的可变惊喜

稍后会有更多细节,但简而言之,就在之后

self.facts = facts
print("facts: ", id(facts))
print("self.facts: ", id(self.facts))

有时self.factsfacts不完全相同。

该应用程序是单线程的,并且差异持续存在,因此它似乎不是竞争条件或缓冲区延迟。

我确实清除了缓存: 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__并保持相等的元素(在 的意义上),足够好,但不完全相同,这仍然很奇怪。factsself.facts__eq__

那是已知的吗?这是我的python版本:

Python 3.6.5 (default, Mar 31 2018, 19:45:04) [GCC] on linux
on openSUSE Leap 15.0

会发生什么?

标签: pythonpython-3.x

解决方案


这种行为可能是由许多事情引起的,所有这些都控制着如何访问对象的属性。从这个意义上说self.x = yx = y. 前者尝试 在对象上设置属性"x",而后者将名称绑定到本地范围内的对象。yself"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.factsfacts) 仍然是不同的。代码基本上归结为:

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.

推荐阅读