Je n'arrive pas à comprendre comment fonctionne le partiel dans functools. J'ai le code suivant de ici :
>>> 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
Maintenant dans la file
incr = lambda y : sum(1, y)
Je comprends que, quel que soit l'argument que je passe à incr
, il sera passé sous la forme y
à lambda
qui retournera sum(1, y)
c'est-à-dire 1 + y
.
Je comprends que. Mais je n'ai pas compris cela incr2(4)
.
Comment le 4
est-il passé sous la forme x
dans une fonction partielle? Pour moi, 4
devrait remplacer le sum2
. Quelle est la relation entre x
et 4
?
En gros, partial
fait quelque chose comme ceci (mis à part le support des mots clés, etc.):
def partial(func, *part_args):
def wrapper(*extra_args):
args = list(part_args)
args.extend(extra_args)
return func(*args)
return wrapper
Donc, en appelant partial(sum2, 4)
, vous créez une nouvelle fonction (un callable, pour être précis) qui se comporte comme sum2
, mais avec un argument de position en moins. Cet argument manquant est toujours remplacé par 4
, de sorte que partial(sum2, 4)(2) == sum2(4, 2)
Pour ce qui est de la nécessité, il existe une variété de cas. Juste pour un, supposons que vous deviez passer une fonction quelque part où elle devrait avoir 2 arguments:
class EventNotifier(object):
def __init__(self):
self._listeners = []
def add_listener(self, callback):
''' callback should accept two positional arguments, event and params '''
self._listeners.append(callback)
# ...
def notify(self, event, *params):
for f in self._listeners:
f(event, params)
Mais une fonction que vous avez déjà besoin d’accéder à un troisième objet context
pour faire son travail:
def log_event(context, event, params):
context.log_event("Something happened %s, %s", event, params)
Donc, il y a plusieurs solutions:
Un objet personnalisé:
class Listener(object):
def __init__(self, context):
self._context = context
def __call__(self, event, params):
self._context.log_event("Something happened %s, %s", event, params)
notifier.add_listener(Listener(context))
Lambda:
log_listener = lambda event, params: log_event(context, event, params)
notifier.add_listener(log_listener)
Avec partiels:
context = get_context() # whatever
notifier.add_listener(partial(log_event, context))
De ces trois, partial
est le plus court et le plus rapide. (Pour un cas plus complexe, vous voudrez peut-être un objet personnalisé).
partiels sont incroyablement utiles.
Par exemple, dans une séquence d'appels de fonctions "alignés sur les tuyaux" (dans laquelle la valeur renvoyée par une fonction est l'argument transmis à la suivante).
Parfois, une fonction dans un tel pipeline nécessite un argument unique, mais la fonction immédiatement en amont de celle-ci renvoie deux valeurs.
Dans ce scénario, functools.partial
peut vous permettre de conserver ce pipeline de fonctions intact.
Voici un exemple spécifique et isolé: supposons que vous souhaitiez trier certaines données en fonction de la distance de chaque point de données par rapport à une cible:
# create some data
import random as RND
fnx = lambda: RND.randint(0, 10)
data = [ (fnx(), fnx()) for c in range(10) ]
target = (2, 4)
import math
def euclid_dist(v1, v2):
x1, y1 = v1
x2, y2 = v2
return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
Pour trier ces données en fonction de la distance par rapport à la cible, ce que vous voudriez bien sûr faire est la suivante:
data.sort(key=euclid_dist)
mais vous ne pouvez pas - le paramètre key de la méthode sort accepte uniquement les fonctions qui prennent un argument single.
donc réécrivez euclid_dist
comme une fonction prenant un paramètre single:
from functools import partial
p_euclid_dist = partial(euclid_dist, target)
p_euclid_dist
accepte maintenant un seul argument,
>>> p_euclid_dist((3, 3))
1.4142135623730951
alors maintenant, vous pouvez trier vos données en passant la fonction partielle pour l'argument clé de la méthode de tri:
data.sort(key=p_euclid_dist)
# verify that it works:
for p in data:
print(round(p_euclid_dist(p), 3))
1.0
2.236
2.236
3.606
4.243
5.0
5.831
6.325
7.071
8.602
Ou, par exemple, l'un des arguments de la fonction change dans une boucle externe mais est fixe lors de l'itération dans la boucle interne. En utilisant un partiel, vous n'avez pas besoin de transmettre le paramètre supplémentaire lors de l'itération de la boucle interne, car la fonction modifiée (partielle) ne l'exige pas.
>>> from functools import partial
>>> def fnx(a, b, c):
return a + b + c
>>> fnx(3, 4, 5)
12
créer une fonction partielle (en utilisant le mot-clé arg)
>>> pfnx = partial(fnx, a=12)
>>> pfnx(b=4, c=5)
21
vous pouvez également créer une fonction partielle avec un argument de position
>>> pfnx = partial(fnx, 12)
>>> pfnx(4, 5)
21
mais cela jettera (par exemple, créer un argument partiel avec mot-clé puis appeler en utilisant des arguments positionnels)
>>> pfnx = partial(fnx, a=12)
>>> pfnx(4, 5)
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
pfnx(4, 5)
TypeError: fnx() got multiple values for keyword argument 'a'
un autre cas d'utilisation: écrire du code distribué en utilisant la bibliothèque multiprocessing
de python. Un pool de processus est créé à l'aide de la méthode Pool:
>>> import multiprocessing as MP
>>> # create a process pool:
>>> ppool = MP.Pool()
Pool
a une méthode map, mais il ne faut qu'une seule itérative, donc si vous devez transmettre une fonction avec une liste de paramètres plus longue, redéfinissez la fonction en tant que partielle, pour corriger tout le monde sauf un:
>>> ppool.map(pfnx, [4, 6, 7, 8])
Les partiels peuvent être utilisés pour créer de nouvelles fonctions dérivées ayant certains paramètres d'entrée prédéfinis.
Pour voir une utilisation réelle des partiels dans le monde réel, reportez-vous à ce très bon article de blog:
http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/
L'exemple du débutant, simple mais soigné, du blog, explique comment utiliser partial
sur re.search
pour rendre le code plus lisible. La signature de la méthode re.search
est:
search(pattern, string, flags=0)
En appliquant partial
, nous pouvons créer plusieurs versions de l'expression régulière search
pour répondre à nos besoins. Ainsi, par exemple:
is_spaced_apart = partial(re.search, '[a-zA-Z]\s\=')
is_grouped_together = partial(re.search, '[a-zA-Z]\=')
Maintenant, is_spaced_apart
et is_grouped_together
sont deux nouvelles fonctions dérivées de re.search
auxquelles l'argument pattern
est appliqué (puisque pattern
est le premier argument du re.search
signature de la méthode).
La signature de ces deux nouvelles fonctions (appelable) est:
is_spaced_apart(string, flags=0) # pattern '[a-zA-Z]\s\=' applied
is_grouped_together(string, flags=0) # pattern '[a-zA-Z]\=' applied
Voici comment utiliser ces fonctions partielles sur du texte:
for text in lines:
if is_grouped_together(text):
some_action(text)
Elif is_spaced_apart(text):
some_other_action(text)
else:
some_default_action()
Vous pouvez vous référer au lien ci-dessus pour obtenir une compréhension plus approfondie du sujet, car il couvre cet exemple spécifique et bien plus encore.
réponse courte, partial
donne les valeurs par défaut aux paramètres d'une fonction qui autrement n'aurait pas de valeurs par défaut.
from functools import partial
def foo(a,b):
return a+b
bar = partial(foo, a=1) # equivalent to: foo(a=1, b)
bar(b=10)
#11 = 1+10
bar(a=101, b=10)
#111=101+10
À mon avis, c'est une façon de mettre en œuvre currying en python.
from functools import partial
def add(a,b):
return a + b
def add2number(x,y,z):
return x + y + z
if __== "__main__":
add2 = partial(add,2)
print("result of add2 ",add2(1))
add3 = partial(partial(add2number,1),2)
print("result of add3",add3(1))
Le résultat est 3 et 4.
Il convient également de mentionner que, lorsque la fonction partielle passe une autre fonction pour laquelle nous voulons "coder en dur" certains paramètres, ce doit être le paramètre le plus à droite.
def func(a,b):
return a*b
prt = partial(func, b=7)
print(prt(4))
#return 28
mais si nous faisons la même chose, mais en changeant un paramètre à la place
def func(a,b):
return a*b
prt = partial(func, a=7)
print(prt(4))
il va générer une erreur, "TypeError: func () a eu plusieurs valeurs pour l'argument 'a'"