属性与先前的属性分配操作的右侧值不同

时间:2019-01-27 23:06:40

标签: python python-3.x

从表面上看,这个问题看起来像重复的问题, 但这不是通常的带有列表的mutable surprise的。

稍后再详细介绍,简而言之

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

可以在this issue中找到快照和复制步骤。

一旦__eq__expanded还要检查确实发生变化的字段,则factsself.facts都拥有相等的元素(就{{ 1}}),足够好,但不完全相同,这仍然很奇怪。

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

__eq__

会发生什么?

1 个答案:

答案 0 :(得分:4)

这种行为可能是由许多事情引起的,所有这些事情控制如何访问对象的属性。 从这个意义上讲,self.x = yx = 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__)

OP的具体示例

您链接的类继承自 this class定义 __setattr__ 以某种方式检查 等于具有现有值的平等==;而不是标识(is),如果比较相等则返回。 这意味着,如果您有两个比较相等的列表,即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.