2010-07-15 27 views
138

La aplicación parcial es genial. ¿Qué funcionalidad ofrece functools.partial que no se puede obtener a través de lambdas?Python: ¿Por qué es necesario functools.partial?

>>> sum = lambda x, y : x + y 
>>> sum(1, 2) 
3 
>>> incr = lambda y : sum(1, y) 
>>> incr(2) 
3 
>>> def sum2(x, y): 
    return x + y 

>>> incr2 = functools.partial(sum2, 1) 
>>> incr2(4) 
5 

Es functools de alguna manera más eficiente, o se puede leer?

Respuesta

1

Entiendo el intento más rápido en el tercer ejemplo.

Cuando analizo lambdas, estoy esperando más complejidad/rareza que la que ofrece la biblioteca estándar directamente.

Además, observará que el tercer ejemplo es el único que no depende de la firma completa de sum2; lo que lo hace un poco más débilmente acoplado.

+1

Hm, en realidad soy de la creencia opuesta, me tomó mucho más tiempo analizar los 'functools '.llamada parcial, mientras que las lambdas son evidentes. –

57

Bueno, aquí hay un ejemplo que muestra una diferencia:

In [132]: sum = lambda x, y: x + y 

In [133]: n = 5 

In [134]: incr = lambda y: sum(n, y) 

In [135]: incr2 = partial(sum, n) 

In [136]: print incr(3), incr2(3) 
8 8 

In [137]: n = 9 

In [138]: print incr(3), incr2(3) 
12 8 

Estos mensajes de Ivan Moore ampliar las "limitaciones de lambda" y cierres en Python:

+1

Buen ejemplo. Para mí, esto parece más un "error" con lambda, en realidad, pero entiendo que otros pueden estar en desacuerdo. (Algo similar ocurre con los cierres definidos dentro de un bucle, como se implementa en varios lenguajes de programación). – ShreevatsaR

+23

La solución a este "dilema vinculante temprano versus tardío" es utilizar explícitamente el enlace anticipado, cuando lo desee, mediante 'lambda y, n = n: ... '. La vinculación tardía (de nombres que aparecen _only_ en el cuerpo de una función, no en su 'def' o equivalente' lambda') es cualquier cosa ** pero ** un error, como he mostrado extensamente en respuestas de SO largas en el pasado: usted Al principio, agréguese explícitamente cuando así lo desee, utilice el valor predeterminado de último enlace cuando * ese * es lo que desea, y eso es _exactamente_ la opción de diseño correcta dado el contexto del resto del diseño de Python. –

+1

Sí, este es un buen ejemplo. –

212

¿Qué funcionalidad ofrece functools.partial que no se puede obtener a través de lambdas?

No hay mucho en términos de funcionalidad adicional (pero, véase más adelante) - y, facilidad de lectura está en el ojo del espectador.
La mayoría de las personas que están familiarizadas con los lenguajes de programación funcionales (las familias Lisp/Scheme en particular) parecen gustar lambda bien - digo "más", definitivamente no todo, porque Guido y yo ciertamente estamos entre aquellos "familiarizado con" (etc.) aún piensa en lambda como una anomalía de monstruosidad en Python ...
Estaba arrepentido de haberlo aceptado en Python mientras planeaba eliminarlo de Python 3, como uno de los "fallos técnicos de Python".
Lo apoyé completamente en eso. (Me encanta lambdaen el Esquema ... mientras que sus limitaciones en Python, y la forma extraña que no encaja en con el resto de la lengua, hacen que mi piel de gallina).

No es así, sin embargo, para las hordas de lambda amantes - que llevaron a cabo una de las cosas más cercanas a una rebelión jamás visto en la historia de Python, hasta Guido dio marcha atrás y decidieron dejar lambda en
Varias adiciones posibles a functools. (para hacer funciones devolviendo constantes, identidad, etc.) no sucedió (para evitar duplicar explícitamente más de la funcionalidad de lambda), aunque partial por supuesto permaneció (no es total duplicación, ni es una monstruosidad).

Recuerde que el cuerpo de lambda está limitado a expresión, por lo que tiene limitaciones. Por ejemplo...: Función

>>> import functools 
>>> f = functools.partial(int, base=2) 
>>> f.args 
() 
>>> f.func 
<type 'int'> 
>>> f.keywords 
{'base': 2} 
>>> 

functools.partial 's volvieron está decorado con atributos útiles para la introspección - la función que ha de envolver, y lo posicionales y nombrados argumentos que fija en ella. Además, los argumentos con nombre se pueden anular la derecha de nuevo (la "fijación" es más bien, en un sentido, el ajuste de los valores predeterminados):

>>> f('23', base=10) 
23 

tanto, como se ve, es definely no tan simplista como lambda s: int(s, base=2) ! -)

Sí, usted podría retorcer su lambda para darle algo de esto - por ejemplo, para la palabra clave-predominante,

>>> f = lambda s, **k: int(s, **dict({'base': 2}, **k)) 

pero muy caro espero que incluso la más ardiente lambda -pluma no considera este horror más legible que la llamada partial! -). La parte de "ajuste de atributo" es aún más difícil, debido a la limitación de "cuerpo es una sola expresión" de Python lambda (más el hecho de que la asignación nunca puede ser parte de una expresión de Python) ... terminas "simulando asignaciones dentro de una expresión "estirando lista por comprensión mucho más allá de sus límites de diseño ...:

