231 votes

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

L'application partielle est cool. Quelle fonctionnalité offre functools.partial 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 quelque sorte plus efficace ou lisible?

305voto

Alex Martelli Points 330805

Ce que la fonctionnalité n' functools.partielle de l'offre que vous ne pouvez pas passer à travers les lambdas?

Pas beaucoup en termes de supplément de fonctionnalités (mais, à voir plus tard) - et, la lisibilité est dans l'oeil du spectateur. La plupart des gens qui sont familiers avec les langages de programmation fonctionnelle (ceux du Lisp/Scheme familles en particulier) semblent comme lambda seulement des beaux -- je dis "la plupart", certainement pas tous, parce que Guido et j'sont certainement parmi ceux-ci "familier" (etc), mais la pense de lambda comme une pollution visuelle anomalie en Python... il s'est repenti de l'avoir jamais accepté en Python et prévu de l'enlever en Python 3, comme l'un des "Python pépins", et je suis entièrement pris en charge lui. (J'adore lambda sur le Schéma... mais ses limites en Python, et la façon bizarre, il ne s'intègre pas dans le reste de la langue, faire de ma peau ramper).

Il n'est pas, cependant, pour les hordes de lambda amoureux-qui met en scène l'un des plus proches choses à une rébellion jamais vu en Python de l'histoire, jusqu'à ce que Guido fait marche arrière et a décidé de quitter lambda . Plusieurs ajouts possibles pour functools (pour faire des fonctions retournant des constantes, de l'identité, etc) n'a pas eu lieu (pour éviter explicitement reproduire le plus de lambdas'de la fonctionnalité), mais partial ne restent évidemment (il n'est pas total, la duplication, ni est-il une horreur).

Rappelez-vous que lambda's du corps est limitée à une expression, il y a donc des limites. Par exemple...:

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

functools.partials'est retourné fonction est décoré avec des attributs utiles pour l'introspection -- la fonction de l'emballage, et ce de position et les arguments nommés elle fixe qui y est. En outre, les arguments nommés peut être substituée à l'arrière droit (la "fixation" est plutôt, en un sens, la définition des valeurs par défaut):

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

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

Oui, vous pourrait se contorsionner votre lambda pour vous donner un peu de cela -- par exemple, pour le mot-dominante,

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

mais j'cher espoir que même les plus ardents lambda-amant ne considère pas cette horreur plus lisible que l' partial appeler!-). Et, l'attribut "réglage" la partie est encore plus difficile, en raison de la "le corps est une expression unique" limitation de Python lambda (plus le fait que la cession ne peut jamais faire partie d'une expression Python)... en fin de compte vous "de truquer les affectations au sein de l'expression" par l'étirement de compréhension de 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]

Maintenant combiner les nom-arguments overridability, plus le réglage de trois attributs, en une seule expression, et dites-moi juste comment lisible qui va être...!-)

102voto

ars Points 35803

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 par Ivan Moore développent les "limitations de lambda" et les fermetures en python:

29voto

larsmans Points 167484

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

26voto

Trilarion Points 2045

Est functools d'une certaine manière plus efficace..?

Comme une partie de réponse à cela, j'ai décidé de tester la performance. 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, ça 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 partielle besoin d'un peu plus de temps pour la création, mais beaucoup moins de temps pour l'exécution. Cela peut bien être l'effet du début et de la fin de la liaison qui sont abordés dans la réponse de l' ars.

13voto

Leonardo.Z Points 2494

En plus de la fonctionnalité supplémentaire Alex mentionné, un autre avantage de functools.partielle est la vitesse. Partielle, vous pouvez éviter de construire (et de destruction) d'un autre cadre de pile.

La fonction générée par partielles hérite de la docstring de la fonction d'origine, tandis que les lambdas ont pas docstrings par défaut(bien que vous pouvez définir le doc chaîne pour tous les objets via __doc__ )

Vous pouvez trouver plus de détails dans ce blog: Fonction Partielle de l'Application en Python

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X