library python functools

library - python 3 reduce



¿Cómo funcionan los functools en Python? (4)

Aproximadamente, partial hace algo como esto (aparte del soporte de palabras clave args, etc.):

def partial(func, *part_args): def wrapper(*extra_args): args = list(part_args) args.extend(extra_args) return func(*args) return wrapper

Por lo tanto, al llamar partial(sum2, 4) crea una nueva función (una invocable, para ser precisos) que se comporta como sum2 , pero tiene un argumento posicional menor. Ese argumento que falta siempre se sustituye por 4 , de modo que partial(sum2, 4)(2) == sum2(4, 2)

En cuanto a por qué es necesario, hay una variedad de casos. Solo por uno, suponga que tiene que pasar una función en algún lugar donde se espera que tenga 2 argumentos:

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)

Pero una función que ya tiene necesita acceder a un tercer objeto de context para hacer su trabajo:

def log_event(context, event, params): context.log_event("Something happened %s, %s", event, params)

Entonces, hay varias soluciones:

Un objeto personalizado:

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)

Con parciales:

context = get_context() # whatever notifier.add_listener(partial(log_event, context))

De esos tres, partial es el más corto y el más rápido. (Para un caso más complejo, es posible que desee un objeto personalizado).

No puedo entender cómo funciona el parcial en functools. Tengo el siguiente código desde here :

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

Ahora en la línea

incr = lambda y : sum(1, y)

Entiendo que cualquier argumento que pase para incr se pasará como y a lambda que devolverá la sum(1, y) es decir, 1 + y .

Entiendo que. Pero no entendí este incr2(4) .

¿Cómo pasa el 4 como x en función parcial? Para mí, 4 debería reemplazar el sum2 . ¿Cuál es la relación entre x y 4 ?


Los parciales se pueden usar para crear nuevas funciones derivadas que tienen algunos parámetros de entrada preasignados

Para ver el uso de parciales en el mundo real, consulte esta muy buena publicación de blog:
http://chriskiehl.com/article/Cleaner-coding-through-partially-applied-functions/

Un ejemplo de principiante simple pero ordenado del blog, cubre cómo uno podría usar partial en re.search para hacer que el código sea más legible.
La re.search método re.search es search(pattern, string, flags=0) .

Al aplicar partial , podemos crear múltiples versiones de la search expresión regular para satisfacer nuestros requisitos, por ejemplo:

is_spaced_apart = partial(re.search, ''[a-zA-Z]/s/='') is_grouped_together = partial(re.search, ''[a-zA-Z]/='')

Ahora is_spaced_apart y is_grouped_together son dos nuevas funciones derivadas de re.search que tienen aplicado el argumento pattern (ya que pattern es el primer argumento en la re.search método re.search ).

La firma de estas dos nuevas funciones (invocable) es:

is_spaced_apart(string, flags=0) # pattern ''[a-zA-Z]/s/='' applied is_grouped_together(string, flags=0) # pattern ''[a-zA-Z]/='' applied

Así es como podría utilizar estas funciones parciales en algún texto:

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

Puede consultar el enlace anterior para obtener una comprensión más profunda del tema, ya que cubre este ejemplo específico y mucho más.


respuesta corta, partial da valores predeterminados a los parámetros de una función que de otro modo no tendría valores predeterminados.

from functools import partial def foo(a,b,c,d): return a+b+c+d bar = partial(foo, a=1, b=2) # bar is equivalent to: foo(a=1, b=2, c, d) bar(c=10, d=20) #33 = 1+2+10+20 bar(a=101, c=10, d=20) #133=101+2+10+20


parciales son increíblemente útiles.

Por ejemplo, en una secuencia de llamadas a función ''canalizadas'' (en la cual el valor devuelto de una función es el argumento pasado al siguiente).

A veces, una función en tal canalización requiere un único argumento , pero la función inmediatamente anterior a ella devuelve dos valores .

En este escenario, functools.partial puede permitirle mantener esta tubería de función intacta.

Aquí hay un ejemplo específico y aislado: supongamos que desea ordenar algunos datos por la distancia de cada punto de datos de algún objetivo:

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

Para ordenar estos datos por distancia del objetivo, lo que le gustaría hacer, por supuesto, es este:

data.sort(key=euclid_dist)

pero no puede: el parámetro clave del método de clasificación solo acepta funciones que toman un solo argumento.

así que vuelva a escribir euclid_dist como una función tomando un solo parámetro:

from functools import partial p_euclid_dist = partial(euclid_dist, target)

p_euclid_dist ahora acepta un solo argumento,

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

así que ahora puede ordenar sus datos pasando la función parcial para el argumento clave del método de clasificación:

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

O, por ejemplo, uno de los argumentos de la función cambia en un bucle externo, pero se fija durante la iteración en el bucle interno. Al usar un parcial, no tiene que pasar el parámetro adicional durante la iteración del bucle interno, porque la función modificada (parcial) no lo requiere.

>>> from functools import partial >>> def fnx(a, b, c): return a + b + c >>> fnx(3, 4, 5) 12

crear una función parcial (usando la palabra clave arg)

>>> pfnx = partial(fnx, a=12) >>> pfnx(b=4, c=5) 21

también puedes crear una función parcial con un argumento posicional

>>> pfnx = partial(fnx, 12) >>> pfnx(4, 5) 21

pero esto arrojará (por ejemplo, crear un argumento parcial con palabra clave y luego llamar usando argumentos posicionales)

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

otro caso de uso: escribir código distribuido utilizando la biblioteca de multiprocessing de python. Se crea un conjunto de procesos utilizando el método Pool:

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

Pool tiene un método de mapa, pero solo requiere un único iterable, por lo que si necesita pasar una función con una lista de parámetros más larga, vuelva a definir la función como parcial, para corregir todos menos uno:

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