我想在python中向某些人展示装饰器的用处,并在一个简单的例子中失败:考虑两个函数(为了简单而没有参数)f
和g
。
可以将它们的和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
这看起来像是范围泄漏,但我不明白为什么会发生这种情况。
答案 0 :(得分:2)
对于第一个问题,我将binary_op_names
从list
更改为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