Is __setattr__ called when adding an existing attribute?

Is __setattr__ called when adding an existing attribute?

I have a class named C and I am trying to overload __setattr__. This is a piece of code inside 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__, as per the Python Docs ,

object .__ setattr __ (self, name, value): Called when trying to assign an attribute. This is called instead of the normal mechanism (i.e. stores the value in the instance dictionary). name is the name of the attribute, value is the value that should be assigned to it.

I know you can assign a value to a variable (ex:) C.__dict__[name] = value

, but what about incrementing an existing attribute like self.a += 1

in bump()

?

Assuming that the attributes a , b and c are already defined , I called bump()

which was then called __setattr__

, However, I get this error:

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

      

Called setattr when you add an existing attribute? If so, how would I increase the existing attribute inside setattr?

Note. Suppose it bump()

is called after a, b, and c have been defined. In addition, it in_C(calling)

is a function that checks if it was called __setattr__

from __init__

, some method inside C or a method outside C.

Please tell me if further clarification is needed.

+3


source to share


2 answers


Python: is __setattr__ called when adding an existing attribute?

The answer is yes. This is easily seen with a simplified version of your code:

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

      

Running this snippet gives:

Setting a to 11

      



The problem with the code you posted is what +=

is calling __getattribute__

before it calls __setattr__

. This is what fails if the attribute doesn't exist yet.

The solution is to make sure the attributes are initialized before calling 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)

      

There are other bugs in addition to this fix (for example, when checking), but that should get you started.

+6


source


Although it may seem that

a += 1

      

is equivalent a.__iadd__(1)

(if it a

has __iadd__

), it is actually equivalent to:

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

      

It's just for mutable types, __iadd__

returns the same object, so you can't see the difference.

Thus, if target c.a

, is called c

__setattr__

. Likewise, __setitem__

called if you do something like c['a']+=1

.



This is because Python has immutable types for which extended assignment would otherwise do nothing.

This is documented in the grammar reference entry for extended assignments (emphasis mine):

Extended assignment evaluates the target (which, unlike normal assignment, cannot be unboxing), and the list expression performs a type-specific binary operation on two operands and assigns the result to the original target. The goal is evaluated only once.

To illustrate:

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'

      

-1


source







All Articles