2012-02-26 4 views
13

Sto cercando di scrivere un decoratore currying in python, e credo di aver avuto l'idea generale verso il basso, ma ancora ottenuto alcuni casi che non funzionano bene ...Currying decoratore in 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 

Mentre per il seguente caso questo funziona bene:

myfun(5) 
myfun(5) 

per il caso seguente non riesce:

myfun(6)(7) 

Eventuali indicazioni su come farlo correttamente sarebbe molto apprezzato!

Grazie!

+0

Perché non utilizzare qualcosa come parziale nei moduli functools? [link] (http://docs.python.org/library/functools.html#functools.partial) – astevanovic

+4

@digivampire: Perché non fa curry, probabilmente. –

+0

Per scrivere un decoratore per quale scopo? –

risposta

21

L'implementazione di seguito è ingenua, google per "currying python" per esempi più precisi.

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

Grazie amico! Yah, ho capito che doveva essere ricorsivo, ma non avevo idea di come creare implicitamente una funzione con argomenti n-1. Molto bello! –

+3

in python 2.6 o python 3, la riga 3 dovrebbe essere scritta come: argc = x .__ codice __. Co_argcount – NullPointer

0

penso che ho una migliore:

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 

Questa soluzione utilizza propria funzione di Python functools.partial invece di ricreare in modo efficace tale funzionalità. Permette anche di passare più argomenti del minimo, consente argomenti di parole chiave, e passa semplicemente attraverso funzioni che non devono prendere argomenti, dal momento che sono inutili al curry. (Certo, il programmatore dovrebbe sapere meglio di curry funzioni zero Arity o multi-arity, ma è meglio che creare una nuova funzione in quel caso.)

UPDATE: Ops, la parte argomento chiave non lo fa funziona davvero bene Inoltre, gli argomenti opzionali sono contati nell'arity ma * args no. Strano.

3

Il codice sorgente per curry nello toolz library è disponibile al seguente collegamento.

https://github.com/pytoolz/toolz/blob/master/toolz/functoolz.py

Gestisce args, kwargs, funzioni built-in, e la gestione degli errori. Riattiva persino le docstring sull'oggetto al curry.

+2

Questa è di gran lunga la migliore risposta, e usare la versione cytoolz della libreria è molto veloce. Non penso che nessuna altra risposta qui gestisca correttamente gli argomenti predefiniti. – metaperture

1

Questo è abbastanza semplice e non utilizza ispezionare o esaminare args della funzione proposta

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

modo semplice per curry una funzione in pitone è così:

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

Si può usare come segue:

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

che produrrà:

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

Molte delle risposte qui non riescono ad affrontare il fatto che una funzione al curry dovrebbe prendere solo un argomento.

Un preventivo da Wikipedia:

In matematica e informatica, currying è la tecnica di tradurre la valutazione di una funzione che accetta più argomenti (o una tupla di argomenti) in valutazione di una sequenza di funzioni, ciascuna con un singolo argomento (applicazione parziale).

La scelta di decorare con ricorsione e senzaco_argcount fa per una soluzione decentemente elegante.

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 _ 

Come indicato sopra, è anche abbastanza banale per scrivere una uncurry decoratore. :) Sfortunatamente, la funzione uncurried risultante consentirà un numero qualsiasi di argomenti anziché richiedere un numero specifico di argomenti, poiché potrebbe non essere vero per la funzione originale, quindi non è un vero inverso di curry. Il vero inversa in questo caso sarebbe in realtà qualcosa di simile unwrap, ma richiederebbe curry utilizzare functools.wraps o qualcosa di simile che imposta un attributo __wrapped__ per ogni funzione appena creata:

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

Ecco la mia versione di curry che doesn usare parzializzato e fa sì che tutte le funzioni accettino esattamente un parametro:

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