python动态创建类方法

时间:2012-06-10 17:15:06

标签: python

对于一些sqlalchemy模型我有一个相当复杂的基类,我想创建一个rails风格的setter但是,因为我对python很新,我遇到了一个我似乎无法绕过的问题。我在 new 方法上创建了setter,因此我可以在新实例和查询上触发,但无论我定义和执行什么setter,它总是选择要执行的最后一个setter。一个例子更好:

class Test(object):

    columns = ['email', 'username']

    def __new__( cls, *args, **kwargs ):
        for column in cls.columns:
            setattr( cls, "set%s" % column.capitalize(), lambda cls, v: cls.setAttribute( cls, column, v ) )
        return super( Test, cls ).__new__( cls, *args, **kwargs )

    @staticmethod
    def setAttribute(cls, attribute, value):
        print "Setting attribute %s with value %s" % ( attribute, value )
        setattr( cls, attribute, value )

test = Test()
test.setEmail('test@test.com')

正如您所看到的,我正在设置电子邮件,但在执行时,代码会尝试设置最后一列的用户名。知道为什么会这样吗?

1 个答案:

答案 0 :(得分:1)

这是因为您的lambda函数引用了column但未将其作为参数传递:

lambda cls, v: cls.setAttribute( cls, column, v )

执行此函数时,它将在包含或全局范围内查找名称column,并始终找到值'username',因为这是column设置为持续的值

以下是使用默认参数值解决此问题的简单方法:

    def __new__( cls, *args, **kwargs ):
        for column in cls.columns:
            setattr( cls, "set%s" % column.capitalize(), lambda cls, v, column=column: cls.setAttribute( cls, column, v ) )
        return super( Test, cls ).__new__( cls, *args, **kwargs )

另一种选择是使用闭包(在某种程度上,可变默认参数是一种闭包):

    def __new__( cls, *args, **kwargs ):
        def make_setter(column):
            return lambda cls, v: cls.setAttribute( cls, column, v )
        for column in cls.columns:
            setattr( cls, "set%s" % column.capitalize(), make_setter(column))
        return super( Test, cls ).__new__( cls, *args, **kwargs )