Peut-on appliquer partiellement le deuxième argument d'une fonction qui ne prend aucun argument de mot-clé?
Prenez par exemple la fonction python intégrée à 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]
Mais comment élever le xs à la puissance de 2?
Pour obtenir [1, 4, 9, 16, 25, 49, 64]
list(map(partial(pow,y=2),xs))
TypeError: pow() takes no keyword arguments
Je sais que les compréhensions de liste seraient plus faciles.
10 réponses
Non
Selon la documentation, partial
ne peut pas ce faire (l'emphase est de mon propre):
Partiel.args
Les arguments positionnelsles plus à gauche qui seront ajoutés aux arguments positionnels
Vous pouvez toujours simplement "réparer" pow
pour avoir des args de mots clés:
_pow = pow
pow = lambda x, y: _pow(x, y)
Je pense que je voudrais juste utiliser ce simple one-liner:
import itertools
print list(itertools.imap(pow, [1, 2, 3], itertools.repeat(2)))
Mise à Jour:
J'ai aussi trouvé une solution plus drôle qu'utile. C'est un beau sucre syntaxique, profitant du fait que le littéral ...
signifie Ellipsis
en Python3. C'est une version modifiée de partial
, permettant d'omettre certains arguments positionnels entre les arguments les plus à gauche et à droite. Le seul inconvénient est que vous ne pouvez plus passer D'ellipses en argument.
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
Donc la la solution pour le question originale serait avec cette version de partielle list(map(partial(pow, ..., 2),xs))
Pourquoi ne pas simplement créer une fonction lambda rapide qui réorganise les arguments et Partielle que
partial(lambda p, x: pow(x, p), 2)
Vous pouvez créer une fonction d'aide pour cela:
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)
Disclaimer: Je n'ai pas testé cela avec des arguments de mots clés, donc il pourrait exploser à cause d'eux en quelque sorte. Aussi, je ne suis pas sûr si c'est ce à quoi @wraps
devrait être utilisé, mais cela semblait juste.
Vous pouvez utiliser une fermeture
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)
Une façon de le faire serait:
def testfunc1(xs):
from functools import partial
def mypow(x,y): return x ** y
return list(map(partial(mypow,y=2),xs))
Mais cela implique de redéfinir la fonction pow.
Si l'utilisation partielle n'était pas "nécessaire" alors qu'un simple lambda ferait l'affaire
def testfunc2(xs):
return list(map(lambda x: pow(x,2), xs))
Et une façon spécifique de mapper le pow de 2 serait
def testfunc5(xs):
from operator import mul
return list(map(mul,xs,xs))
Mais aucun de ceux-ci ne résout entièrement le problème directement de l'application partielle par rapport aux arguments de mots clés
, Vous pouvez le faire avec lambda
, ce qui est plus flexible que functools.partial()
:
pow_two = lambda base: pow(base, 2)
print(pow_two(3)) # 9
Plus généralement:
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 inconvénient de lambda est que certaines bibliothèques ne sont pas capables de le sérialiser.
Le très polyvalent funcy inclut une fonction rpartial
qui répond exactement à ce problème.
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]
, C'est juste un lambda sous le capot:
def rpartial(func, *args):
"""Partially applies last arguments."""
return lambda *a: func(*(a + args))
Comme déjà dit, c'est une limitation de functools.partial
si la fonction que vous voulez partial
n'accepte pas les arguments mots-clefs.
Si cela ne vous dérange pas d'utiliser une bibliothèque externe1 vous pourriez utiliser iteration_utilities.partial
qui a un partiel qui prend en charge les espaces réservés:
>>> 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 Avertissement: je suis l'auteur de la iteration_utilities
bibliothèque (les instructions d'installation peuvent être trouvés dans la documentation dans le cas où vous êtes intéressé).
Si vous ne pouvez pas utiliser les fonctions lambda, vous pouvez également écrire une simple fonction wrapper qui réordonne les arguments.
def _pow(y, x):
return pow(x, y)
Puis appelez
list(map(partial(_pow,2),xs))
>>> [1, 4, 9, 16, 25, 36, 49, 64]