Bonne façon d'utiliser ** kwargs en Python

454

Quelle est la bonne façon d'utiliser **kwargsen Python en ce qui concerne les valeurs par défaut?

kwargsrenvoie un dictionnaire, mais quelle est la meilleure façon de définir les valeurs par défaut, ou y en a-t-il un? Dois-je simplement y accéder en tant que dictionnaire? Utiliser la fonction get?

class ExampleClass:
    def __init__(self, **kwargs):
        self.val = kwargs['val']
        self.val2 = kwargs.get('val2')

Une question simple, mais sur laquelle je ne trouve pas de bonnes ressources. Les gens le font de différentes manières dans le code que j'ai vu et il est difficile de savoir quoi utiliser.

Kekoa
la source

Réponses:

468

Vous pouvez transmettre une valeur par défaut à get()pour les clés qui ne figurent pas dans le dictionnaire:

self.val2 = kwargs.get('val2',"default value")

Cependant, si vous prévoyez d'utiliser un argument particulier avec une valeur par défaut particulière, pourquoi ne pas utiliser les arguments nommés en premier lieu?

def __init__(self, val2="default value", **kwargs):
balpha
la source
16
J'aime utiliser des arguments positionnels uniquement pour les arguments requis et des kwargs pour les arguments qui peuvent ou non être spécifiés, mais il est utile d'avoir une valeur par défaut. kwargs est agréable car vous pouvez soumettre vos arguments dans l'ordre de votre choix. Les arguments de position ne vous donnent pas cette liberté.
Kekoa
95
Vous pouvez passer des arguments nommés dans l'ordre que vous souhaitez. Vous ne devez respecter les positions que si vous n'utilisez pas les noms - ce que dans le cas des kwargs, vous devez. Au contraire, l'utilisation d'arguments nommés par opposition à kwargs vous donne la liberté supplémentaire de ne pas utiliser les noms - alors, vous devez cependant conserver l'ordre.
balpha
13
@Kekoa: Vous pouvez toujours soumettre des arguments nommés dans l'ordre de votre choix. Vous n'avez pas besoin d'utiliser ** kwargs pour obtenir cette flexibilité.
S.Lott
13
pylint le signale comme une mauvaise forme pour utiliser les kwargs __init__(). Quelqu'un peut-il expliquer pourquoi il s'agit d'une transgression non pelucheuse?
Hughdbrown
271

Alors que la plupart des réponses disent que, par exemple,

def f(**kwargs):
    foo = kwargs.pop('foo')
    bar = kwargs.pop('bar')
    ...etc...

est le même que"

def f(foo=None, bar=None, **kwargs):
    ...etc...

ce n'est pas vrai. Dans ce dernier cas, fpeut être appelé as f(23, 42), tandis que le premier cas accepte uniquement les arguments nommés - pas d'appels de position. Souvent, vous voulez permettre à l'appelant une flexibilité maximale et, par conséquent, la deuxième forme, comme l'affirment la plupart des réponses, est préférable: mais ce n'est pas toujours le cas. Lorsque vous acceptez de nombreux paramètres facultatifs dont généralement seuls quelques-uns sont transmis, il peut être une excellente idée (éviter les accidents et le code illisible sur vos sites d'appels!) De forcer l'utilisation d'arguments nommés - en threading.Threadest un exemple. La première forme est de savoir comment implémenter cela dans Python 2.

L'idiome est si important qu'en Python 3 il a maintenant une syntaxe de support spéciale: chaque argument après un simple *dans la defsignature est uniquement mot-clé, c'est-à-dire, ne peut pas être passé comme argument positionnel, mais seulement comme argument nommé. Donc, en Python 3, vous pouvez coder ce qui précède comme suit:

def f(*, foo=None, bar=None, **kwargs):
    ...etc...

En effet, dans Python 3, vous pouvez même avoir des arguments de mots clés uniquement qui ne sont pas facultatifs (ceux sans valeur par défaut).

Cependant, Python 2 a encore de longues années de vie productive à venir, il est donc préférable de ne pas oublier les techniques et les idiomes qui vous permettent d'implémenter dans Python 2 des idées de conception importantes qui sont directement prises en charge dans le langage Python 3!

Alex Martelli
la source
10
@Alex Martelli: Je n'ai pas trouvé une seule réponse qui prétend que les kwargs soient identiques aux arguments nommés, encore moins supérieurs. Mais le bon discours sur le Py3k change, alors +1
balpha
1
@Alex Martelli: merci beaucoup pour votre réponse, il m'a appris que python 3 autorise les arguments de mots clés obligatoires, ce qui a souvent eu pour résultat une architecture insatisfaisante dans mon code et mes fonctions.
FloW
78

