为什么这个“可操作函数”的python实现失败了?

时间:2011-11-29 20:21:28

标签: python decorator

我想在python中向某些人展示装饰器的用处,并在一个简单的例子中失败:考虑两个函数(为了简单而没有参数)fg。 可以将它们的和f + g定义为返回f()+ g()的函数。当然,通常不定义函数的加法,减法等。但是编写一个将每个函数转换为可添加函数的装饰器很容易。

现在我想有一个装饰器将任何函数转换为“可操作”函数,也就是说,对于标准模块operator中的任何运算符,该函数的行为都是描述的。我的实现如下:

import operator

class function(object):
    def __init__(self, f):
        self.f = f
    def __call__(self):
        return self.f()

def op_to_function_op(op):
    def function_op(self, operand):
        def f():
            return op(self(), operand())
        return function(f)
    return function_op
binary_op_names = ['__add__', '__and__', '__div__', '__eq__', '__floordiv__', '__ge__', '__gt__', '__le__', '__lt__', '__mod__', '__mul__', '__ne__', '__or__', '__pow__', '__sub__', '__truediv__', '__xor__']
for name in binary_op_names:
    type.__setattr__(function, name, op_to_function_op(getattr(operator, name)))

让我们进行一些测试,看它是否有效:

@function
def a():
    return 4

def b():
    return 7

c = a + b
print c()
print c() == operator.__add__(4, 7)

输出:

11
True

这是我经过一些实验后得到的最终版本。 现在让我们做两个小的,不相关的修改,看看我以前尝试过的东西:

首先:在binary_op_names的定义中,将方括号更改为圆括号。突然,一个(对我来说)完全不相关的错误信息出现了:

Traceback (most recent call last):
  File "example.py", line 30, in <module>
    c = a + b
TypeError: unsupported operand type(s) for +: 'function' and 'function'

这是从哪里来的?

第二个:将op_to_function_op写为lambda表达式:

op = getattr(operator, name)
type.__setattr__(function, name, lambda self, other: function(lambda: op(self(), other())))

执行稍微复杂的测试用例:

@function
def a():
    return 4

def b():
    return 7

c = a + b
print c()
print c() == operator.__add__(4, 7)
print c() == operator.__xor__(4, 7)

输出:

3
False
True

这看起来像是范围泄漏,但我不明白为什么会发生这种情况。

1 个答案:

答案 0 :(得分:2)

对于第一个问题,我将binary_op_nameslist更改为tuple后没有发现任何问题,不确定为什么会看到这一点。

至于第二个问题,所有操作都会执行XOR,因为__xor__op设置的最后一项。因为op在创建时没有传递给lambda,所以每次调用lambda时,它都会在全局范围内查找op并始终看到__xor__

你可以通过创建一个充当闭包的lambda来阻止这种情况,类似于你当前的op_tofunction_op,它可能最终看起来像这样:

op_to_function_op = lambda f: lambda self, other: function(lambda: f(self(), other()))
for name in binary_op_names:
    op = getattr(operator, name)
    type.__setattr__(function, name, op_to_function_op(op))

>>> (function(lambda: 4) + (lambda: 7))()
11
>>> (function(lambda: 4) - (lambda: 7))()
-3
>>> (function(lambda: 4) ^ (lambda: 7))()
3