In Python class, when I use __setattr__
it takes precedence over properties defined in this class (or any base classes). Consider the following code:
class Test(object): def get_x(self): x = self._x print "getting x: %s" % x return x def set_x(self, val): print "setting x: %s" % val self._x = val x = property(get_x, set_x) def __getattr__(self, a): print "getting attr %s" % a return -1 def __setattr__(self, a, v): print "setting attr %s" % a
When I create the class and try to set x
, __setattr__
is called instead of set_x
:
>>> test = Test() >>> test.x = 2 setting attr x >>> print test.x getting attr x_ getting x: -1 -1
What I want to achieve is that the actual code in __setattr__
were called only if there is no relevant property i.e. test.x = 2
should call set_x
. I know that I can achieve this easily by manually checking if a
is "x" is __setattr__
, however this would make a poor design. Is there a more clever way to ensure the proper behavior in __setattr__
for every property defined in the class and all the base classes?
What is setattr() used for? The Python setattr() function sets a new specified value argument to the specified attribute name of a class/function's defined object. This method provides an alternate means to assign values to class variables, in addition to constructors and object functions.
Python's magic method __setattr__() implements the built-in setattr() function that takes an object and an attribute name as arguments and removes the attribute from the object. We call this a “Dunder Method” for “Double Underscore Method” (also called “magic method”).
Python setattr() and getattr() goes hand-in-hand. As we have already seen what getattr() does; The setattr() function is used to assign a new value to an object/instance attribute.
The setattr() function sets the value of the specified attribute of the specified object.
The search order that Python uses for attributes goes like this:
__getattribute__
and __setattr__
property
__dict__
(when setting an attribute, the search ends here)__getattr__
Since __setattr__
is first in line, if you have one you need to make it smart unless want it to handle all attribute setting for your class. It can be smart in either of two ways: Make it handle a specific set attributes only, or make it handle all but some set of attributes. For the ones you don't want it to handle, call super().__setattr__
.
For your example class, handling "all attributes except 'x'" is probably easiest:
def __setattr__(self, name, value): if name == "x": super(Test, self).__setattr__(name, value) else: print "setting attr %s" % name
This is not a bullet-proof solution, but, like you suggested, you can check if a property is being setattr
ed by trying to access the property
object, from class's attributes (using getattr
on the class object).
class Test(object): def get_x(self): x = self._x print "getting x: %s" % x return x def set_x(self, val): print "setting x: %s" % val self._x = val x = property(get_x, set_x) @property # no fset def y(self): print "getting y: 99" return 99 def __getattr__(self, a): print "getting attr %s" % a return -1 def __setattr__(self, a, v): propobj = getattr(self.__class__, a, None) if isinstance(propobj, property): print "setting attr %s using property's fset" % a if propobj.fset is None: raise AttributeError("can't set attribute") propobj.fset(self, v) else: print "setting attr %s" % a super(Test, self).__setattr__(a, v) test = Test() test.x = 2 print test.x #test.y = 88 # raises AttributeError: can't set attribute print test.y test.z = 3 print test.z
EDIT: replaced self.__dict__[a] = v
with super(Test, self).__setattr__(a, v)
, as seen on @Blckknght's answer
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With