glouie glouie - 29 days ago 16
Python Question

Python: overloading the __getattr__ and properties, making __setattr__ work correctly

Consider the following python code:

class Foo(object):

def __init__(self, value):
self._value = value

@property
def value(self):
return "value: {v}".format(v=self._value)

@value.setter
def value(self, value):
self._value = value

class Bar(object):

def __init__(self):
self.foo = Foo('foo')

def __getattr__(self, attr, *args, **kwargs):
"""
Intercepts attribute calls, and if we don't have it, look at the
webelement to see if it has the attribute.
"""

# Check first to see if it looks like a method, if not then just return
# the attribute the way it is.
# Note: this has only been tested with variables, and methods.
if not hasattr(getattr(self.foo, attr), '__call__'):
return getattr(self.foo, attr)

def callable(*args, **kwargs):
'''
Returns the method from the webelement module if found
'''
return getattr(self.foo, attr)(*args, **kwargs)
return callable

>>> b = Bar()
>>> b.foo
<__main__.Foo object at 0x819410>
>>> b.foo.value
'value: foo'
>>> b.foo.value = '2'
>>> b.foo.value
'value: 2'
>>> b.value
'value: 2'
>>> b.value = '3'
>>> b.value
'3'


That last part, I want it to be 'value: 3' instead of '3' because now my property 'value' is now an attribute instead.

Is it possible, and if it is how would I would I do that.

Answer Source

Your __getattr__ returns the property value, not the property itself. When you access getattr(self.foo, attr) it does the equivalent of self.foo.value and returns that, and the property is called at that time.

You thus need to implement a __setattr__ method too, to mirror the __getattr__ and pass on the value setting to the contained foo object.

Under the hood, Python implements properties as descriptors; their __get__() method is called by the lower-level __getattribute__ method, which causes them to return their computed value. It is never the property object itself that is returned.

Here's an example __setattr__:

def __setattr__(self, attr, value):
    if hasattr(self, 'foo') and hasattr(self.foo, attr):
        setattr(self.foo, attr, value)
        return
    super(Bar, self).__setattr__(attr, value)

Note: because your __init__ sets self.foo, you need to test if foo exists on your class (hasattr(self, 'foo'). You also need to call the original __setattr__ implementation to make sure that things like self.foo = Foo() work still.