¿Por qué usar **kwargs en python? ¿Cuáles son algunas ventajas del mundo real sobre el uso de argumentos con nombre?


Vengo de un fondo en lenguajes estáticos. ¿Puede alguien explicar (idealmente a través del ejemplo) las ventajas del mundo real de usar **kwargs sobre argumentos nombrados?

Para mí solo parece hacer que la llamada a la función sea más ambigua. Gracias.

Author: binary lobster, 2009-09-12

8 answers

Ejemplos del mundo real:

Decoradores-por lo general son genéricos, por lo que no puede especificar los argumentos por adelantado:

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

Lugares donde desea hacer magia con un número desconocido de argumentos de palabras clave. El Dj de Django hace eso, por ejemplo:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar')
 36
Author: Cat Plus Plus,
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-09-12 18:50:27

Es posible que desee aceptar argumentos con nombres casi arbitrarios por una serie de razones -- y eso es lo que el formulario **kw le permite hacer.

La razón más común es pasar los argumentos directamente a alguna otra función que esté envolviendo (los decoradores son un caso de esto, pero lejos de ser el único!) {en este caso, **kw afloja el acoplamiento entre wrapper y wrappee, ya que el wrapper no tiene que conocer o preocuparse por todos los argumentos del wrappee. Aquí hay otro, completamente diferente razón:

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

Si todos los nombres tuvieran que ser conocidos de antemano, entonces obviamente este enfoque no podría existir, ¿verdad? Y por cierto, cuando es aplicable, prefiero esta forma de hacer un dictado cuyas claves son cadenas literales a:

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

Simplemente porque este último es bastante pesado en puntuación y, por lo tanto, menos legible.

Cuando ninguna de las excelentes razones para aceptar **kwargs se aplica, entonces no lo acepte: es tan simple como eso. IOW, si no hay una buena razón para permitir el caller para pasar args con nombre extra con nombres arbitrarios, no permita que eso suceda avoid simplemente evite poner un formulario **kw al final de la firma de la función en la instrucción def.

Como para usando **kw en una llamada, que le permite reunir el conjunto exacto de argumentos con nombre que debe pasar, cada uno con los valores correspondientes, en un dict, independientemente de un solo punto de llamada, y luego usar ese dict en el único punto de llamada. Comparar:

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

A:

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

Incluso con solo dos posibilidades (¡y del tipo más simple!), la falta de **kw está haciendo que la segunda opción sea absolutamente insostenible e intolerable imagine imagínese cómo se desarrolla cuando hay media docena de posibilidades, posiblemente en una interacción un poco más rica... ¡sin **kw, la vida sería un infierno absoluto bajo tales circunstancias!

 57
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
2012-07-03 16:53:59

Otra razón por la que podrías querer usar **kwargs (y *args) es si estás extendiendo un método existente en una subclase. Desea pasar todos los argumentos existentes al método de la superclase, pero desea asegurarse de que su clase siga funcionando incluso si la firma cambia en una versión futura:

class MySubclass(Superclass):
    def __init__(self, *args, **kwargs):
        self.myvalue = kwargs.pop('myvalue', None)
        super(MySubclass, self).__init__(*args, **kwargs)
 36
Author: Daniel Roseman,
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-09-12 19:15:33

Hay dos casos comunes:

Primero: Está envolviendo otra función que toma un número de argumentos de palabras clave, pero solo los va a pasar:

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

Segundo: Está dispuesto a aceptar cualquier argumento de palabra clave, por ejemplo, para establecer atributos en un objeto:

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'
 10
Author: Ned Batchelder,
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-09-12 18:56:22

**kwargs son buenos si no sabes de antemano el nombre de los parámetros. Por ejemplo, el constructor dict los usa para inicializar las claves del nuevo diccionario.

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
Author: Cristian Ciupitu,
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-09-12 18:45:42

Aquí hay un ejemplo, que usé en Python CGI. Creé una clase que llevó **kwargs a la función __init__. Eso me permitió emular el DOM en el lado del servidor con clases:

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

El único problema es que no puedes hacer lo siguiente, porque class es una palabra clave de Python.

Div(class = 'foo')

La solución es acceder al diccionario subyacente.

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

No estoy diciendo que este sea un uso "correcto" de la característica. Lo que estoy diciendo es que hay todo tipo de formas imprevistas en qué características como esta se pueden utilizar.

 2
Author: Imagist,
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-09-12 21:56:05

Y aquí hay otro ejemplo típico:

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
Author: ilya n.,
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-09-13 05:30:16

Un ejemplo es implementar python-argument-binders , usado así:

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

Esto es de las funtools.partial python docs: partial es 'relativamente equivalente' a este impl:

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
Author: Dustin Getz,
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:46:52