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?
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. ..! -)
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:
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>
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 .
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
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é.