Je suggère quelque chose comme ça

def testFunc( **kwargs ):
    options = {
            'option1' : 'default_value1',
            'option2' : 'default_value2',
            'option3' : 'default_value3', }

    options.update(kwargs)
    print options

testFunc( option1='new_value1', option3='new_value3' )
# {'option2': 'default_value2', 'option3': 'new_value3', 'option1': 'new_value1'}

testFunc( option2='new_value2' )
# {'option1': 'default_value1', 'option3': 'default_value3', 'option2': 'new_value2'}

Et puis utilisez les valeurs comme vous le souhaitez

dictionaryA.update(dictionaryB)ajoute le contenu dictionaryBà dictionaryAécraser les clés en double.

Abhinav Gupta
la source
2
Merci @AbhinavGupta! Exactement ce que je cherchais. Juste ajouté for key in options: self.__setattr__(key, options[key])et je suis prêt à partir. :)
propjk007
53

Tu ferais

self.attribute = kwargs.pop('name', default_value)

ou

self.attribute = kwargs.get('name', default_value)

Si vous utilisez pop, vous pouvez vérifier si des valeurs parasites ont été envoyées et prendre les mesures appropriées (le cas échéant).

Vinay Sajip
la source
2
Pouvez-vous clarifier ce que vous voulez dire en suggérant que .popcela vous aiderait à «vérifier s'il y a des valeurs parasites envoyées»?
Alan H.15
13
@Alan H .: s'il reste quelque chose dans les kwargs après tout le popping, alors vous avez des valeurs erronées.
Vinay Sajip
@VinaySajip: Ok, c'est un excellent point sur .pop "vs" .get, mais je ne vois toujours pas pourquoi la pop est préférable aux arguments nommés, en plus de forcer l'appelant à ne pas utiliser de paramètres de position.
MestreLion
1
@MestreLion: Cela dépend du nombre d'arguments de mots clés autorisés par votre API. Je ne prétends pas que ma suggestion est meilleure que les arguments nommés, mais Python vous permet de capturer des arguments sans nom kwargspour une raison.
Vinay Sajip
Donc, je vérifie. Est-ce que pop retourne une valeur de dictionnaire si la clé existe et sinon, elle retourne la valeur default_valuepassée? Et supprime cette clé par la suite?
Daniel Möller
42

L'utilisation de ** kwargs et des valeurs par défaut est facile. Parfois, cependant, vous ne devriez pas utiliser ** kwargs en premier lieu.

Dans ce cas, nous ne faisons pas vraiment le meilleur usage de ** kwargs.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = kwargs.get('val',"default1")
        self.val2 = kwargs.get('val2',"default2")

Ce qui précède est un "pourquoi s'embêter?" déclaration. C'est la même chose que

class ExampleClass( object ):
    def __init__(self, val="default1", val2="default2"):
        self.val = val
        self.val2 = val2

Lorsque vous utilisez ** kwargs, vous voulez dire qu'un mot clé n'est pas seulement facultatif, mais conditionnel. Il existe des règles plus complexes que de simples valeurs par défaut.

Lorsque vous utilisez ** kwargs, vous voulez généralement dire quelque chose de semblable à ce qui suit, où les valeurs par défaut simples ne s'appliquent pas.

class ExampleClass( object ):
    def __init__(self, **kwargs):
        self.val = "default1"
        self.val2 = "default2"
        if "val" in kwargs:
            self.val = kwargs["val"]
            self.val2 = 2*self.val
        elif "val2" in kwargs:
            self.val2 = kwargs["val2"]
            self.val = self.val2 / 2
        else:
            raise TypeError( "must provide val= or val2= parameter values" )
S.Lott
la source
J'aime ce petit casse-tête! Je n'arrêtais pas de penser, "Mais vous pouvez simplement utiliser get ou pop avec - oh, ils sont co-dépendants ..."
trojjer
28

Puisque **kwargsest utilisé lorsque le nombre d'arguments est inconnu, pourquoi ne pas le faire?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])
srhegde
la source
oui, c'est élégant et puissant ... pas trop sûr des crochets autour de acceptable_keys_list cependant: j'en ferais un tuple ou une liste, puis je déposerais ces crochets dans la déclaration "if"
mike rodent
J'ai légèrement modifié cela pour les cas où toutes les clés sont attendues: stackoverflow.com/questions/1098549/…
rebelliard
14

Voici une autre approche:

