所以我正在玩Python中的currying函数,我注意到的一件事就是functools.partial返回一个部分对象而不是一个实际的函数。令我恼火的事情之一是,如果我按照以下方式做了一些事情:
five = partial(len, 'hello')
five('something')
然后我们得到
TypeError: len() takes exactly 1 argument (2 given)
但我想要发生的是
TypeError: five() takes no arguments (1 given)
是否有一种干净的方式让它像这样工作?我写了一个解决方法,但它对我的品味太过苛刻了(对于使用varargs的函数还没有用):
def mypartial(f, *args):
argcount = f.func_code.co_argcount - len(args)
params = ''.join('a' + str(i) + ',' for i in xrange(argcount))
code = '''
def func(f, args):
def %s(%s):
return f(*(args+(%s)))
return %s
''' % (f.func_name, params, params, f.func_name)
exec code in locals()
return func(f, args)
编辑:我认为如果添加更多上下文可能会有所帮助。我正在写一个装饰器,它会像这样自动地调用一个函数:
@curry
def add(a, b, c):
return a + b + c
f = add(1, 2) # f is a function
assert f(5) == 8
我想隐瞒f是从部分创建的事实(也许是一个坏主意:P)。上面给出的TypeError消息给出的消息是可以揭示某些东西是否是部分的一个示例。我想改变它。
这需要推广,因此EnricoGiampieri和mgilson的建议仅适用于该特定情况。
答案 0 :(得分:4)
您绝对不希望使用exec
执行此操作。
你可以在纯Python中找到partial
的食谱,例如this one - 其中许多被错误标记为curry
食谱,所以也要查找它。无论如何,这些都会在没有exec
的情况下向您显示正确的方法,您可以选择一个并对其进行修改以达到您想要的效果。
或者你可以包裹partial
...
然而,无论你做什么,包装器都无法知道它正在定义一个名为“five”的函数;这只是您存储函数的变量的名称。因此,如果您需要自定义名称,则必须将其传递给函数:
five = my_partial('five', len, 'hello')
在那一点上,你不得不想知道为什么这比定义一个新函数更好。
但是,我认为这不是你真正想要的。你的最终目标是定义一个@curry
装饰器,它创建一个装饰函数的curried版本,与装饰函数具有相同的名称(和docstring,arg list等)。替换中间人partial
的名称的整个想法是一个红色的鲱鱼;在curry
函数中正确使用functools.wraps
,无论您如何定义curried函数,它都会保留原始名称。
在某些情况下,functools.wraps
不起作用。事实上,这可能是其中一个时间 - 您需要修改arg列表,例如,curry(len)
可以采用0或1参数而不需要1个参数,对吧?请参阅update_wrapper
以及wraps
和update_wrapper
的{非常简单的} source code,了解基础知识的工作原理,并从那里开始构建。
扩展前一个:为了理解一个函数,你几乎必须返回一些需要(*args)
或(*args, **kw)
的东西并明确地解析args,并可能引发TypeError
和其他适当的明确的例外。为什么?好吧,如果foo
需要3个参数,curry(foo)
需要0,1,2或3个参数,如果给定0-2参数,则返回一个从0到n-1参数的函数。
你可能想要**kw
的原因是它允许调用者按名称指定params - 虽然然后检查你在累积参数时会变得更复杂,并且可以说这是一个奇怪的事情与currying一起 - 最好先用partial
绑定命名参数,然后curry
结果并以咖喱风格传递所有剩余参数...
如果foo
具有默认值或关键字args,则会变得更复杂,但即使没有这些问题,您也需要处理此问题。
例如,假设您将curry
实现为一个包含该函数的类,并将所有已经咖喱的参数实现为实例成员。然后你会有这样的事情:
def __call__(self, *args):
if len(args) + len(self.curried_args) > self.fn.func_code.co_argcount:
raise TypeError('%s() takes exactly %d arguments (%d given)' %
(self.fn.func_name, self.fn.func_code.co_argcount,
len(args) + len(self.curried_args)))
self.curried_args += args
if len(self.curried_args) == self.fn.func_code.co_argcount:
return self.fn(*self.curried_args)
else:
return self
这非常简单,但它展示了如何处理基础知识。
答案 1 :(得分:0)
我的猜测是部分函数只是延迟函数的执行,不要用它创建一个全新的函数。
我的猜测是,更容易直接定义新功能:
def five(): return len('hello')
这是一个非常简单的行,不会使你的代码混乱并且非常清楚,所以我不打算写一个函数来替换它,特别是如果你在很多情况下不需要这种情况< / p>