Forma adecuada de usar ** kwargs en Python

454

¿Cuál es la forma correcta de usar **kwargsen Python cuando se trata de valores predeterminados?

kwargsdevuelve un diccionario, pero ¿cuál es la mejor manera de establecer valores predeterminados, o hay alguno? ¿Debo acceder a él como un diccionario? Utilice la función get?

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

Una pregunta simple, pero en la que no puedo encontrar buenos recursos. La gente lo hace de diferentes maneras en el código que he visto y es difícil saber qué usar.

Kekoa
fuente

Respuestas:

468

Puede pasar un valor predeterminado a las get()claves que no están en el diccionario:

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

Sin embargo, si planea usar un argumento particular con un valor predeterminado particular, ¿por qué no usar argumentos nombrados en primer lugar?

def __init__(self, val2="default value", **kwargs):
balpha
fuente
16
Me gusta usar argumentos posicionales solo para argumentos requeridos, y kwargs para argumentos que pueden o no especificarse, pero es útil tener un valor predeterminado. kwargs es bueno porque puedes enviar tus argumentos en el orden que elijas. Los argumentos posicionales no te dan esa libertad.
Kekoa
95
Puede pasar argumentos con nombre en el orden que desee. Solo necesita adherirse a las posiciones si no usa los nombres, que en el caso de los kwargs, debe hacerlo. Por el contrario, el uso de argumentos con nombre en lugar de kwargs le da la libertad adicional de no usar los nombres; luego, sin embargo, debe mantener el orden.
balpha
13
@Kekoa: siempre puede enviar argumentos con nombre en el orden que elija. No tiene que usar ** kwargs para obtener esta flexibilidad.
S.Lott
13
pylint lo marca como una mala forma para usar kwargs __init__(). ¿Alguien puede explicar por qué esta es una transgresión digna de pelusa?
hughdbrown
271

Si bien la mayoría de las respuestas dicen eso, por ejemplo,

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

es lo mismo que"

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

esto no es verdad. En este último caso, fpuede ser llamado como f(23, 42), mientras que el primer caso acepta argumentos con nombre sólo - no hay llamadas posicionales. A menudo, desea permitir que la persona que llama tenga la máxima flexibilidad y, por lo tanto, es preferible la segunda forma, como afirman la mayoría de las respuestas: pero ese no es siempre el caso. Cuando acepta muchos parámetros opcionales, de los cuales generalmente solo se pasan unos pocos, puede ser una excelente idea (¡evitar accidentes y códigos ilegibles en sus sitios de llamadas!) Forzar el uso de argumentos con nombre, threading.Threades un ejemplo. La primera forma es cómo implementar eso en Python 2.

El idioma es tan importante que en Python 3 ahora tiene una sintaxis de soporte especial: cada argumento después de un solo *en la deffirma es solo de palabra clave, es decir, no se puede pasar como un argumento posicional, sino solo como uno con nombre. Entonces, en Python 3 podría codificar lo anterior como:

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

De hecho, en Python 3 incluso puede tener argumentos de solo palabras clave que no son opcionales (los que no tienen un valor predeterminado).

Sin embargo, Python 2 todavía tiene largos años de vida productiva por delante, por lo que es mejor no olvidar las técnicas y expresiones idiomáticas que le permiten implementar en Python 2 ideas de diseño importantes que se admiten directamente en el lenguaje de Python 3.

Alex Martelli
fuente
10
@Alex Martelli: No he encontrado una sola respuesta que afirme que los kwargs sean idénticos a los argumentos nombrados, y mucho menos superiores. Pero buen discurso sobre los cambios de Py3k, entonces +1
balpha
1
@Alex Martelli: muchas gracias por su respuesta, me enteré de que Python 3 permite argumentos de palabra clave obligatorios, lo que a menudo no resultaba en una arquitectura insatisfactoria en mi código y funciones.
FloW
78

Sugiero algo como esto

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

Y luego usa los valores como quieras

dictionaryA.update(dictionaryB)agrega el contenido de dictionaryBa dictionaryAsobrescribir los duplicados de las llaves.

Abhinav Gupta
fuente
2
Gracias @AbhinavGupta! Exactamente lo que estaba buscando. Acabo de agregar for key in options: self.__setattr__(key, options[key])y estoy listo para ir. :)
propjk007
53

Tu harias

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

o

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

Si lo usa pop, puede verificar si hay valores falsos enviados y tomar las medidas apropiadas (si corresponde).

