propias - parametros por omision python
¿Se puede aplicar parcialmente el segundo argumento de una función que no toma argumentos de palabras clave? (10)
Tomemos, por ejemplo, la función python integrada en pow()
.
xs = [1,2,3,4,5,6,7,8]
from functools import partial
list(map(partial(pow,2),xs))
>>> [2, 4, 8, 16, 32, 128, 256]
¿Pero cómo elevaría las xs a la potencia de 2?
para obtener [1, 4, 9, 16, 25, 49, 64]
list(map(partial(pow,y=2),xs))
TypeError: pow() takes no keyword arguments
Sé que la comprensión de la lista sería más fácil.
No
Según la documentación , partial
no puede hacer esto (énfasis mío):
arcos parciales
Los argumentos posicionales más a la izquierda que se añadirán a los argumentos posicionales.
Siempre se puede simplemente "arreglar" el poder tener argumentos de palabras clave:
_pow = pow
pow = lambda x, y: _pow(x, y)
¿Por qué no simplemente crear una función lambda rápida que reordena los argumentos y parcial que
partial(lambda p, x: pow(x, p), 2)
Como ya se dijo, eso es una limitación de functools.partial
si la función que desea partial
no acepta argumentos de palabras clave.
Si no te importa usar una biblioteca externa 1, puedes usar iteration_utilities.partial
que tiene un parcial que admite marcadores de posición:
>>> from iteration_utilities import partial
>>> square = partial(pow, partial._, 2) # the partial._ attribute represents a placeholder
>>> list(map(square, xs))
[1, 4, 9, 16, 25, 36, 49, 64]
1 Descargo de responsabilidad: soy el autor de la biblioteca iteration_utilities
(las instrucciones de instalación se pueden encontrar en la documentación en caso de que esté interesado).
Creo que solo usaría este sencillo de una sola línea:
import itertools
print list(itertools.imap(pow, [1, 2, 3], itertools.repeat(2)))
Actualizar:
También se me ocurrió una solución más divertida que útil. Es un hermoso azúcar sintáctico, aprovechando el hecho de que ...
literal significa Ellipsis
en Python3. Es una versión modificada de partial
, que permite omitir algunos argumentos posicionales entre los más a la izquierda y los más a la derecha. El único inconveniente es que no se puede pasar más elipsis como argumento.
import itertools
def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords):
newkeywords = keywords.copy()
newkeywords.update(fkeywords)
return func(*(newfunc.leftmost_args + fargs + newfunc.rightmost_args), **newkeywords)
newfunc.func = func
args = iter(args)
newfunc.leftmost_args = tuple(itertools.takewhile(lambda v: v != Ellipsis, args))
newfunc.rightmost_args = tuple(args)
newfunc.keywords = keywords
return newfunc
>>> print partial(pow, ..., 2, 3)(5) # (5^2)%3
1
>>> print partial(pow, 2, ..., 3)(5) # (2^5)%3
2
>>> print partial(pow, 2, 3, ...)(5) # (2^3)%5
3
>>> print partial(pow, 2, 3)(5) # (2^3)%5
3
Así que la solución para la pregunta original sería con esta versión de list(map(partial(pow, ..., 2),xs))
parcial list(map(partial(pow, ..., 2),xs))
La muy versátil función incluye una función rpartial
que aborda exactamente este problema.
xs = [1,2,3,4,5,6,7,8]
from funcy import rpartial
list(map(rpartial(pow, 2), xs))
# [1, 4, 9, 16, 25, 36, 49, 64]
Es solo un lambda bajo el capó:
def rpartial(func, *args):
"""Partially applies last arguments."""
return lambda *a: func(*(a + args))
Podrías crear una función de ayuda para esto:
from functools import wraps
def foo(a, b, c, d, e):
print(''foo(a={}, b={}, c={}, d={}, e={})''.format(a, b, c, d, e))
def partial_at(func, index, value):
@wraps(func)
def result(*rest, **kwargs):
args = []
args.extend(rest[:index])
args.append(value)
args.extend(rest[index:])
return func(*args, **kwargs)
return result
if __name__ == ''__main__'':
bar = partial_at(foo, 2, ''C'')
bar(''A'', ''B'', ''D'', ''E'')
# Prints: foo(a=A, b=B, c=C, d=D, e=E)
Descargo de responsabilidad: no he probado esto con argumentos de palabras clave por lo que podría explotar debido a ellos de alguna manera. Además, no estoy seguro de si esto es para lo que se debe usar @wraps
pero parece correcto.
Puedes hacer esto con lambda
, que es más flexible que functools.partial()
:
pow_two = lambda base: pow(base, 2)
print(pow_two(3)) # 9
Más generalmente:
def bind_skip_first(func, *args, **kwargs):
return lambda first: func(first, *args, **kwargs)
pow_two = bind_skip_first(pow, 2)
print(pow_two(3)) # 9
Un inconveniente de la lambda es que algunas bibliotecas no pueden serializarlo.
Si no puede usar las funciones lambda, también puede escribir una función de envoltura simple que reordene los argumentos.
def _pow(y, x):
return pow(x, y)
y luego llamar
list(map(partial(_pow,2),xs))
>>> [1, 4, 9, 16, 25, 36, 49, 64]
Una forma de hacerlo sería:
def testfunc1(xs):
from functools import partial
def mypow(x,y): return x ** y
return list(map(partial(mypow,y=2),xs))
pero esto implica redefinir la función pow.
Si el uso de parcial no fuera ''necesario'', entonces un simple lambda haría el truco.
def testfunc2(xs):
return list(map(lambda x: pow(x,2), xs))
Y una forma específica de mapear el poder de 2 sería
def testfunc5(xs):
from operator import mul
return list(map(mul,xs,xs))
pero ninguno de estos aborda completamente el problema directamente de la aplicación parcial en relación con los argumentos de palabras clave
podrías usar un cierre
xs = [1,2,3,4,5,6,7,8]
def closure(method, param):
def t(x):
return method(x, param)
return t
f = closure(pow, 2)
f(10)
f = closure(pow, 3)
f(10)