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.

26
demandé sur beoliver 2012-06-24 02:58:26

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)
28
répondu Eric 2012-06-23 23:46:25

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

11
répondu kosii 2012-06-24 00:23:13

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)
7
répondu nrob 2016-05-26 18:12:15

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.

6
répondu millimoose 2012-06-23 23:10:46

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)
5
répondu iruvar 2012-06-23 23:16:55

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

2
répondu beoliver 2012-06-23 23:28:22

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

1
répondu danijar 2018-02-27 18:57:56

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))
1
répondu Micah Smith 2018-08-13 19:32:59

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

0
répondu MSeifert 2017-06-24 10:00:31

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]
0
répondu Corvince 2018-09-14 13:03:16