346 votes

Comment fonctionne le partiel des functools en Python?

Je ne suis pas en mesure d'obtenir ma tête sur la manière dont les parties d'ouvrages en functools. J'ai le code suivant à partir d' 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 ligne de

incr = lambda y : sum(1, y)

Je reçois que, quel que soit l'argument que je passe à l' incr il sera passée en y de lambda qui sera de retour sum(1, y) - je.e 1 + y.

Je comprends que. Mais je ne comprenais pas ce incr2(4).

Comment l' 4 est passée comme x en fonction partielle? Pour moi, 4 devrait remplacer l' sum2. Quelle est la relation entre x et 4?

405voto

bereal Points 7280

En gros, partial fait quelque chose comme ça (en dehors de mot-clé args soutien, 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éer une nouvelle fonction (un callable, pour être précis) qui se comporte comme sum2, mais a une position argument de moins en moins. Que l'argument manquant est toujours remplacé par 4, de sorte qu' partial(sum2, 4)(2) == sum2(4, 2)

Comme de raison d'être, il ya une variété de cas. Juste pour un, supposons que vous ayez à passer d'une fonction à un endroit où il est censé 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 de l'accès à certains tiers - context objet 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 les harmoniques:

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

177voto

doug Points 29567

les partiels sont incroyablement utile, indispensable, parfois-par exemple, une fonction, vous devez appeler à côté n'accepte qu'un seul argument et vous avez deux valeurs retournées à partir de l'étape précédente.

supposons que vous voulez trier certaines données par chaque point de la distance d'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 les données en fonction de la distance de la cible, ce que vous voulez faire est bien sûr ce:

data.sort(key=euclid_dist)

mais vous ne pouvez pas--le tri de la méthode clé paramètre accepte uniquement les fonctions qui prennent un seul argument.

donc ré-écrire euclid_dist comme une fonction prenant un seul paramètre:

from functools import partial

p_euclid_dist = partial(euclid_dist, target)

p_euclid_dist maintenant accepte un seul argument,

>>> p_euclid_dist((3, 3))
  1.4142135623730951

alors maintenant, vous pouvez trier vos données en passant par la fonction partielle de la méthode de tri de l'argument-clé:

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, les arguments de la fonction des changements dans une boucle externe, mais est fixe au cours d'une itération dans la boucle interne. À l'aide d'un partiel, vous n'avez pas à passer dans le paramètre supplémentaire au cours d'une itération de la boucle interne, en raison de la modification de la (partielle) de la fonction 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 (à l'aide de mots clés arg)

>>> pfnx = partial(fnx, a=12)

>>> pfnx(b=4, c=5)
     21

vous pouvez également créer une fonction partielle avec une position d'argument

>>> pfnx = partial(fnx, 12)

>>> pfnx(4, 5)
      21

mais cela permettra de lancer (par exemple, la création d'partielle avec argument mot-clé, puis de l'appel à l'aide des arguments de position)

>>> 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: l'écriture distribué code à l'aide de python multitraitement de la bibliothèque. Un pool de processus est créé à l'aide de la Piscine de la méthode:

>>> import multiprocessing as MP

>>> # create a process pool:
>>> ppool = MP.Pool()

La piscine dispose d'une carte de la méthode, mais il ne prend qu'un seul objet iterable, donc si vous avez besoin de passer dans une fonction avec une longue liste de paramètres, de re-définir la fonction partielle, de fixer tous, sauf un:

>>> ppool.map(pfnx, [4, 6, 7, 8])

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