__setattr__ 是在递增现有属性时调用的吗?

Is __setattr__ called when an existing attribute is incremented?

现有属性递增时是否调用__setattr__?

我有一个名为 C 的 class,我正在尝试超载 __setattr__。这是 class C:

中的一段代码
class C:

    def bump(self):
        self.a += 1
        self.b += 1
        self.c += 1

    def __setattr__(self,name,value):
        calling = inspect.stack()[1]
        if 'private_' in name:
            raise NameError("\'private_\' is in the variable name.")
        elif '__init__' in calling.function:
            self.__dict__['private_' + name] = value
        elif name.replace('private_', '') in self.__dict__:
            if self.in_C(calling):
                if name.replace('private_', '') in self.__dict__.keys():
                    old_value = self.__dict__[name.replace('private_', '')]
                    new_value = old_value + value
                    self.__dict__[name.replace('private_', '')] = new_value
                else:
                    self.__dict__[name.replace('private_','')] = value
            else:
                raise NameError()
        else:
            self.__dict__[name] = value

__setattr__,根据Python docs

object.__setattr__(self, name, value): Called when an attribute assignment is attempted. This is called instead of the normal mechanism (i.e. store the value in the instance dictionary). name is the attribute name, value is the value to be assigned to it.

我知道您可以为变量赋值(例如:C.__dict__[name] = value),但是当现有属性递增时怎么办,例如 bump() 中的 self.a += 1

假设属性abc已经定义好了,我称为 bump(),然后称为 __setattr__。但是,我收到此错误:

Error: o.bump() raised exception TypeError: unsupported operand type(s) for +=: 'NoneType' and 'int'

是否在递增现有属性时调用setattr?如果是这样,我将如何增加 setattr 中的现有属性?

注:假设在定义了a、b、c之后调用了bump()。此外,in_C(calling) 是一个函数,用于检查 __setattr__ 是从 __init__、C 内部的某个方法还是 C 外部的方法调用的。

如果需要进一步说明,请告诉我。

Python: Is __setattr__ called when an existing attribute is incremented?

答案是肯定的。使用简化版本的代码很容易看出这一点:

class C(object):

    def __init__(self, a):
        object.__setattr__(self, 'a', a)

    def __setattr__(self, name, value):
        print('Setting {} to {}'.format(name, value))
        object.__setattr__(self, name, value)


c = C(10)
c.a += 1

运行 该片段产生:

Setting a to 11

您发布的代码的问题是 += 在调用 __setattr__ 之前先调用 __getattribute__。如果该属性尚不存在,那就是失败。

解决方案是确保在调用 bump() 之前初始化属性:

class C(object):

    def __init__(self, a, b, c):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', a)
        object.__setattr__(self, 'c', a)

除了该修复之外,还有其他错误(例如在 inspect 中),但这应该可以帮助您入门。

虽然看起来

a += 1

相当于a.__iadd__(1)(如果a__iadd__),其实相当于:

a = a.__iadd__(1)    # (but `a` is only evaluated once.)

只是对于可变类型,__iadd__ returns 是同一个对象,所以你看不出有什么不同。

因此,如果目标是 c.a,则调用 c__setattr__。 同样,如果您执行 c['a']+=1.

之类的操作,则会调用 __setitem__

这样做是因为 Python 具有不可变类型,否则扩充赋值将不执行任何操作。

这在 grammar reference entry for augmented assignments(强调我的)中有记录:

An augmented assignment evaluates the target (which, unlike normal assignment statements, cannot be an unpacking) and the expression list, performs the binary operation specific to the type of assignment on the two operands, and assigns the result to the original target. The target is only evaluated once.

举例说明:

In [44]: class C(object):
    ...:     def __init__(self,a):
    ...:         self.a=a
    ...:     def __setattr__(self,attr,value):
    ...:         print "setting `%s' to `%r'"%(attr,value)
    ...:         super(C,self).__setattr__(attr,value)
    ...:     def __setitem__(self,key,value):
    ...:         print "setitem"
    ...:         return setattr(self,key,value)
    ...:     def __getitem__(self,key):
    ...:         return getattr(self,key)
    ...:

In [45]: c=C([])
setting `a' to `[]'

In [46]: c['a']+=[1]
setitem
setting `a' to `[1]'

In [29]: class A(int):
    ...:     def __iadd__(s,v):
    ...:         print "__iadd__"
    ...:         return int.__add__(s,v)
    ...:     def __add__(s,v):
    ...:         print "__add__"
    ...:         return int.__add__(s,v)
    ...:

In [30]: c.a=A(10)
setting `a' to `10'

In [31]: c.a+=1
__iadd__
setting `a' to `11'