2

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?

5 Answers 5

1

"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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
1

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.

1 Comment

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).
0

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.

1 Comment

Adding a comment to indicate I'm creating a closure makes me feel better without adding much complexity to the implementation. ;-)
0

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.

Comments

0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.