2009-09-12 12 views
51

Vengo de un fondo en idiomas estáticos. ¿Alguien puede explicar (idealmente a través del ejemplo) las ventajas del mundo real de usar ** kwargs sobre los argumentos nombrados?¿Por qué usar ** kwargs en python? ¿Cuáles son algunas ventajas del mundo real sobre el uso de argumentos con nombre?

Para mí, solo parece hacer que la función llame más ambigua. Gracias.

+1

Piense en varargs en C - a veces no se puede saber cuáles serán sus argumentos. –

+10

Por favor, no use la frase "mundo real" - es argumentativo. Dice que "todos los ejemplos que he visto hasta ahora son artificiales e inútiles, mi aplicación es el mundo real, tus ejemplos son un mundo de fantasía". Por favor, cambie su pregunta para eliminar el "mundo real". –

+67

Solo alguien a quien le guste discutir pensaría que mi pregunta fue argumentativa. – meppum

Respuesta

32

Ejemplos del mundo real:

decoradores - Suelen ser genérico, por lo que no puede especificar los argumentos iniciales:

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

lugares donde quieres hacer magia con una cantidad desconocida de argumentos de palabra clave. ORM de Django hace que, por ejemplo:

Model.objects.filter(foo__lt = 4, bar__iexact = 'bar') 
4

**kwargs son buenos si no conoce 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} 
+1

A menudo se usa si desea pasar muchos argumentos a otra función donde no necesariamente conoce las opciones.Por ejemplo, specialplot (a, ** kwargs) puede pasar las opciones de trazado en kwargs a una función de trazado genérico que acepta esas opciones como parámetros con nombre. – Tristan

+1

Aunque consideraría ese mal estilo, ya que dificulta la introspección. – bayer

0

Un ejemplo está implementando python-argument-binders, que se utiliza como este:

>>> 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 los functools.partial docs Python: parcial es 'relativamente equivalente' a esto 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

aquí, ya que 'keywords' es un diccionario, no .copy() simplemente crea otro puntero? lo que significaría que 'fkeywords' se está agregando al diccionario original. Creo que querrías usar copy.deepcopy() para crear una copia real del diccionario, ¿no? – aeroNotAuto

+0

* 'currying' * es el nombre de la calle para * 'partial argument binding' * – smci

51

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 la única!) - en este caso, **kw afloja el acoplamiento entre wrapper y wrappee, ya que el envoltorio no tiene que conocer o preocuparse por todos los argumentos de la víctima. Aquí es otra, completamente diferente razón:

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

si todos los nombres tenían que ser conocidos de antemano, entonces, evidentemente, este enfoque simplemente no podría existir, ¿verdad? Y por cierto, en su caso, me gusta mucho más esta forma de hacer un diccionario cuyas claves son cadenas literales a:

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

simplemente porque este último es bastante puntuacion-pesado y por lo tanto menos legible.

Cuando no se aplica ninguna de las excelentes razones para aceptar **kwargs, no lo acepte: es así de simple. IOW, si no hay una buena razón para permitir que la persona que llama pase archivos adicionales con nombres arbitrarios, no permita que eso suceda; simplemente evite poner un formulario **kw al final de la firma de la función en la declaración def.

En cuanto a usando**kw en una llamada, que le permite armar el conjunto exacto de argumentos con nombre que debe pasar, cada uno con valores correspondientes, en un diccionario, de forma independiente de un solo punto de llamada, a continuación, utilizar ese dict en el único punto de llamada. Compare:

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 sólo dos posibilidades (! Y de la clase muy simple), la falta de **kw está haciendo aleady la segunda opción absolutamente insostenible e intolerable - sólo imaginar 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 en tales circunstancias!

+0

+1 - sorprendentemente útil para poder hacer esto, pero es algo así como un cambio de paradigma si estás acostumbrado a (por ejemplo) java. – ConcernedOfTunbridgeWells

10

Hay dos casos comunes:

Primero: Usted está envolviendo otra función que toma una serie de argumentos de palabras clave, pero sólo se va a pasar a lo largo:

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

Segundo: Se encuentra dispuesto a aceptar cualquier argumento de palabra clave, por ejemplo, para establecer los atributos de 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' 
31

Otra razón es posible que desee utilizar **kwargs (y 0.123.) es si está extendiendo un método existente en una subclase. Se debe pasar todos los argumentos existente en el método de la superclase, pero quiere asegurarse de que su clase sigue funcionando incluso si los cambios de compás 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) 
+4

+1: Haz esto muy, muy frecuentemente. –

2

He aquí un ejemplo, he utilizado en CGI Python. Creé una clase que tomó **kwargs a la función __init__. Eso me permitió emular el DOM en el lado del servidor con las 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 se puede hacer lo siguiente, porque class es una palabra clave 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 función. Lo que estoy diciendo es que hay todo tipo de formas imprevistas en las que se pueden usar funciones como esta.

1

Y aquí es 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())) 
Cuestiones relacionadas