Maneira correta de usar ** kwargs em Python


454

Qual é a maneira correta de usar **kwargsno Python quando se trata de valores padrão?

kwargsretorna um dicionário, mas qual é a melhor maneira de definir valores padrão ou existe um? Devo acessá-lo como um dicionário? Use a função get?

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

Uma pergunta simples, mas na qual não consigo encontrar bons recursos. As pessoas fazem isso de maneiras diferentes no código que eu já vi e é difícil saber o que usar.

Respostas:


468

Você pode passar um valor padrão get()para chaves que não estão no dicionário:

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

No entanto, se você planeja usar um argumento específico com um valor padrão específico, por que não usar argumentos nomeados em primeiro lugar?

def __init__(self, val2="default value", **kwargs):

16
Gosto de usar argumentos posicionais apenas para argumentos obrigatórios e kwargs para argumentos que podem ou não ser especificados, mas é útil ter um valor padrão. O kwargs é legal porque você pode enviar seus argumentos em qualquer ordem que você escolher. Argumentos posicionais não lhe dão essa liberdade.
8119 Kekoa

95
Você pode passar argumentos nomeados em qualquer ordem que desejar. Você só precisa seguir as posições se não usar os nomes - o que, no caso de kwargs, é necessário. Em vez disso, o uso de argumentos nomeados em oposição a kwargs oferece a liberdade adicional de não usar os nomes - então, no entanto, você deve manter a ordem.
balpha

13
@Kekoa: Você sempre pode enviar argumentos nomeados em qualquer ordem que escolher. Você não precisa usar ** kwargs para obter essa flexibilidade.
315/09 S.Lott

13
O pylint sinaliza como uma forma incorreta de usar o kwargs __init__(). Alguém pode explicar por que essa é uma transgressão digna de fiapos?
21139 hughdbrown


271

Embora a maioria das respostas esteja dizendo que, por exemplo,

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

é o mesmo que"

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

isso não é verdade. No último caso, fpode ser chamado como f(23, 42), enquanto o primeiro caso aceita apenas argumentos nomeados - sem chamadas posicionais. Freqüentemente, você deseja permitir ao chamador a máxima flexibilidade e, portanto, a segunda forma, como a maioria das respostas afirma, é preferível: mas esse nem sempre é o caso. Quando você aceita muitos parâmetros opcionais dos quais normalmente apenas alguns são passados, pode ser uma excelente idéia (evitar acidentes e códigos ilegíveis nos sites de chamadas!) Forçar o uso de argumentos nomeados - threading.Threadé um exemplo. A primeira forma é como você implementa isso no Python 2.

O idioma é tão importante que, no Python 3, agora ele possui uma sintaxe de suporte especial: todos os argumentos após um único *na defassinatura são apenas de palavras-chave, ou seja, não podem ser passados ​​como argumentos posicionais, mas apenas como nomeados. Portanto, no Python 3, você pode codificar o código acima como:

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

De fato, no Python 3 você pode até ter argumentos apenas de palavras-chave que não são opcionais (sem valor padrão).

No entanto, o Python 2 ainda tem longos anos de vida produtiva pela frente, por isso é melhor não esquecer as técnicas e expressões que permitem implementar no Python 2 importantes idéias de design que são diretamente suportadas na linguagem do Python 3!


10
@ Alex Martelli: Não encontrei uma única resposta que afirme que os kwargs sejam idênticos aos argumentos nomeados, muito menos superiores. Mas bom discurso ao Py3k muda, então +1
balpha

1
@ Alex Martelli: muito obrigado pela sua resposta, aprendi que o python 3 permite argumentos de palavras-chave obrigatórios, cuja falta geralmente resultava em arquitetura insatisfatória no meu código e funções.
FloW 04/11/19

78

Sugiro algo parecido com isto

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'}

E então use os valores da maneira que quiser

dictionaryA.update(dictionaryB)adiciona o conteúdo dictionaryBda dictionaryAsubstituir quaisquer chaves duplicadas.


2
Obrigado @AbhinavGupta! Exatamente o que eu estava procurando. Acabei de adicionar for key in options: self.__setattr__(key, options[key])e estou pronto para ir. :)
propjk007

53

Você faria

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

ou

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

Se você usar pop, poderá verificar se há algum valor falso enviado e tomar a ação apropriada (se houver).


2
Você pode esclarecer o que você quer dizer com sugestão para .popajudá-lo a "verificar se há algum valor falso enviado"?
Alan H.

