web-dev-qa-db-fra.com

Pourquoi utiliser ** kwargs en python? Quels sont les avantages du monde réel par rapport à l'utilisation d'arguments nommés?

Je viens d'un milieu dans les langages statiques. Quelqu'un peut-il expliquer (idéalement par l'exemple) le monde réel avantages d'utiliser ** kwargs par rapport aux arguments nommés?

Pour moi, cela ne fait que rendre la fonction plus ambiguë. Merci.

63
meppum

Exemples concrets:

Décorateurs - ils sont généralement génériques, vous ne pouvez donc pas spécifier les arguments à l'avance:

def decorator(old):
    def new(*args, **kwargs):
        # ...
        return old(*args, **kwargs)
    return new

Les endroits où vous voulez faire de la magie avec un nombre inconnu d'arguments de mots clés. L'ORM de Django fait cela, par exemple:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')
37
Cat Plus Plus

Vous voudrez peut-être accepter des arguments nommés presque arbitraires pour une série de raisons - et c'est ce que le **kw le formulaire vous permet de le faire.

La raison la plus courante est de passer les arguments directement à une autre fonction que vous enveloppez (les décorateurs en sont un cas, mais LOIN du seul!) - dans ce cas, **kw desserre le couplage entre le wrapper et le wrappee, car le wrapper n'a pas besoin de connaître ou de se soucier de tous les arguments du wrappee. Voici une autre raison complètement différente:

d = dict(a=1, b=2, c=3, d=4)

si tous les noms devaient être connus à l'avance, alors évidemment cette approche ne pouvait tout simplement pas exister, non? Et btw, le cas échéant, je préfère de beaucoup cette façon de faire un dict dont les clés sont des chaînes littérales à:

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

tout simplement parce que ce dernier est assez ponctué et donc moins lisible.

Lorsqu'aucune des excellentes raisons d'accepter **kwargs s'applique, alors ne l'acceptez pas: c'est aussi simple que cela. IOW, s'il n'y a aucune bonne raison d'autoriser l'appelant à passer des arguments nommés supplémentaires avec des noms arbitraires, ne permettez pas que cela se produise - évitez simplement de mettre un **kw forme à la fin de la signature de la fonction dans l'instruction def.

Quant à en utilisant**kw dans un appel, qui vous permet de rassembler l'ensemble exact d'arguments nommés que vous devez passer, chacun avec des valeurs correspondantes, dans un dict, indépendamment d'un seul point d'appel, puis utilisez ce dict au point d'appel unique. Comparer:

if x: kw['x'] = x
if y: kw['y'] = y
f(**kw)

à:

if x:
  if y:
    f(x=x, y=y)
  else:
    f(x=x)
else:
  if y:
    f(y=y)
  else:
    f()

Même avec seulement deux possibilités (et du genre le plus simple!), Le manque de **kw rend déjà la deuxième option absolument intenable et intolérable - imaginez comment elle se joue quand il y a une demi-douzaine de possibilités, peut-être dans une interaction légèrement plus riche ... sans **kw, la vie serait un enfer absolu dans de telles circonstances!

60
Alex Martelli

Une autre raison pour laquelle vous voudrez peut-être utiliser **kwargs (et *args) est si vous étendez une méthode existante dans une sous-classe. Vous voulez passer tous les arguments existants sur la méthode de la superclasse, mais vous voulez vous assurer que votre classe continue de fonctionner même si la signature change dans une future version:

class MySubclass(Superclass):
    def __init__(self, *args, **kwargs):
        self.myvalue = kwargs.pop('myvalue', None)
        super(MySubclass, self).__init__(*args, **kwargs)
40
Daniel Roseman

Il existe deux cas courants:

Premièrement: vous encapsulez une autre fonction qui prend un certain nombre d'arguments de mots clés, mais vous allez simplement les transmettre:

def my_wrapper(a, b, **kwargs):
    do_something_first(a, b)
    the_real_function(**kwargs)

Deuxièmement: vous êtes prêt à accepter n'importe quel argument de mot clé, par exemple, pour définir des attributs sur un objet:

class OpenEndedObject:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

foo = OpenEndedObject(a=1, foo='bar')
assert foo.a == 1
assert foo.foo == 'bar'
11
Ned Batchelder

**kwargs sont bons si vous ne connaissez pas à l'avance le nom des paramètres. Par exemple, le constructeur dict les utilise pour initialiser les clés du nouveau dictionnaire.

dict(**kwargs) -> new dictionary initialized with the name=value pairs
    in the keyword argument list.  For example:  dict(one=1, two=2)
In [3]: dict(one=1, two=2)
Out[3]: {'one': 1, 'two': 2}
4
Cristian Ciupitu

Voici un exemple, que j'ai utilisé dans CGI Python. J'ai créé une classe qui a pris **kwargs à la __init__ fonction. Cela m'a permis d'émuler le DOM côté serveur avec des classes:

document = Document()
document.add_stylesheet('style.css')
document.append(Div(H1('Imagist\'s Page Title'), id = 'header'))
document.append(Div(id='body'))

Le seul problème est que vous ne pouvez pas faire ce qui suit, car class est un mot-clé Python.

Div(class = 'foo')

La solution consiste à accéder au dictionnaire sous-jacent.

Div(**{'class':'foo'})

Je ne dis pas qu'il s'agit d'une utilisation "correcte" de la fonctionnalité. Ce que je dis, c'est qu'il existe toutes sortes de façons imprévues d'utiliser des fonctionnalités comme celle-ci.

3
Imagist

Et voici un autre exemple typique:

MESSAGE = "Lo and behold! A message {message!r} came from {object_} with data {data!r}."

def proclaim(object_, message, data):
    print(MESSAGE.format(**locals()))
1
ilya n.

Un exemple est l'implémentation python-argument-binders , utilisé comme ceci:

>>> from functools import partial
>>> def f(a, b):
...     return a+b
>>> p = partial(f, 1, 2)
>>> p()
3
>>> p2 = partial(f, 1)
>>> p2(7)
8

Cela vient de functools.partial python docs: partial est 'relativement équivalent' à cet implic:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc
0
Dustin Getz