假设我有一个我作为二元运算符(binop)编写的函数,如何将它扩展为多运算符(multiop),它接受任意数量的参数?库中是否有这样的装饰器(例如在functools中)?
例如(我希望装饰器给出这种行为):
@binop_to_multiop
def mult(a,b):
return a*b
mult(2,3,4) # 2*3*4 = 24
mult(7) # 7
mult(2,3) # 6
显然,我不能在没有提及的情况下提出有关装饰者的问题,answer。
我已经尝试过编写自己的文章,但不能完全让它正常工作,也欢迎任何解释我出错的地方:
def binop_to_multiop(f):
@functools.wraps(f)
def wrapper(*args, **kwds):
if len(args) == 1: return args[0] # fails
return f(args[0],(f(*args[1:], **kwds)), **kwds) #recursion attempt fails
return wrapper
提供 TypeError: mult() takes exactly 2 arguments (N given)
(适用于各种N!=2
)。
答案 0 :(得分:4)
reduce()
:
from functools import wraps
def binop_to_multiop(binop):
@wraps(binop)
def multiop(x, *xs):
return reduce(binop, xs, x)
return multiop
# ...
@binop_to_multiop
def mult(a, b):
return a * b
print mult(2, 3, 4)
print mult(7)
print mult(2, 3)
结果:
$ python multiop.py
24
7
6
答案 1 :(得分:3)
您自己编写代码的尝试非常接近于工作。你只需要改变递归步骤来递归wrapper
而不是将一个参数传递给f
:
def binop_to_multiop(f):
@functools.wraps(f)
def wrapper(*args, **kwds):
if len(args) == 1: return args[0]
return f(args[0], wrapper(*args[1:], **kwds), **kwds)
return wrapper
我对基本情况没有任何问题,因此我不确定您的评论#fails
是什么。
您可能还需要考虑一下您开始解决的列表的哪一端(即您的运营商是否有left or right associativity)。对于像乘法和加法这样的运算符,自(a+b)+c = a+(b+c)
以来无关紧要,但对于其他运算符,您可能会得到奇怪的结果。例如,减法可能无法正常工作:
@binop_to_multiop
def sub(a, b):
return a - b
使用上面定义的装饰器,sub(a, b, c)
将提供与a-b-c
不同的结果(它将a-(b-c)
代替(a-b)-c
)。如果您希望它们以相同的方式运行,您可以将装饰器重新定义为左关联(就像大多数数学运算符在大多数计算机语言中所做的那样),如下所示:
def left_associative_binop_to_multiop(f):
@functools.wraps(f)
def wrapper(*args, **kwds):
if len(args) == 1: return args[0]
return f(wrapper(*args[:-1], **kwds), args[-1], **kwds)
return wrapper
更复杂的方法是将关联性作为装饰器的参数,但如果您不希望需要参数,则会变得棘手。
答案 2 :(得分:3)
def binop_to_multiop(f):
def wrapper(*args):
return reduce(f, args) if args else None
return wrapper
@binop_to_multiop
def mult(a, b):
return a*b
print mult(2,3,4)
print mult(7)
print mult(2,3)
print mult(4,5,6,7)
给出 24 7 6 840