13
@ Alan H .: se sobrar alguma coisa no kwargs depois que todo o popping for feito, você terá valores espúrios.
Vinay Sajip

@VinaySajip: Ok, esse é um ótimo ponto em .pop "vs" .get, mas ainda não vejo por que pop é preferível a argumentos nomeados, além de forçar o chamador a não usar parâmetros posicionais.
MestreLion

1
@MestreLion: depende de quantos argumentos de palavra-chave sua API permite. Não afirmo que minha sugestão seja melhor que argumentos nomeados, mas o Python permite capturar argumentos não nomeados kwargspor um motivo.
Vinay Sajip

Então, apenas verificando. O pop retorna um valor de dicionário se a chave existe e, se não, retorna o default_valuepassado? E remove essa chave depois?
Daniel Möller

42

Usar ** kwargs e valores padrão é fácil. Às vezes, no entanto, você não deve usar ** kwargs em primeiro lugar.

Nesse caso, não estamos realmente fazendo o melhor uso de ** kwargs.

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

O acima é um "por que se preocupar?" declaração. É o mesmo que

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

Quando você usa ** kwargs, significa que uma palavra-chave não é apenas opcional, mas condicional. Existem regras mais complexas do que valores padrão simples.

Quando você usa ** kwargs, geralmente significa algo mais como o seguinte, onde padrões simples não se aplicam.

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

Eu gosto desse pequeno quebra-cabeças! Eu ficava pensando: "Mas você poderia usar apenas obter ou pop com - Oh, eles são co-dependente ..."
trojjer

28

Como **kwargsé usado quando o número de argumentos é desconhecido, por que não fazer isso?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])

sim, este é elegante e poderoso ... não muito certo sobre os colchetes ao redor acceptable_keys_list embora: eu ia fazer isso uma tupla ou uma lista e, em seguida, soltar os suportes na "if"
Mike roedor

Eu ligeiramente modificada deste para os casos quando se espera que todas as chaves: stackoverflow.com/questions/1098549/...
rebelliard

14

Aqui está outra abordagem:

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)

Utilizado muito nos CBVs do Django (por exemplo get_form_kwargs()). Você pode
usar o seguinte

O get_form()método mostra como obter argumentos de palavras-chave extensivamente, adiando para outro método ( get_form_kwargscomo mencionado acima). Ele instancia o formulário da seguinte forma: form_class(**self.get_form_kwargs()).
trojjer

É fácil substituir get_form_kwargs()em uma visualização de subclasse e adicionar / remover kwargs com base em lógica específica. Mas isso é para um tutorial do Django.
trojjer

12

Eu acho que a maneira correta de usar **kwargsem Python quando se trata de valores padrão é usar o método de dicionário setdefault, conforme indicado abaixo:

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

Dessa forma, se um usuário passar 'val' ou 'val2' na palavra-chave args, eles serão usados; caso contrário, os valores padrão que foram definidos serão usados.


11

Você poderia fazer algo assim

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

11

Dando seguimento a @srhegde sugestão de usar 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]

Essa variante é útil quando se espera que a classe tenha todos os itens de nossa acceptablelista.


1
Esse não é um caso de uso para uma compreensão de lista, você deve usar um loop for no seu método init.
55517 ettanyany

5

Se você quiser combinar isso com * args, precisará manter * args e ** kwargs no final da definição.

Assim:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)

1

@AbhinavGupta e @Steef sugeriram o uso update(), que achei muito útil para processar grandes listas de argumentos:

args.update(kwargs)

E se quisermos verificar se o usuário não passou nenhum argumento espúrio / sem suporte? O @VinaySajip apontou que pop()pode ser usado para processar iterativamente a lista de argumentos. Então, quaisquer argumentos restantes são espúrios. Agradável.

Aqui está outra maneira possível de fazer isso, que mantém a sintaxe simples de usar 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_argsé um setcontendo os nomes dos argumentos que não ocorrem nos padrões.


0

Outra solução simples para o processamento de argumentos desconhecidos ou múltiplos pode ser:

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

0

** kwargs oferece a liberdade de adicionar qualquer número de argumentos de palavras-chave. Pode-se ter uma lista de chaves para as quais ele pode definir valores padrão. Mas definir valores padrão para um número indefinido de chaves parece desnecessário. Finalmente, pode ser importante ter as chaves como atributos da instância. Então, eu faria o seguinte:

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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.