54 votes

Comment remplir des arguments positionnels spécifiques avec des partiels en python ?

En gros, ce que j'aimerais faire c'est :

>>> from functools import partial
>>> partial(str.startswith, prefix='a')('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: startswith() takes no keyword arguments

Mais la question est plus générale, comment remplir des arguments positionnels spécifiques avec partial .

P.S. Je réalise que je peux utiliser une lambda à la place.

62voto

Nate Points 6155

C'est impossible. Vous devez créer une fonction d'encapsulation.

En principe, vous utiliseriez des arguments par mot-clé, comme vous avez essayé de le faire - c'est à cela qu'ils servent, non ? Malheureusement, comme vous l'avez découvert, les fonctions de la bibliothèque standard de Python ne prennent pas de paramètres nommés . Ainsi, il n'est pas possible, compte tenu de la mise en œuvre actuelle de l partial sans qu'une autre fonction vienne interférer.

Selon l'acceptation de PEP 309 ce qui a été accepté pour l'inclusion était "une liaison de constructeur de type partial()". le plus à gauche les arguments positionnels et les éventuels mots-clés". En outre, il est précisé :

Notez que lorsque l'objet est appelé comme s'il était une fonction, les arguments positionnels sont en annexe à ceux fournis au constructeur, et les arguments des mots-clés remplacent et augmentent ceux fournis au constructeur.

Les arguments positionnels, les arguments mots-clés ou les deux peuvent être fournis lors de la création de l'objet et lors de son appel.

Parce que les arguments positionnels supplémentaires sont en annexe il n'y aurait aucun moyen de fournir un argument positionnel précédent (par cette implémentation).

Cependant, il continue :

Appliquer partiellement les arguments de l droite ou l'insertion d'arguments à des positions arbitraires crée ses propres problèmes, mais en attendant la découverte d'une bonne implémentation et d'une sémantique non confuse, je ne pense pas qu'il faille l'exclure.

Il semblerait donc qu'elle puisse être sur la table, mais en l'état actuel des choses, elle n'est pas mise en œuvre de cette manière.

Pour des raisons de transparence, l'accentuation entre les guillemets ci-dessus est de mon fait.

12voto

oblalex Points 221

Si vous en avez vraiment besoin, vous pouvez utiliser rpartiel de funcy Bibliothèque tierce partie.

Son code est ici :

def rpartial(func, *args):
    return lambda *a: func(*(a + args))

Donc, votre cas peut être traité comme suit :

>>> startswith_a = rpartial(str.startswith, 'a')
>>> startswith_a('abc')
True
>>> startswith_a('def')
False

10voto

Goodies Points 1501

Je sais que c'est vieux, mais je rencontre ce problème tout le temps, et je pense avoir trouvé une bonne solution. J'utilise une version légèrement modifiée de partial qui utilise un objet Ellipses ( ... ) comme valeur de remplacement si vous ne la connaissez pas au moment où l'objet est construit. C'est très utile dans ce cas précis !

Voici l'original __call__ méthode de partial

def __call__(self, /, *args, **keywords):
    keywords = {**self.keywords, **keywords}
    return self.func(*self.args, *args, **keywords)

Au lieu de cela, nous pouvons utiliser le littéral ... comme un cas spécial pour indiquer une valeur de type "placeholder".

>>> type(...)
<class 'ellipsis'>

Voici l'implémentation complète :

class bind(partial):
    """
    An improved version of partial which accepts Ellipsis (...) as a placeholder
    """
    def __call__(self, *args, **keywords):
        keywords = {**self.keywords, **keywords}
        iargs = iter(args)
        args = (next(iargs) if arg is ... else arg for arg in self.args)
        return self.func(*args, *iargs, **keywords)

L'utilisation est assez simple

def foo(a, b, c, /, *, d):
    print(f"A({a}) B({b}) C({c}) D({d})")

f1 = bind(foo, 1, 2, 3, d=4)
f1()

f2 = bind(foo, 1, 2, d=4)
f2(3)

f3 = bind(foo, 1, ..., 3, d=4)
f3(2)

f4 = bind(foo, ..., 2, ..., d=4)
f4(1, 3)

f5 = bind(foo, ..., d=5)
f5(1, 2, 3, d=4)

1voto

Elizabeth F Points 29

Utilisez ce code :

# See: functoolspartial for binding...

class Function(object):
    def __init__(self, fn):
        self.fn = fn

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)

    def __add__(self, other):
        def subfn(*args, **kwargs):
            return self(*args, **kwargs) + other(*args, **kwargs)
        return subfn

class arg(object):
    """Tagging class"""
    def __init__(self, index):
        self.index = index

class bind(Function):
    """Reorder positional arguments.
    Eg: g = f('yp', _1, 17, _0, dp=23)
    Then g('a', 'b', another=55) --> f('yp', 'b', 17, 'a', dp=23, another=55)
    """

    def __init__(self, fn, *pargs, **pkwargs):
        # Maximum index referred to by the user.
        # Inputs to f above this index will be passed through
        self.fn = fn
        self.pargs = pargs
        self.pkwargs = pkwargs
        self.max_gindex = max(
            (x.index if isinstance(x, arg) else -1 for x in pargs),
            default=-1)

    def __call__(self, *gargs, **gkwargs):
        fargs = \
            [gargs[x.index] if isinstance(x, arg) else x for x in self.pargs] + \
            list(gargs[self.max_gindex+1:])

        fkwargs = dict(self.pkwargs)
        fkwargs.update(gkwargs)    # Overwrite keys
        return self.fn(*fargs, *fkwargs)

Alors essayez-le comme ça :

def myfn(a,b,c):
print(a,b,c)

bind(myfn, arg(1), 17, arg(0))(19,14)

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