我正在尝试动态创建一堆类属性,但每个动态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的相同实例(从范围操作生成的最后一个实例)。
这对我来说似乎是一个糟糕的黑客,有没有更好的方法呢?
答案 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)
这很有效,但也看起来非常hacky且不太可读。
答案 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)