>>> f = [f for f in (lambda f: int(s, base=2),) 
      if setattr(f, 'keywords', {'base': 2}) is None][0] 

Ahora combinar el overridability-argumentos con nombre, además de la configuración de tres atributos, en una sola expresión, y dime lo que puede leer va a ser ...! -)

+0

Sí, diría que la funcionalidad extra de 'functools.partial' que mencionaste la hace superior a lambda. Tal vez este es el tema de otra publicación, pero ¿qué hay en el nivel de diseño que te molesta tanto acerca de 'lambda'? –

+11

@Rosarch, como dije: primero, limitaciones (Python distingue claramente expresiones y declaraciones; hay mucho que no se puede hacer, o no se puede hacer _sensiblemente_, dentro de una sola expresión, y eso es lo que el cuerpo de una lambda _es_); en segundo lugar, su azúcar sintáctica absolutamente extraña. Si pudiera retroceder en el tiempo y cambiar una cosa dentro de Python, serían las palabras clave absurdas, sin sentido, monstruosas 'def' y' lambda': las dos 'función' (una opción de nombre JavaScript tiene _really_derecha), y en al menos 1/3 de mis objeciones desaparecerían! -). Como dije, no tengo nada que objetar a lambda _in Lisp _...! -) –

+1

@Alex Martelli, ¿Por qué Guido estableció tal limitación para lambda: "el cuerpo es una expresión única"? El cuerpo lambda de C# podría ser cualquier cosa válida en el cuerpo de una función. ¿Por qué Guido no elimina la limitación de python lambda? –

10

Además de la funcionalidad adicional que Alex mencionó, otra ventaja de functools.partial es la velocidad. Con parcial puede evitar construir (y destruir) otro marco de pila.

La función generada por parciales hereda la cadena de documentación de la función original, mientras que lambdas no tienen docstrings por defecto (aunque se puede establecer la cadena de documentación de los objetos a través de __doc__)

Puede encontrar más detalles en este blog: Partial Function Application in Python

+0

Si ha probado la ventaja de velocidad, ¿qué mejora de velocidad de parcial sobre lambda se puede esperar? – Trilarion

20

En las últimas versiones de Python (> = 2.7), puede pickle un partial, pero no un lambda:

>>> pickle.dumps(partial(int)) 
'cfunctools\npartial\np0\n(c__builtin__\nint\np1\ntp2\nRp3\n(g1\n(tNNtp4\nb.' 
>>> pickle.dumps(lambda x: int(x)) 
Traceback (most recent call last): 
    File "<ipython-input-11-e32d5a050739>", line 1, in <module> 
    pickle.dumps(lambda x: int(x)) 
    File "/usr/lib/python2.7/pickle.py", line 1374, in dumps 
    Pickler(file, protocol).dump(obj) 
    File "/usr/lib/python2.7/pickle.py", line 224, in dump 
    self.save(obj) 
    File "/usr/lib/python2.7/pickle.py", line 286, in save 
    f(self, obj) # Call unbound method with explicit self 
    File "/usr/lib/python2.7/pickle.py", line 748, in save_global 
    (obj, module, name)) 
PicklingError: Can't pickle <function <lambda> at 0x1729aa0>: it's not found as __main__.<lambda> 
+1

Desafortunadamente, las funciones parciales no se pueden agrupar para 'multiprocesamiento.Pool.map()'. http://stackoverflow.com/a/3637905/195139 – wting

+1

@wting Esa publicación es de 2010. 'partial' es elegible en Python 2.7. –

15

¿Son los functools de alguna manera más eficientes ...?

Como una respuesta parcial a esto, decidí probar el rendimiento. Aquí está mi ejemplo:

from functools import partial 
import time, math 

def make_lambda(): 
    x = 1.3 
    return lambda: math.sin(x) 

def make_partial(): 
    x = 1.3 
    return partial(math.sin, x) 

Iter = 10**7 

start = time.clock() 
for i in range(0, Iter): 
    l = make_lambda() 
stop = time.clock() 
print('lambda creation time {}'.format(stop - start)) 

start = time.clock() 
for i in range(0, Iter): 
    l() 
stop = time.clock() 
print('lambda execution time {}'.format(stop - start)) 

start = time.clock() 
for i in range(0, Iter): 
    p = make_partial() 
stop = time.clock() 
print('partial creation time {}'.format(stop - start)) 

start = time.clock() 
for i in range(0, Iter): 
    p() 
stop = time.clock() 
print('partial execution time {}'.format(stop - start)) 

en Python 3.3 que da:

lambda creation time 3.1743163756961392 
lambda execution time 3.040552701787919 
partial creation time 3.514482823352731 
partial execution time 1.7113973411608114 

parcial que significa que necesita un poco más de tiempo para la creación pero considerablemente menos tiempo para su ejecución. Este bien puede ser el efecto del enlace temprano y tardío que se discuten en la respuesta del ars.

+1

Más importante aún, 'partial' está escrito en C, en lugar de Python puro, lo que significa que puede producir un invoble más eficiente que simplemente crear una función que llame a otra función. – chepner

Cuestiones relacionadas