25

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?

Maciek D.
  • 2,754
  • 1
  • 20
  • 17
  • You can use descriptors: http://docs.python.org/2/howto/descriptor.html – Grisha S Apr 01 '13 at 19:45
  • Note: Using `@properety` decorators make properties more clear: http://docs.python.org/2/library/functions.html#property -- Is your question how to define property functions are defined in subclasses? – ninMonkey Apr 01 '13 at 19:45
  • `@property` decorator is equal to `x = property(...)` and using it does not change the behavior. I did not use it in the example as I wanted to be able to explicitly refer to `set_x` setter. – Maciek D. Apr 01 '13 at 19:53
  • 1
    @GrishaS -- How is that any different than using a property? Isn't a `property` just a very useful (and common) descriptor? – mgilson Apr 01 '13 at 19:57

4 Answers4

36

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
Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Actually I want to avoid setting up special handler for `x` as I may add some other attributes e.g. in base classes in the future and I don't want to have to update it here. However, `super(Test, self).__setattr__(name, value)` seems generic enough that I may do exactly the opposite: make `__setattr__` do what it has to do for some names and call `super` instead. – Maciek D. Apr 01 '13 at 20:43
  • 4
    What a revealation that `__setattr__` comes first. – John McGehee Oct 02 '15 at 17:54
9

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

shx2
  • 61,779
  • 13
  • 130
  • 153
2

AFAIK, There is no clean way to do this. The problem here arises from the asymmetry between __getattr__ and __setattr__. The former is called only if attribute by the normal means fails, but the latter is called unconditionally. Since there is no general way that __setattr__ will fail, I don't know if there is a way that this behavior could be changed.

Ultimately, I believe the only way to get the behavior that you want is to fold the set_ action of your properties into your __setattr__ function -- And if you're doing that, you might as well fold the behavior of the getters into __getattr__ to have it all maintainable from 1 place.

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • Actually I am writing C++ binding using Boost Python, which creates properties for me. However, I need `__setattr__` for some additional non-standard stuff. – Maciek D. Apr 01 '13 at 19:57
  • I don't know anything about Boost, but I wrestled with this one a while back and this was the conclusion that I came up with. Ultimately, `__setattr__` trumps your descriptors... (Although I hope I'm wrong about this and someone else gives a better answer). – mgilson Apr 01 '13 at 20:00
  • I have in mind some way of searching the class and bases dictionaries for property descriptors, but I don't know how to do it in a generic way. – Maciek D. Apr 01 '13 at 20:03
1

I encountered this problems several times, my preferred solution is the following:

  • For each property [property], define get_[property] and set_[property] functions as you do, but without using decorators
  • Modify __getattr__ and __setattr__ so that they check for the presence of these functions and use them if they are available.

Here's a minimal example:

class SmartSetter(object):

  def __init__(self,name):
    self.set_name(name)

  def get_name(self):
    #return the name property
    return self._name

  def set_name(self,value):
    #set the name property
    self._name = value

  def __getattr__(self,key):
    #first condition is to avoid recursive calling of this function by
    #__setattr__ when setting a previously undefined class attribute.
    if not key.startswith('get_') and hasattr(self,'get_'+key):
      return getattr(self,'get_'+key)()
    #implement your __getattr__ magic here...
    raise AttributeError("no such attribute: %s" % key)

  def __setattr__(self,key,value):
    try:
      return getattr(self,'set_'+key)(value)
    except AttributeError:
      #implement your __setattr__ magic here...
      return super(SmartSetter,self).__setattr__(key,value)

if __name__ == '__main__':
  smart_setter = SmartSetter("Bob the Builder")

  print smart_setter.name
  #Will print "Bob the Builder"

  smart_setter.name = "Spongebob Squarepants"

  print smart_setter.name
  #Will print "Spongebob Squarepants"

The advantage of this method is that it preserves the normal behavior for all attributes except those that have "getter" and "setter" methods and that it doesn't require any modifications of the __getattr__ and __setattr__ functions when you add or remove properties.

ThePhysicist
  • 1,822
  • 16
  • 21