web-dev-qa-db-fra.com

Python: Pourquoi functools.partial est-il nécessaire?

L'application partielle est cool. Quelle fonctionnalité fait functools.partial offre que vous ne pouvez pas passer à travers les lambdas?

>>> 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

Est-ce que functools est en quelque sorte plus efficace ou lisible?

176
Nick Heiner

Quelles sont les fonctionnalités proposées par functools.partial que vous ne pouvez pas utiliser à travers lambdas?

Pas beaucoup en termes de fonctionnalité supplémentaire (mais, voir plus loin) - et la lisibilité est dans l'œil du spectateur.
La plupart des gens familiers avec les langages de programmation fonctionnels (ceux des familles LISP/Scheme en particulier) semblent aimer lambda tout va bien - je dis "most", définitivement pas tous, parce que Guido et moi sommes certainement parmi ceux "familiers" (etc) mais que nous pensons que lambda est une anomalie visuelle dans Python ...
Il se repentait de l'avoir jamais accepté dans Python alors qu'il prévoyait de l'enlever de Python 3, en tant qu'un des "problèmes de Python".
Je l’ai pleinement soutenu. (J'aime lambda dans Scheme ... alors que ses limitations en Python , et la façon bizarre dont il ne rentre tout simplement pas avec le reste de la langue, fait ramper ma peau).

Ce n’est pas le cas, cependant, des hordes de lambda amoureux - qui ont mis en scène l’un des éléments les plus proches d’une rébellion jamais vue dans l’histoire de Python, jusqu’à ce que Guido fasse marche arrière et décide de laisser lambda en.
Plusieurs ajouts possibles à functools (pour que les fonctions renvoyant constantes, identité, etc.) ne se soient pas produits (pour éviter de dupliquer explicitement davantage de fonctionnalités de lambda), bien que partial est bien entendu resté (ce n'est pas une ( duplication totale , ni une duplication).

Rappelez-vous que le corps de lambda est limité à une expression , donc il possède des limitations. Par exemple...:

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

La fonction retournée par functools.partial Est décorée d'attributs utiles pour l'introspection - la fonction qu'elle encapsule et les arguments de position et de nom nommés qu'elle y corrige. De plus, les arguments nommés peuvent être remplacés immédiatement (la "réparation" est plutôt, dans un sens, la définition des valeurs par défaut):

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

Donc, comme vous le voyez, c'est définitivement pas aussi simpliste que lambda s: int(s, base=2)! -)

Oui, vous pourriez contourner votre lambda pour vous en donner une partie - par exemple, pour le mot-clé écrasant,

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

mais je chéri espoir que même l'amant le plus ardent lambda- ne considère pas cette horreur plus lisible que le partial call! -). La partie "paramétrage d'attribut" est encore plus difficile à cause de la limitation de lambda de Python "du corps est une seule expression" (plus le fait que l'affectation ne peut jamais faire partie d'un Python expression) ... vous finissez par "simuler des tâches dans une expression" en étendant la compréhension de la liste bien au-delà de ses limites de conception ...:

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

Combinez maintenant la capacité de substitution des arguments nommés, ainsi que la définition de trois attributs, en une seule expression, et dites-moi à quel point la lecture de sera lisible. ..! -)

241
Alex Martelli

Eh bien, voici un exemple qui montre une différence:

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

Ces articles d'Ivan Moore développent les "limitations de lambda" et les fermetures en python:

71
ars

Dans les dernières versions de Python (> = 2.7), vous pouvez pickle un partial, mais pas 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>
26
Fred Foo

Les functools sont-ils plus efficaces ..?

En partie pour répondre à cela, j'ai décidé de tester les performances. Voici mon exemple:

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))

sur Python 3.3 cela donne:

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

Ce qui signifie que partiel nécessite un peu plus de temps pour la création mais beaucoup moins de temps pour l’exécution. Cela peut très bien être l’effet de la liaison précoce et de la liaison tardive qui sont discutés dans la réponse de ars .

21
Trilarion

Outre les fonctionnalités supplémentaires mentionnées par Alex, un autre avantage de functools.partial est la rapidité. Avec partial, vous pouvez éviter de construire (et de détruire) un autre cadre de pile.

Ni la fonction générée par partial ni lambdas n’ont de docstrings par défaut (bien que vous puissiez définir la chaîne de documentation pour n’importe quel objet via __doc__).

Vous trouverez plus de détails dans ce blog: Application de fonction partielle en Python

11
Leonardo.Z

Je comprends l'intention le plus rapidement dans le troisième exemple.

Quand j'analyse lambdas, je m'attends à plus de complexité/bizarrerie que celle offerte directement par la bibliothèque standard.

De plus, vous remarquerez que le troisième exemple est le seul à ne pas dépendre de la signature complète de sum2; le rendant ainsi légèrement plus faiblement couplé.

1
Jon-Eric