0

I want to add properties dynamically to my class as follows, however I end up creating aliases. How can I prevent this?

class A:

    def __init__(self, a, b):
        self._a = a
        self._b = b

for attr in ('a', 'b'):
    f = lambda self: getattr(self, '_'+attr)
    setattr(A, attr, property(f, None))

a = A(0,1)

print(a.a)
print(a.b)

However this yields:

1
1

Edit:

The comment on closure scoping is relevant, however that leaves the question whether one can generate properties dynamically that reference some attribute of self open.

Specifically with respect to the example above: how, if at all, can I set the property such that a.a returns 0 instead of 1? If I simply try to pass the attribute argument to the lambda function, this attribute will need to be passed and thus this won't work.

  • 2
    Does this answer your question? [Python lambda closure scoping](https://stackoverflow.com/questions/13355233/python-lambda-closure-scoping) – kaya3 Dec 19 '19 at 11:56
  • So, does that imply that one cannot achieve dynamically setting properties of classes that reference some attribute of self? – Michiel Karrenbelt Dec 19 '19 at 14:03
  • It means that `attr` isn't evaluated at the time the lambda is created, rather the name `attr` is bound to the variable in the outer scope which ends up having the value `'b'`, so both lambdas get the `_b` attribute. – kaya3 Dec 19 '19 at 14:24
  • I think I understand that. The question that remains then is: can one can generate properties that reference some attribute of self dynamically? Or specifically with respect to the example above: how can I set the property such that a.a returns 0 instead of 1 – Michiel Karrenbelt Dec 19 '19 at 16:49
  • For the specific example, it seems like it would make more sense to implement `__getattr__` or `__getattribute__` to reroute the attribute accesses programmatically. – Karl Knechtel Aug 18 '22 at 02:16

1 Answers1

1

In order to get your desired result, you'd need to wrap your lambda in another function, like so:

def make_fget(attr):
    return lambda self: getattr(self, '_' + attr)

This way, when you call make_fget, the local name attr is bound to the argument passed.

You can call this in a loop as in your original code:

for attr in ('a', 'b'):
    setattr(A, attr, property(make_fget(attr), None))

The difference here is that in your original version, the loop essentially reassigns attr every iteration, and the lambda is only looking at attr in the outer scope (the loop) at the time it is called, and it will end up with whatever was assigned in the last iteration.

By wrapping in another function, in every loop iteration you essentially create a fresh outer scope (the function call), with the name attr bound to the passed argument, for the returned lambda.

bgse
  • 8,237
  • 2
  • 37
  • 39