polemon
polemon

Reputation: 4782

How to call __setattr__() correctly in Python3 as part of a class?

I want to call a function when an object attribute is set:

class MyClass():
    myattrib = None

    def __setattr__(self, prop, val):
        self.myattrib = val
        print("setting myattrib")

x = MyClass()
x.myattrib = "something"

The problem is, that this creates an infinite recursion.

The idea is to call a function when a member variable is set (and actually set that member variable), but at the same time run extra code (showcased with the print() statement in the example above).

The other obvious way to do this, is using set_attrib() functions, as is common in Java, but I'd like to do it the pythonic way, and set the attributes directly, but at the same time running extra code.

Upvotes: 2

Views: 5640

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1124378

Call the base version via super():

class MyClass(object):
    myattrib = None

    def __setattr__(self, prop, val):
        super().__setattr__('myattrib', val)
        print("setting myattrib")

You probably do not want to ignore the prop argument here, it is not necessarily 'myattrib' that is being set.

However, consider using a property instead of intercepting all attribute setting:

class MyClass(object):
    _myattrib = None

    @property:
    def myattrib(self):
        return self._myattrib

    @myattrib.setter
    def myattrib(self, val):
        self._myattrib = val
        print("setting myattrib")

I added object as a base-class; this is the default in Python 3, but a requirement for super() and property objects to work in Python 2.

Upvotes: 5

Blckknght
Blckknght

Reputation: 104792

If you want to do a specific thing when any attribute is set, use __setattr__ and call the inherited version using super for the actual assignment:

def __setattr__(self, prop, val):
    super().__setattr__(prop, val)
    print("setting {} to {!r}".format(prop, val)

If you only want to do something special for one attribute (not all attributes), you should probably use a property instead:

class MyClass():
    @property
    def some_attribute(self):
        return self._val

    @some_attribute.setter
    def some_attribute(self, value):
        self._val = value
        print("set some_attribute to {!r}".format(value))

Upvotes: 3

Related Questions