具有局部变量的动态函数

时间:2009-12-09 17:26:49

标签: python

我正在尝试动态创建一堆类属性,但每个动态fget访问器都需要一个唯一的局部变量。

这是一个简化的例子:

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

为了使每个fget函数都有正确的'i'值,我必须在创建函数时将其作为关键字参数传递。否则,所有函数都会看到i的相同实例(从范围操作生成的最后一个实例)。

这对我来说似乎是一个糟糕的黑客,有没有更好的方法呢?

5 个答案:

答案 0 :(得分:1)

“每个动态fget访问器都需要一个唯一的局部变量。”

这告诉你每个“属性”都是某个类的单独实例。

考虑使用描述符,以便你有一个完整的类而不是一些cobbed-up实例变量。

或者考虑在策略设计模式上使用一些变体将此“唯一局部变量”委托给此属性相关的Strategy对象。

答案 1 :(得分:1)

另一种方法是定义第二个函数,该函数将变量的值捕获为新的本地。 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)

我不确定我会说这比你的方法更清洁,但它至少是另一种选择。

你能否更清楚(不那么抽象)你想要完成什么?定义动态fget可能不是最好的方法。

答案 2 :(得分:0)

您可以对局部变量使用闭包,而不是将i作为默认参数传递:

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)

这很有效,但也看起来非常h​​acky且不太可读。

答案 3 :(得分:0)

对于这个简化的例子,我认为你的工作得很好(除了有点hacky)。虽然i=i部分可能是丑陋和棘手的,但它是一种相对众所周知的闭包方式。如果您害怕有人不理解,请添加评论。

然而,如果你做的事情比较复杂,我肯定会同意S. Lott的说法。

另一种可能的方法:

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)

就个人而言,我觉得这会使整个事情变得更容易理解,因为你将循环的每个迭代分离成它自己的函数。

答案 4 :(得分:0)

你只需要另一个i是一个参数的函数:

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)