Forma correcta de usar * * kwargs en Python


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

kwargs devuelve un diccionario, pero ¿cuál es la mejor manera de establecer valores predeterminados, o hay uno? ¿Debería acceder a él como un diccionario? ¿Usar la función get?

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

Una pregunta simple, pero sobre 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.

 346
Author: SilentGhost, 2009-07-08

13 answers

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

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

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

def __init__(self, val2="default value", **kwargs):
 345
Author: balpha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-08 14:57:23

Mientras que la mayoría de las respuestas están diciendo que, 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 cierto. En este último caso, f puede ser llamado como f(23, 42), mientras que el primer caso acepta argumentos con nombre solo no sin llamadas posicionales. A menudo se desea permitir al llamante la máxima flexibilidad y, por lo tanto, la segunda forma, como la mayoría de las respuestas afirman, es preferible: pero ese no es siempre el caso. Cuando acepta muchos parámetros opcionales, de los cuales normalmente solo unos pocos son pasado, puede ser una excelente idea (evitar accidentes y código ilegible en sus sitios de llamadas!) para forzar el uso de argumentos con nombre {threading.Thread es un ejemplo. El primer formulario es cómo implementarlo en Python 2.

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

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

De hecho, en Python 3 incluso puede tener argumentos solo para palabras clave que no son opcionales (sin 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 que le permiten implementar en Python 2 ideas de diseño importantes que son compatibles directamente con el lenguaje en Python 3!

 237
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-08 15:29:29

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 use los valores de la manera que desee

dictionaryA.update(dictionaryB) añade el contenido de dictionaryB a dictionaryA sobrescribiendo cualquier clave duplicada.

 61
Author: Abhinav Gupta,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-08 15:10:34

Usted haría

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

O

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

Si usa pop, entonces puede verificar si hay algún valor falso enviado, y tomar la acción apropiada (si la hay).

 49
Author: Vinay Sajip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-08 14:49:58

Usar **kwargs y valores predeterminados es fácil. A veces, sin embargo, no debería 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 usas **kwargs, quieres decir que una palabra clave no es solo opcional, sino condicional. Hay reglas más complejas que simples valores predeterminados.

Cuando estás usando * * kwargs, generalmente quieres decir algo más como 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" )
 38
Author: S.Lott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-08 14:55:22

Dado que **kwargs se usa cuando el número de argumentos es desconocido, ¿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])
 23
Author: srhegde,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-29 16:15:24

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)
 12
Author: orokusaki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-13 00:44:27

Creo que la forma correcta de usar **kwargs en 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 han establecido.

 10
Author: user1930143,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-22 20:46:15

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']
 9
Author: Steef,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-08 14:51:57

Siguiendo la sugerencia de @srhegde 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]

Esta variante es útil cuando se espera que la clase tenga todos los elementos en nuestra lista acceptable.

 8
Author: rebelliard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:57

Si desea combinar esto con *args usted tiene que mantener *args y **kwargs al final de la definición.

Así que:

def method(foo, bar=None, *args, **kwargs):
    do_something_with(foo, bar)
    some_other_function(*args, **kwargs)
 2
Author: Jens Timmerman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-19 09:50:37

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

args.update(kwargs)

¿Qué pasa si queremos comprobar que el usuario no ha pasado ningún argumento espurio/no soportado? @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 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 es un set contiene los nombres de los argumentos que no aparecen en los valores predeterminados.

 1
Author: user20160,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-22 02:08:18

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()
 0
Author: tmsss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-25 13:40:36