2012-02-26 94 views
13

我试图用Python语言编写一个钻营装饰,我想我已经得到了普遍的想法了,但还是有一些情况下,那些不工作的权利......柯里装饰在Python

def curry(fun): 

    cache = [] 
    numargs = fun.func_code.co_argcount 

    def new_fun(*args, **kwargs): 
     print args 
     print kwargs 
     cache.extend(list(args)) 

     if len(cache) >= numargs: # easier to do it explicitly than with exceptions 

      temp = [] 
      for _ in xrange(numargs): 
       temp.append(cache.pop()) 
      fun(*temp) 

    return new_fun 


@curry 
def myfun(a,b): 
    print a,b 

在以下情况下能正常工作:

myfun(5) 
myfun(5) 

对于失败下面的情况:

myfun(6)(7) 

如何任何指针正确地做到这一点将不胜感激!

谢谢!

+0

为什么不在functools模块中使用类似partial的东西? [链接](http://docs.python.org/library/functools.html#functools.partial) – astevanovic 2012-02-26 23:48:26

+4

@digivampire:因为它不会做currying,可能。 – 2012-02-27 00:00:19

+0

写一个装饰器用于什么目的? – 2012-02-27 08:01:27

回答

21

下面的实现是天真的,谷歌的“咖喱python”为更准确的例子。

def curry(x, argc=None): 
    if argc is None: 
     argc = x.func_code.co_argcount 
    def p(*a): 
     if len(a) == argc: 
      return x(*a) 
     def q(*b): 
      return x(*(a + b)) 
     return curry(q, argc - len(a)) 
    return p 

@curry 
def myfun(a,b,c): 
    print '%d-%d-%d' % (a,b,c) 



myfun(11,22,33) 
myfun(44,55)(66) 
myfun(77)(88)(99) 
+0

谢谢你!是啊,我意识到它需要递归,但不知道如何隐式地创建一个n-1参数的函数。很酷! – 2012-02-27 00:00:51

+3

在Python 2.6或Python 3中,第3行应写为: argc = x .__ code __。co_argcount – NullPointer 2012-03-23 14:11:38

0

我想我有一个更好的:

def curried (function): 
    argc = function.__code__.co_argcount 

    # Pointless to curry a function that can take no arguments 
    if argc == 0: 
     return function 

    from functools import partial 
    def func (*args): 
     if len(args) >= argc: 
      return function(*args) 
     else: 
      return partial(func, *args) 
    return func 

该解决方案使用Python的functools.partial功能,而不是有效地重建该功能。它还允许你传递比最小值,-allows关键字参数更多的参数,并且只传递不需要参数的函数,因为这些函数没有意义。 (当然,程序员应该知道不如咖喱零元数或多元数的功能,但它是不是在这种情况下,创建一个新的功能更好。)

UPDATE:哎呦,关键字参数部分不实际上工作正确。此外,可选参数计算在arity中,但* args不是。奇怪的。

1

这一个是相当简单的,不使用查验给定函数的ARGS

import functools 


def curried(func): 
    """A decorator that curries the given function. 

    @curried 
    def a(b, c): 
     return (b, c) 

    a(c=1)(2) # returns (2, 1) 
    """ 
    @functools.wraps(func) 
    def _curried(*args, **kwargs): 
     return functools.partial(func, *args, **kwargs) 
    return _curried 
1

讨好Python中的功能最简单的方法是这样的:

from functools import partial 
curry = lambda f, g: partial(
    lambda F, G, *args, **kwargs: F(G(*args,**kwargs)), 
    f, g 
) 

https://gist.github.com/hkupty/0ba733c0374964d41dec

可以使用它如下:

_list = [] 
mask = "Test {}" 
append_masked = curry(_list.append, mask.format) 
for i in range(10): 
    append_masked(i) 

这将产生:

['Test 1', 'Test 2', 'Test 3' ... 'Test 10'] 
1

因为它很酷写讨好装饰在Python中,我试图矿:5 lines of code, readable, and tested curry function

def curry(func): 
    def curried(*args, **kwargs): 
     if len(args) + len(kwargs) >= func.__code__.co_argcount: 
      return func(*args, **kwargs) 
     return (lambda *args2, **kwargs2: 
       curried(*(args + args2), **dict(kwargs, **kwargs2))) 
    return curried 
2

这里的许多答案未能解决curry函数应该只带一个参数这一事实。

Wikipedia引述:

在数学和计算机科学,钻营是 平移这需要多个参数 函数的评价(或参数的元组)到评估序列中的技术函数, 每个都有一个参数(部分应用程序)。

选择与递归和来装饰它没有co_argcount使一个体面优雅的解决方案。

from functools import partial, wraps, reduce 

def curry(f): 
    @wraps(f) 
    def _(arg): 
     try: 
      return f(arg) 
     except TypeError: 
      return curry(wraps(f)(partial(f, arg))) 
    return _ 

def uncurry(f): 
    @wraps(f) 
    def _(*args): 
     return reduce(lambda x, y: x(y), args, f) 
    return _ 

如上所示,它也是相当微不足道写一个uncurry装饰器。 :)不幸的是,由此产生的uncurried函数将允许任意数量的参数,而不需要特定数量的参数,原始函数可能不是这样,所以它不是curry的真正反函数。在这种情况下,真正的反转实际上是这样的unwrap,但它需要curry使用functools.wraps或类似的东西,设置一个__wrapped__属性为每个新创建的函数:

def unwrap(f): 
    try: 
     return unwrap(f.__wrapped__) 
    except AttributeError: 
     return f 
0

这里是我的版本咖喱是没有按不使用部分,并使所有的功能只能接受一个参数:

def curry(func): 
"""Truly curry a function of any number of parameters 
returns a function with exactly one parameter 
When this new function is called, it will usually create 
and return another function that accepts an additional parameter, 
unless the original function actually obtained all it needed 
at which point it just calls the function and returns its result 
""" 
def curried(*args): 
    """ 
    either calls a function with all its arguments, 
    or returns another functiont that obtains another argument 
    """ 
    if len(args) == func.__code__.co_argcount: 
     ans = func(*args) 
     return ans 
    else: 
     return lambda x: curried(*(args+(x,))) 

return curried