【问题标题】:Why doesn't functools.partial return a real function (and how to create one that does)?为什么 functools.partial 不返回一个真正的函数(以及如何创建一个函数)?
【发布时间】:2012-11-20 22:47:15
【问题描述】:

所以我在 Python 中使用柯里化函数,我注意到其中一件事是 functools.partial 返回一个部分对象而不是实际函数。对此让我感到恼火的一件事是,如果我做了以下事情:

five = partial(len, 'hello')
five('something')

然后我们得到

TypeError: len() takes exactly 1 argument (2 given)

但我想要发生的是

TypeError: five() takes no arguments (1 given)

有没有一种干净的方法让它像这样工作?我写了一个解决方法,但它对我的口味来说太老套了(对于带有可变参数的函数还不起作用):

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

【问题讨论】:

  • lambda : len('hello')有什么问题吗?对于functools.partiallambda 之外所做的事情,我总是有点模糊......
  • @mgilson:嗯,它使错误消息变得更糟。你会得到TypeError: <lambda>() takes 0 positional arguments but 1 was given,而不是TypeError: len() takes exactly 1 argument (2 given)
  • @mgilson:如果你有两个函数,每个函数的参数数量不同,会发生什么。 lambda 需要事先确切知道它将需要多少个 args……当然,除非您将 args 包装在一个元组中并将它们解压缩到 lambda
  • @mgilson:看看我上面的编辑是否回答了这个问题
  • @abarnert -- 这难道不是 OP 所说的想要的吗? “但我想要发生的是:TypeError: five() takes no arguments (1 given)”?

标签: python function partial currying


【解决方案1】:

你绝对不想用 exec 这样做。

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

或者你也可以把partial 换成…

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

five = my_partial('five', len, 'hello')

此时,您一定想知道为什么这比定义一个新函数要好。

但是,无论如何,我认为这不是您真正想要的。您的最终目标是定义一个 @curry 装饰器,它创建一个装饰函数的柯里化版本,与装饰函数具有相同的名称(以及文档字符串、参数列表等)。替换中间名称partial 的整个想法是红鲱鱼;在您的 curry 函数中正确使用 functools.wraps ,无论您如何定义柯里化函数,它都会保留原始名称。

在某些情况下,functools.wraps 不起作用。事实上,这可能是其中一种情况——例如,您需要修改 arg 列表,因此 curry(len) 可以采用 0 或 1 个参数而不是 1 个参数,对吧?请参阅 update_wrapper 和(非常简单的)source code 了解 wrapsupdate_wrapper 了解基础知识如何工作,并从那里构建。

扩展前面的内容:要对函数进行 curry,您几乎必须返回接受 (*args)(*args, **kw) 的内容并显式解析 args,并可能显式引发 TypeError 和其他适当的异常。为什么?好吧,如果 foo 需要 3 个参数,curry(foo) 需要 0、1、2 或 3 个参数,如果给定 0-2 个参数,它会返回一个函数,该函数需要 0 到 n-1 个参数。

您可能想要**kw 的原因是它允许调用者按名称指定参数——尽管当您完成累积参数时检查它会变得更加复杂,并且可以说这与柯里化有关— 最好先将命名参数与partial 绑定,然后curry 结果并以咖喱样式传递所有剩余的参数……

如果foo 有默认值或关键字参数,它会变得更加复杂,但即使没有这些问题,你也已经需要处理这个问题了。

例如,假设您将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

这太简单了,但它展示了如何处理基础知识。

【讨论】:

  • 我使用 exec 的原因是因为我需要定义一个完全接受 X 参数的函数,但直到运行时我才知道 X 是什么。 (好吧,这不完全正确。我可以将参数的数量作为参数传递给我的咖喱装饰器(见编辑),但我想避免这种情况)
  • @epsilon:你可以通过检查发现,或者……真的,看看现有的 functools 东西(hg.python.org/cpython/file/2.7/Lib/functools.py 用于 2.7)中的代码,以及其中一些 PyPI 模块和 AS 配方了解什么是可能的,什么是可行的合理方法。
  • 我可以通过 func.func_code.co_argcount 很好地找到该值,但它正在使用有问题的参数数量创建函数。我会看看其中的一些食谱,谢谢。
  • @epsilon:您几乎必须定义一个只接受(*args, **kw) 的函数,因为即使foo 具有固定的参数计数,curry(foo) 也可以接受从0 到@987654356 的任何值@args 和 curry(foo)(x) 可以取 0 到 that-1 之间的任意值,以此类推。
  • 这是一个很好的观点,尽管如果给出的参数太多,仍然可以做一些事情。
【解决方案2】:

我的猜测是偏函数只是延迟了函数的执行,不要用它创建一个全新的函数。

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

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

这是一个非常简单的行,不会弄乱你的代码并且非常清晰,所以我不会费心写一个函数来替换它,特别是如果你在大量情况下不需要这种情况

【讨论】:

  • 好吧,我不是函数式编程方面的专家,但我认为 curry 操作背后的想法是保持原始函数的身份,所以隐藏它有点奇怪。 ..但我会看看我能想出什么:)
  • 再想一想,你所要求的不能用一个好的 sintax 来获得,因为你首先创建了 curryied 函数,然后将它分配给一个名称。你可以解决这个问题,但将是一个黑客......我的建议是坚持一个新函数的定义,对不起
猜你喜欢
  • 2016-10-12
  • 2012-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多