Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class properties and __setattr__

Tags:

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?

like image 747
Maciek D. Avatar asked Apr 01 '13 19:04

Maciek D.


People also ask

What is Setattr () used for in class?

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.

What is __ Setattr __?

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”).

What is Setattr () and Getattr () used for?

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.

What does Setattr mean?

The setattr() function sets the value of the specified attribute of the specified object.


2 Answers

The search order that Python uses for attributes goes like this:

  1. __getattribute__ and __setattr__
  2. Data descriptors, like property
  3. Instance variables from the object's __dict__ (when setting an attribute, the search ends here)
  4. Non-Data descriptors (like methods) and other class variables
  5. __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 
like image 57
Blckknght Avatar answered Oct 04 '22 11:10

Blckknght


This is not a bullet-proof solution, but, like you suggested, you can check if a property is being setattred 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

like image 22
shx2 Avatar answered Oct 04 '22 10:10

shx2