1

I'd like to have a class decorator that adds properties to a class based on a class attribute _arg_names, as in this minimal example:

def add_properties(cls):
    for name in cls._arg_names:
        setattr(cls, name, property(lambda self: getattr(self, '_' + name)))
    return cls

@add_properties  # .a->._a, .b->._b
class A():
    _arg_names = ('a', 'b')
    def __init__(self):
        self._a = 1
        self._b = 2

However, when I run this, I find that all properties point to the last argument name:

>>> x = A()
>>> x.a
2
>>> x.b
2

Any ideas why the closure inside property isn't working as expected? Are there any other solutions for this problem? I need the properties to be read-only, and they should show up in the class documentation.

martineau
  • 119,623
  • 25
  • 170
  • 301
Michael Goerz
  • 4,300
  • 3
  • 23
  • 31
  • just to be clear: I don't want to use `__getattr__` to simply reroute `a` to `_a`, because that wouldn't be read-only (is there a way to make it read-only?), and it would also be undocumented. – Michael Goerz Jun 06 '18 at 22:28
  • See [Creating functions in a loop](//stackoverflow.com/q/3431676) for an explanation of the problem in your code. – Aran-Fey Jun 06 '18 at 22:28
  • What do you mean "it would be undocumented"? – Aran-Fey Jun 06 '18 at 22:32
  • 3
    As a side note, wouldn't it be a simpler API to just `@add_properties('a', 'b')` instead of requiring `_arg_names`? (Unless you need that class attribute for something else?) – abarnert Jun 06 '18 at 22:32
  • @Aran-Fey Thanks! I don't think I've run into this late-binding problem ever before. Using `partial` solves the problem! What I meant by "undocumented" is that if I handled the attributes `a` and `b` in `__getattr__`, then `help(A)` wouldn't mention anything about them. – Michael Goerz Jun 07 '18 at 02:39
  • @abarnert In principle, I agree. This was just my minimal example, though. In the actual code, `arg_names` is used for more than just this. – Michael Goerz Jun 07 '18 at 02:41
  • People who understand closures (as you seem to, and many others) still usually find this confusing. This is exactly what capturing a variable in a closure means, by definition. And it seems like it’s obvious that the loop variable is one variable that gets reassigned over and over, not separate variables with the same name (as it would be in, say, Scheme). And yet, people who are knowledgeable (and smarter than me) have been running into this since Python 1.x (and continue in newer languages, like Go). If someone could work out why, it would be a big step in language design… – abarnert Jun 07 '18 at 03:40
  • When you put it like that, it totally makes sense. I suppose the confusion might come from having a bit of a functional programming mindset, with block-level scope, and where an assignment creates a new variable (memory location). Of course, that's not how Python works, but in a purely functional language where everything is immutable, something like the original example should work. – Michael Goerz Jun 07 '18 at 05:30
  • I'd suspect my reason for having fallen into that mindset was that this problem showed up in the development of a computer algebra library, which uses a lot of functional patterns. The point of the automatic properties is in fact to enforce immutability of expressions. – Michael Goerz Jun 07 '18 at 05:33
  • @MichaelGoerz Actually, I think it's not even about immutability. Lisp mutates all over the place, but most Lisp loop constructs create a new namespace with a new let binding for the loop variable each time through the loop. And other functional languages with mutability implement loops as sugar over tail recursion. So it's just a matter of not thinking of loops as an imperative construct—or, rather, subconsciously expecting loop+closure to work in functional-loop terms, because you rarely use closures in imperative languages, even though once you think about it you can see what's happening? – abarnert Jun 07 '18 at 23:55
  • Anyway, as I said, it's not just you, it's almost everyone, and people coming from languages that encourage lots of closures maybe even more than novices. But then once you realize why it works the way it does, it's hard to imagine why you'd ever have thought otherwise… – abarnert Jun 07 '18 at 23:56

0 Answers0