为什么functools.partial不会返回一个真正的函数(以及如何创建一个函数)? [英] Why doesn't functools.partial return a real function (and how to create one that does)?

查看:168
本文介绍了为什么functools.partial不会返回一个真正的函数(以及如何创建一个函数)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以我在用Python的currying函数玩弄,我注意到的一件事情是functools.partial返回的是部分对象而不是实际函数。其中一件令我烦恼的事情是,如果我按照以下方式做了一些事情:

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

然后我们得到

  TypeError:len()只需要1个参数(给出2个)

但我想要发生的是

  TypeError:five参数(1给出)

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

  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 ))
返回%s
'''%(f.func_name,params,params,f.func_name)

在本地执行代码()
返回func(f,args)

编辑:我认为这可能有帮助如果我添加更多的上下文。我正在写一个装饰器,它会自动将一个函数如下所示:

  @curry 
def add(a, b,c):
return a + b + c

f = add(1,2)#f是一个函数
assert f(5)== 8

我想隐藏f是从部分创建(也许是个坏主意:P)的事实。上面的TypeError消息给出的消息是某个部分是否可以显示的一个例子。我希望改变这种情况。



这需要具有可推广性,因此EnricoGiampieri和mgilson的建议仅适用于特定情况。

解决方案

您绝对不希望使用 exec 执行此操作。



您可以在纯Python中找到 partial 的配方,例如这一个 - 他们中的许多人被错误标记为咖喱食谱,所以寻找那个好。无论如何,这些将告诉你如何在没有 exec 的情况下完成它的正确方法,你可以选择一个并修改它以完成你想要的操作。



或者你可以换行 partial ...



然而,无论你做什么,封装者无法知道它定义了一个名为five的函数;这只是你存储函数的变量的名字。所以如果你想要一个自定义名字,你必须将它传递给函数:

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

在那一点上,你必须想知道为什么这比定义一个新的函数更好。



然而,我不认为这是你真正想要的。您的最终目标是定义一个 @curry 装饰器,该装饰器创建装饰函数的一个curried版本,其名称与docstring,arg list等名称相同功能。替换中间名 partial 的整个想法是一个红鲱鱼;使用 functools.wraps 正确地在你的 curry 函数中,你如何定义curried函数并不重要,它会保留原文的名字。



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



在前面展开:为了模拟函数,几乎必须返回一些需要(* args)(* args,** kw)并显式解析args,并可能显式地引发 TypeError 和其他适当的异常。为什么?那么,如果 foo 需要3个参数, curry(foo)需要0,1,2或3个参数,并且如果给出0-2个参数,它将返回一个从0到n-1个参数的函数。

您可能希望 ** kw 的原因是它允许调用者通过名称指定参数 - 尽管如此,当你完成累加参数时,要检查它是否复杂得多,并且可以说这对于currying来说是一件很奇怪的事情 - 首先将命名参数绑定到 partial ,然后 curry 结果并传入所有curcur风格的其余参数...



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



例如,假设您实现 curry 作为一个类,该类将函数和所有已经curried的参数保存为实例成员。然后你会得到这样的结果:

pre $ def $ _call __(self,* args):
len(args )+ len(self.curried_args)> self.fn.func_code.co_argcount:
raise TypeError('%s()正好接受%d个参数(%d给出)'%
(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:
返回self.fn(* self.curried_args)
else:
返回self

这太可怕了,但它显示了如何处理基本知识。


So I was playing around with currying functions in Python and one of the things that I noticed was that functools.partial returns a partial object rather than an actual function. One of the things that annoyed me about this was that if I did something along the lines of:

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

then we get

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

but what I want to happen is

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

Is there a clean way to make it work like this? I wrote a workaround, but it's too hacky for my taste (doesn't work yet for functions with 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)

Edit: I think it might be helpful if I added more context. I'm writing a decorator that will automatically curry a function like so:

@curry
def add(a, b, c):
  return a + b + c

f = add(1, 2) # f is a function
assert f(5) == 8

I want to hide the fact that f was created from a partial (maybe a bad idea :P). The message that the TypeError message above gives is one example of where whether something is a partial can be revealed. I want to change that.

This needs to be generalizable so EnricoGiampieri's and mgilson's suggestions only work in that specific case.

解决方案

You definitely don't want to do this with exec.

You can find recipes for partial in pure Python, such as this one—many of them are mislabeled as curry recipes, so look for that as well. At any rate, these will show you the proper way to do it without exec, and you can just pick one and modify it to do what you want.

Or you could just wrap partial

However, whatever you do, there's no way the wrapper can know that it's defining a function named "five"; that's just the name of the variable you store the function in. So if you want a custom name, you'll have to pass it in to the function:

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

At that point, you have to wonder why this is any better than just defining a new function.

However, I don't think this is what you actually want anyway. Your ultimate goal is to define a @curry decorator that creates a curried version of the decorated function, with the same name (and docstring, arg list, etc.) as the decorated function. The whole idea of replacing the name of the intermediate partial is a red herring; use functools.wraps properly inside your curry function, and it won't matter how you define the curried function, it'll preserve the name of the original.

In some cases, functools.wraps doesn't work. And in fact, this may be one of those times—you need to modify the arg list, for example, so curry(len) can take either 0 or 1 parameter instead of requiring 1 parameter, right? See update_wrapper, and the (very simple) source code for wraps and update_wrapper to see how the basics work, and build from there.

Expanding on the previous: To curry a function, you pretty much have to return something that takes (*args) or (*args, **kw) and parse the args explicitly, and possibly raise TypeError and other appropriate exceptions explicitly. Why? Well, if foo takes 3 params, curry(foo) takes 0, 1, 2, or 3 params, and if given 0-2 params it returns a function that takes 0 through n-1 params.

The reason you might want **kw is that it allows callers to specify params by name—although then it gets much more complicated to check when you're done accumulating arguments, and arguably this is an odd thing to do with currying—it may be better to first bind the named params with partial, then curry the result and pass in all remaining params in curried style…

If foo has default-value or keyword args, it gets even more complicated, but even without those problems, you already need to deal with this problem.

For example, let's say you implement curry as a class that holds the function and all already-curried parameters as instance members. Then you'll have something like this:

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

This is horribly oversimplified, but it shows how to handle the basics.

这篇关于为什么functools.partial不会返回一个真正的函数(以及如何创建一个函数)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