用于动态方法创建者的python setattr与装饰器

时间:2015-10-14 20:05:14

标签: python decorator factory python-decorators setattr

我有一个定义了多个方法的类。

class Klass(object):
    pass

list1 = [('method1', 'value1'), ('method2', 'value2').....('method10', 'value10')]

for each in list1:
    method_name, method_value = each
    setattr(Klass, method_name, lambda self: method_value)

想象一下,我需要为这个'Klass'填充10个方法。我想生成这些方法而不是明确地写出它们。所以我想做一个为每个方法做setattr的工厂。问题是我做了以下,最后一个方法有最后一个值。每个都没有得到它的相关值,但值10。另外下面的解决方案没有实现装饰器,我不知道如何分配装饰器

k = Klass()
print k.method1(), method2()...., method10()

所以,当我关注....

$xml_catalog=[xml]@'
<catalogue date_maj="2015-10-10T19:04:51">
                <products>
                               <product id="1">Pdt1</product>
                               <product id="2">Pdt2</product>
                </products>
                <categories>
                               <category id="1">cat1</category>
                               <category id="2">cat2</category>
                </categories>  
</catalogue>
'@

$xml_catalog_target = [xml]@'
<?xml version="1.0" encoding="iso-8859-1"?>
<catalogue date_maj="2015-10-12T19:04:51">
</catalogue>
'@

Foreach ($Node in $xml_catalog.DocumentElement.ChildNodes) {
    $xml_catalog_target.DocumentElement.AppendChild($xml_catalog.ImportNode($Node, $true))
}

每个方法都会产生值10。不明白为什么 ? 另外,任何人都可以帮助如何使用一个属性实现装饰器? PS:如果你有不使用'setattr'的建议,那也会受到欢迎。

2 个答案:

答案 0 :(得分:4)

使用lambda创建每个方法时,将其绑定到当前本地范围。该作用域具有名为method_value的变量的单个实例,并且在每个循环之后将其设置为新值。因为每个lambda引用这个局部变量,所以它们都看到相同的值(例如,设置的最后一个值)。

如果您使用不同的方法创建lambda,它将具有不同的范围,因此您可以获得所需的行为:

class Klass(object):
        pass

list1 = [('method1', 'value1'), ('method2', 'value2'), ('method10', 'value10')]

def make_attr(value):
    return lambda self: value

for method_name, method_value in list1:
    setattr(Klass, method_name, make_attr(method_value))

c = Klass()
print c.method1(), c.method2(), c.method10()  # "value1, value2, value10"

这里,make_attr创建了一个新范围,因此变量value有唯一的实例,每个lambda创建一个。

您还可以使用一个很好的技巧来内联创建一个lambda范围的变量,如下所示:

lambda self, val=method_value: val 

此处,val在lambda声明时被赋予method_value 的值,为lambda的每个实例赋予其自己的值。这使您可以使用更紧凑的:

for method_name, method_value in list1:
    setattr(Klass, method_name, lambda self, val=method_value:val))

最后,Marti在a different answer中指出如何修改我的make_attr函数以应用所需的装饰器:

def make_attr(value):
    return mat.sell(mat.CanSet)(lambda self: value))

答案 1 :(得分:4)

Myk Willis的回答解释了method_value变量范围的问题。我会尝试扩展它来回答你的其他疑问。

关于装饰器,以及如何以编程方式生成方法时应用它:一旦你理解了装饰器在Python中的工作方式,就会变得更容易。问题是,它们只是功能。装饰器是一个接收函数并返回函数的函数。 @语法只是语法糖,它以更方便的方式将指示的装饰器应用于装饰函数。

所以这个:

@my_decorator
def my_function():
    ...

将完全相同:

def my_function():
    ...

my_function = my_decorator(my_function)

这同样适用于您的原始示例。这样:

@mat.sell(mat.CanSet):
def method1(self):
    return None

等同于:

def method1(self):
    return None

method1 = mat.sell(mat.CanSet)(method1)

这个有点复杂,因为mat.sell不是装饰器,而是一个返回装饰器的函数;因此双调用:首先我们调用mat.sell,传递mat.CanSet参数,然后该调用返回一个我们用来包装method1的装饰器。

了解这一点,修改Myk的应用装饰器的答案很简单:

class Klass(object):
    pass

list1 = [('method1', 'value1'), ('method2', 'value2'), ('method10', 'value10')]

def make_attr(value):
    return mat.sell(mat.CanSet)(lambda self: value)

for method_name, method_value in list1:
    setattr(Klass, method_name, make_attr(method_value))

注意make_attr()的变化;我们不是直接返回伪造的方法,而是先将它传递给装饰器,然后返回它的返回值。

至于此:

  

另外,任何人都可以帮助如何使用一个属性实现装饰器? PS:如果您有不使用&#39; setattr&#39;的建议,那也会受到欢迎。

我不确定我明白你在问什么。您是否想要将方法转换为属性?你能告诉我们mat.sell的作用吗?这可能有助于我们更清楚地了解您的目标。