def my_func(arg1, arg2, arg3):
    ... so something ...

kwargs = {'arg1': 'Value One', 'arg2': 'Value Two', 'arg3': 'Value Three'}
# Now you can call the function with kwargs like this:

my_func(**kwargs)
orokusaki
la source
Beaucoup utilisé dans les CBV Django (par exemple get_form_kwargs()). ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
trojjer
La get_form()méthode montre comment obtenir des arguments de mots clés de manière extensive en se reportant à une autre méthode ( get_form_kwargscomme mentionné ci-dessus). Il instancie le formulaire comme suit: form_class(**self.get_form_kwargs()).
trojjer
Il est ensuite facile de remplacer get_form_kwargs()dans une vue de sous-classe et d'ajouter / supprimer des kwargs en fonction d'une logique spécifique. Mais c'est pour un tutoriel Django.
trojjer
12

Je pense que la bonne façon d'utiliser **kwargs en Python en ce qui concerne les valeurs par défaut est d'utiliser la méthode du dictionnaire setdefault, comme indiqué ci-dessous:

class ExampleClass:
    def __init__(self, **kwargs):
        kwargs.setdefault('val', value1)
        kwargs.setdefault('val2', value2)

De cette façon, si un utilisateur passe 'val' ou 'val2' dans le mot-clé args, ils seront utilisés; sinon, les valeurs par défaut qui ont été définies seront utilisées.

user1930143
la source
11

Vous pourriez faire quelque chose comme ça

class ExampleClass:
    def __init__(self, **kwargs):
        arguments = {'val':1, 'val2':2}
        arguments.update(kwargs)
        self.val = arguments['val']
        self.val2 = arguments['val2']
Steef
la source
11

Faisant suite à @srhegde suggestion d'utiliser setattr :

class ExampleClass(object):
    __acceptable_keys_list = ['foo', 'bar']

    def __init__(self, **kwargs):
        [self.__setattr__(key, kwargs.get(key)) for key in self.__acceptable_keys_list]

Cette variante est utile lorsque la classe devrait avoir tous les éléments de notre acceptableliste.

rebelliard
la source
1
Ce n'est pas un cas d'utilisation pour une compréhension de liste, vous devez utiliser une boucle for dans votre méthode init.
ettanany
5

Si vous souhaitez combiner cela avec * args, vous devez conserver * args et ** kwargs à la fin de la définition.

Donc:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)
Jens Timmerman
la source
1

@AbhinavGupta et @Steef ont suggéré d'utiliser update()ce que j'ai trouvé très utile pour le traitement de grandes listes d'arguments:

args.update(kwargs)

Que se passe-t-il si nous voulons vérifier que l'utilisateur n'a transmis aucun argument erroné / non pris en charge? @VinaySajip a souligné qu'il pop()peut être utilisé pour traiter de manière itérative la liste des arguments. Ensuite, tous les arguments restants sont faux. Agréable.

Voici une autre façon possible de le faire, qui conserve la syntaxe simple d'utilisation update():

# kwargs = dictionary of user-supplied arguments
# args = dictionary containing default arguments

# Check that user hasn't given spurious arguments
unknown_args = user_args.keys() - default_args.keys()
if unknown_args:
    raise TypeError('Unknown arguments: {}'.format(unknown_args))

# Update args to contain user-supplied arguments
args.update(kwargs)

unknown_argsest un setcontenant les noms des arguments qui n'apparaissent pas dans les valeurs par défaut.

user20160
la source
0

Une autre solution simple pour traiter des arguments inconnus ou multiples peut être:

class ExampleClass(object):

    def __init__(self, x, y, **kwargs):
      self.x = x
      self.y = y
      self.attributes = kwargs

    def SomeFunction(self):
      if 'something' in self.attributes:
        dosomething()
tmsss
la source
0

** kwargs donne la liberté d'ajouter n'importe quel nombre d'arguments de mots clés. On peut avoir une liste de clés pour lesquelles il peut définir des valeurs par défaut. Mais la définition de valeurs par défaut pour un nombre indéfini de clés semble inutile. Enfin, il peut être important d'avoir les clés comme attributs d'instance. Donc, je ferais cela comme suit:

class Person(object):
listed_keys = ['name', 'age']

def __init__(self, **kwargs):
    _dict = {}
    # Set default values for listed keys
    for item in self.listed_keys: 
        _dict[item] = 'default'
    # Update the dictionary with all kwargs
    _dict.update(kwargs)

    # Have the keys of kwargs as instance attributes
    self.__dict__.update(_dict)
user3503692
la source