0

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
  1. Why are both 'foo' and 'bar' printed out when creating the instance ?
  2. How does the 'foo' property point to the same getter as 'bar' ?
Valentin B.
  • 602
  • 6
  • 18
  • 1
    Your second question has a more detailed answer here: https://stackoverflow.com/questions/10452770/python-lambdas-binding-to-local-values – Patrick Haugh Jul 02 '18 at 12:24

1 Answers1

2

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
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
  • Thank you for your answer and link to the other question ! I thought of defining `__getattr__`, but in my mind it would override the standard attribute accessor. It seems logical (and it's convenient to me at least) that it would only be called when the standard accessor fails. It also ends up being way cleaner code than my original attempt. TIL that lambdas are great but can be dangerously misleading... – Valentin B. Jul 02 '18 at 12:42