Vinay Sajip
fuente
2
¿Puede aclarar lo que quiere decir sugiriendo que .poplo ayudaría a "verificar si hay valores falsos enviados"?
Alan H.
13
@ Alan H .: si queda algo en los kwargs después de hacer todo el estallido, entonces tienes valores espurios.
Vinay Sajip
@VinaySajip: Ok, ese es un gran punto en .pop "vs" .get, pero todavía no veo por qué es preferible pop sobre los argumentos nombrados, además de obligar a la persona que llama a no usar parámetros posicionales.
MestreLion
1
@MestreLion: depende de cuántos argumentos de palabras clave permita su API. No afirmo que mi sugerencia sea mejor que los argumentos con nombre, pero Python le permite capturar argumentos sin nombre kwargspor una razón.
Vinay Sajip
Entonces, solo comprobando. ¿Pop devuelve un valor de diccionario si la clave existe y si no, devuelve la default_valuepasada? ¿Y quita esa llave después?
Daniel Möller
42

Usar ** kwargs y valores predeterminados es fácil. A veces, sin embargo, no deberías usar ** kwargs en primer lugar.

En este caso, realmente no estamos haciendo el mejor uso de ** kwargs.

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

Lo anterior es un "¿por qué molestarse?" declaración. Es lo mismo que

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

Cuando usa ** kwargs, quiere decir que una palabra clave no es solo opcional, sino condicional. Hay reglas más complejas que los valores predeterminados simples.

Cuando usa ** kwargs, generalmente quiere decir algo más parecido a lo siguiente, donde los valores predeterminados simples no se aplican.

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
fuente
¡Me gusta ese pequeño acertijo! Seguí pensando, "Pero podrías usar get o pop con - oh, son co-dependientes ..."
trojjer
28

Como **kwargsse usa cuando se desconoce el número de argumentos, ¿por qué no hacer esto?

class Exampleclass(object):
  def __init__(self, **kwargs):
    for k in kwargs.keys():
       if k in [acceptable_keys_list]:
          self.__setattr__(k, kwargs[k])
srhegde
fuente
sí, esto es elegante y poderoso ... aunque no estoy muy seguro acerca de los corchetes alrededor de aceptable_keys_list: haría esto una tupla o una lista y luego dejaría esos corchetes en la declaración "if"
mike rodent
Modifiqué
14

Aquí hay otro enfoque:

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
fuente
Se usa mucho en Django CBV (por ejemplo get_form_kwargs()). ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/…
trojjer
El get_form()método muestra cómo obtener argumentos de palabras clave ampliamente difiriendo a otro método ( get_form_kwargscomo se mencionó anteriormente). Se crea una instancia del formulario como sigue: form_class(**self.get_form_kwargs()).
trojjer
Entonces es fácil anular get_form_kwargs()en una vista de subclase y agregar / eliminar kwargs según una lógica específica. Pero eso es para un tutorial de Django.
trojjer
12

Creo que la forma correcta de usar **kwargsen Python cuando se trata de valores predeterminados es usar el método de diccionario setdefault, como se indica a continuación:

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

De esta manera, si un usuario pasa 'val' o 'val2' en la palabra clave args, se usarán; de lo contrario, se usarán los valores predeterminados que se hayan establecido.

usuario1930143
fuente
11

Podrías hacer algo como esto

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

El seguimiento de @srhegde sugerencia de utilizar 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]

Esta variante es útil cuando se espera que la clase tenga todos los elementos de nuestra acceptablelista.

rebelde
fuente
1
Ese no es un caso de uso para una comprensión de la lista, debe usar un bucle for en su método init.
ettanany
5

Si desea combinar esto con * args, debe mantener * args y ** kwargs al final de la definición.

Entonces:

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

@AbhinavGupta y @Steef sugirieron usar update(), lo que me pareció muy útil para procesar grandes listas de argumentos:

args.update(kwargs)

¿Qué sucede si queremos verificar que el usuario no haya pasado ningún argumento falso / no admitido? @VinaySajip señaló que pop()se puede usar para procesar iterativamente la lista de argumentos. Entonces, cualquier argumento sobrante es falso. Agradable.

Aquí hay otra forma posible de hacer esto, que mantiene la sintaxis simple de uso 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_argses un que setcontiene los nombres de los argumentos que no aparecen en los valores predeterminados.

usuario20160
fuente
0

Otra solución simple para procesar argumentos desconocidos o múltiples puede 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()
tmsss
fuente
0

** kwargs le da la libertad de agregar cualquier número de argumentos de palabras clave. Uno puede tener una lista de claves para las cuales puede establecer valores predeterminados. Pero establecer valores predeterminados para un número indefinido de teclas parece innecesario. Finalmente, puede ser importante tener las claves como atributos de instancia. Entonces, haría esto de la siguiente manera:

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
fuente