Python 为什么';t functools.partial返回实函数(以及如何创建这样的函数)?

Python 为什么';t functools.partial返回实函数(以及如何创建这样的函数)?,python,function,partial,currying,Python,Function,Partial,Currying,所以我在Python中使用curry函数,我注意到的一点是functools.partial返回的是partial对象,而不是实际的函数。让我恼火的一件事是,如果我做了以下事情: five = partial(len, 'hello') five('something') 然后我们得到 TypeError: len() takes exactly 1 argument (2 given) 但我想发生的是 TypeError: five() takes no arguments (1 give

所以我在Python中使用curry函数,我注意到的一点是functools.partial返回的是partial对象,而不是实际的函数。让我恼火的一件事是,如果我做了以下事情:

five = partial(len, 'hello')
five('something')
然后我们得到

TypeError: len() takes exactly 1 argument (2 given)
但我想发生的是

TypeError: five() takes no arguments (1 given)
有没有一个干净的方法让它像这样工作?我写了一个变通方法,但对我来说太粗糙了(对于使用varargs的函数还不起作用):

编辑:我想如果我添加更多的上下文可能会有所帮助。我正在编写一个装饰程序,它将自动实现如下功能:

@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的建议仅适用于该特定情况。

我的猜测是部分函数只是延迟了函数的执行,而不是从中创建一个全新的函数

我的猜测是,直接定义一个新函数更容易:

def five(): return len('hello')

这是一行非常简单的代码,不会使代码变得杂乱无章,而且非常清晰,因此我不会费心编写函数来替换它,特别是如果您在很多情况下不需要这种情况,您肯定不想使用
exec

您可以在纯Python中找到
partial
的菜谱,例如-许多菜谱被错误地标记为
curry
菜谱,所以也要寻找它们。无论如何,这些将向您展示在不使用
exec
的情况下执行此操作的正确方法,您只需选择一个并对其进行修改即可

或者你可以只包装
部分

然而,无论您做什么,包装器都无法知道它正在定义一个名为“五”的函数;这只是存储函数的变量的名称。因此,如果需要自定义名称,则必须将其传递给函数:

five = my_partial('five', len, 'hello')
在这一点上,您必须想知道为什么这比定义一个新函数更好

然而,我并不认为这是你真正想要的。您的最终目标是定义一个
@curry
装饰器,该装饰器创建装饰函数的一个curry版本,其名称(以及docstring、arg list等)与装饰函数相同。替换中间
partial
名称的整个想法都是在转移注意力;在
curry
函数中正确使用,无论您如何定义curry函数,它都会保留原始函数的名称

在某些情况下,
functools.wrapps
不起作用。事实上,这可能是您需要修改arg列表的次数之一,例如,
curry(len)
可以接受0或1个参数,而不需要1个参数,对吗?请参阅和(非常简单的)for
wrapps
update_wrapper
,了解基础知识的工作原理,并从中构建

扩展上一个:要实现函数,您几乎必须返回一些内容,这些内容接受
(*args)
(*args,**kw)
并显式解析args,还可能显式引发
类型错误
和其他适当的异常。为什么?如果
foo
接受3个参数,
curry(foo)
接受0、1、2或3个参数,如果给定0-2个参数,则返回一个接受0到n-1个参数的函数

您可能需要
**kw
的原因是,它允许调用方按名称指定参数,尽管在累积参数后,检查参数会变得复杂得多,可以说这是一件很奇怪的事情,使用curry时,最好先用
partial
绑定命名的参数,然后
curry
结果并以curry样式传入所有剩余参数

如果
foo
有默认值或关键字args,它会变得更加复杂,但即使没有这些问题,您也需要处理这个问题

例如,假设您将
curry
实现为一个类,该类将函数和所有已经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

这非常简单,但它展示了如何处理基本问题。

lambda:len(“hello”)有什么问题吗??我总是对
functools.partial
lambda
之上和之外所做的事情有点模糊。…@mgilson:嗯,这会让错误消息变得更糟。而不是
TypeError:len()只接受1个参数(给定2个)
您将得到
TypeError:()接受0个位置参数,但得到1个位置参数
@mgilson:如果您有两个函数,每个函数接受不同数量的参数,会发生什么。一个
lambda
需要事先准确地知道需要多少个参数……当然,除非您将参数包装在一个元组中,然后在
lambda
@mgilson中解压它们:看看我上面的编辑是否回答了这个问题that@abarnert这不正是OP所说的吗?“但我想做的是:
TypeError:five()不接受任何参数(给定1个)
”?我使用exec的原因是我需要定义一个只接受X个参数的函数,但直到运行时我才知道X是什么。(好吧,这不完全正确。我可以将参数的数量作为参数传递给我的curry decorator(请参见编辑),但我想避免这种情况)@epsilon:您可以通过检查发现,或者……实际上,查看现有
functools
内容(适用于2.7)中的代码,还有一些PyPI模块,作为一些关于什么是可能的想法的配方,以及什么是做可能的事情的合理方法。我可以通过func.func_code.co_argcount,bu找到值
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