Creating properties dynamically that point to an attribute's properties
Question:
I want to make properties from an attribute of my class accessible directly through the instance of the class (without inheriting from it). So basically if I have:
class A:
@property
def foo(self):
print("foo")
@property
def bar(self):
print("bar")
class B:
def __init__(self):
self._a = A()
Instead of doing b._a.bar
I want to be able to do b.bar
. Based on this answer here, I tried the following in class B:
class B:
def __init__(self):
self._a = A()
attrs = [attr for attr in dir(self._a)
if not callable(self._a.__getattribute__(attr))
and not attr.startswith("__")]
for attr in attrs:
setattr(self.__class__, attr,
property(lambda s: s._a.__getattribute__(attr)))
But when instantiating and testing it out, I get one of those weird python moments:
>>> b = B()
foo
bar
>>> b.foo
bar
>>> b.bar
bar
- Why are both ‘foo’ and ‘bar’ printed out when creating the instance ?
- How does the ‘foo’ property point to the same getter as ‘bar’ ?
Answers:
bar
and foo
are printed when you create the instance because doing _a.__getattribute__("foo")
and _a.foo
will both call the property object to get a value.
Both of the attributes you set up in B
use lambdas to get the correct property
from A
. This is a common mistake when calling lambdas. Because the attr
value is inherited from the outside scope, it isn’t frozen when the lambda is evaluated. Instead, it is simply the same attr
reference as the enclosing scope’s attr
, and changes accordingly. So all of your lambdas will have the same attr
value.
You can define a B.__getattr__
method instead. This method is called when ordinary attribute lookup fails.
class B:
def __init__(self):
self._a = A()
def __getattr__(self, name):
return getattr(self._a, name)
b = B()
b.bar # bar
b.foo # foo
I want to make properties from an attribute of my class accessible directly through the instance of the class (without inheriting from it). So basically if I have:
class A:
@property
def foo(self):
print("foo")
@property
def bar(self):
print("bar")
class B:
def __init__(self):
self._a = A()
Instead of doing b._a.bar
I want to be able to do b.bar
. Based on this answer here, I tried the following in class B:
class B:
def __init__(self):
self._a = A()
attrs = [attr for attr in dir(self._a)
if not callable(self._a.__getattribute__(attr))
and not attr.startswith("__")]
for attr in attrs:
setattr(self.__class__, attr,
property(lambda s: s._a.__getattribute__(attr)))
But when instantiating and testing it out, I get one of those weird python moments:
>>> b = B()
foo
bar
>>> b.foo
bar
>>> b.bar
bar
- Why are both ‘foo’ and ‘bar’ printed out when creating the instance ?
- How does the ‘foo’ property point to the same getter as ‘bar’ ?
bar
and foo
are printed when you create the instance because doing _a.__getattribute__("foo")
and _a.foo
will both call the property object to get a value.
Both of the attributes you set up in B
use lambdas to get the correct property
from A
. This is a common mistake when calling lambdas. Because the attr
value is inherited from the outside scope, it isn’t frozen when the lambda is evaluated. Instead, it is simply the same attr
reference as the enclosing scope’s attr
, and changes accordingly. So all of your lambdas will have the same attr
value.
You can define a B.__getattr__
method instead. This method is called when ordinary attribute lookup fails.
class B:
def __init__(self):
self._a = A()
def __getattr__(self, name):
return getattr(self._a, name)
b = B()
b.bar # bar
b.foo # foo