tags:

views:

123

answers:

5

I'm trying to dynamically create a bunch of class properties, but each dynamic fget accessor needs a unique local variable.

Here is a simplified example:

class Test(object):
    def __metaclass__(name, bases, dict):
        for i in range(5):
            def fget(self, i=i):
                return i

            dict['f%d' % i] = property(fget)

        return type(name, bases, dict)

>>> t = Test()
>>> print t.f0, t.f1, t.f2, t.f4
0, 1, 2, 3, 4

In order to have each correct 'i' value available to each fget function, I have to pass it as a keyword argument when creating the function. Otherwise, all functions would see the same instance of i (the last one generated from the range operation).

This seems like a bad hack to me, is there a better way to do it?

+1  A: 

"Each dynamic fget accessor needs a unique local variable."

That tells you that each "property" is a separate instance of some class.

Consider using descriptors for this so that you have a complete class instead of some cobbed-up instance variable.

Or consider using some variant on the Strategy design pattern to delegate this "unique local variable" to this property-related Strategy object.

S.Lott
In fairness, I think that the statement could also be an indicator that that each property is also a closure. And I feel that that would work better for this simple example. *However*, you may be right depending on how complex the OP's needs are.
Jason Baker
A: 

You could use a closure over a local variable instead of passing i as a default argument:

class Test(object):
    def __metaclass__(name, bases, dict):
        for i in range(5):
            def asclosure():
               # function to create a new local scope for |ci|
               ci = i
               def fget(self):
                   return ci
               return fget

            dict['f%d' % i] = property(asclosure())

        return type(name, bases, dict)

This works, but also seems very hacky and not very readable.

sth
I think this is even more hacky than the original code. :-)
Jason Baker
+1  A: 

One alternative is to define a second function that captures the value of the variable as a new local. E.g.:

def make_prop_that_returns(i):
    return property(lambda self: i)

class Test(object):
    def __metaclass__(name, bases, dict):
        for i in range(5):
            dict['f%d' % i] = make_prop_that_returns(i)

        return type(name, bases, dict)

I'm not sure I'd say this is much cleaner than your approach, but it's an alternative at least.

Can you be more clear (less abstract) about what you're trying to accomplish? Defining dynamic fget's might not be the best way to do it.

Edward Loper
The code is intended to be used primarily from the interpreter, so I want the property to behave exactly like an int for simplicity (no casts necessary to read the value for example).In reality the property also has an fset, and you could think of it as manipulating a backing-store like a database or in my case embedded hardware registers (i being an address offset).
Patrick McKinnon
A: 

For this simplified example, I think that what you have works pretty well (aside from being a bit hacky). While the i=i part can be ugly and tricky, it is a relatively well known way to make closures. Add a comment if you're afraid someone won't get it.

However, if you're doing something that's more complex, I'd definitely agree with S. Lott above.

One other possible approach:

def make_property(i, dict):
    def fget(self):
        return i

    dict['f%d' % i] = property(fget)

class Test(object):
    def __metaclass__(name, bases, dict):
        for i in range(5):
            make_property(i, dict)

        return type(name, bases, dict)

Personally, I feel that this makes the whole thing a lot easier to understand as you're separating out each iteration of the loop into its own function.

Jason Baker
Adding a comment to indicate I'm creating a closure makes me feel better without adding much complexity to the implementation. ;-)
Patrick McKinnon
A: 

You just need another function where i is a argument:

class Test(object):
    def __metaclass__(name, bases, dict):

        def makeprop( i ):
            # a new namespace where `i` never changes
            def fget(self):
                return i
            return property(fget)

        for i in range(5):
            dict['f%d' % i] = makeprop(i)

        return type(name, bases, dict)
THC4k