2009-09-11 9 views
18

Estoy soñando con un método de Python con argumentos explícitos de palabras clave:a encontrar las palabras clave de los argumentos de hecho pasan a un método de Python

def func(a=None, b=None, c=None): 
    for arg, val in magic_arg_dict.items(): # Where do I get the magic? 
     print '%s: %s' % (arg, val) 

quiero conseguir un diccionario de sólo aquellos argumentos la persona que llama en realidad pasó en las método, al igual que **kwargs, pero no quiero que la persona que llama pueda pasar ningún argumento aleatorio viejo, a diferencia de **kwargs.

>>> func(b=2) 
b: 2 
>>> func(a=3, c=5) 
a: 3 
c: 5 

Así que: ¿hay tal hechizo? En mi caso, soy capaz de comparar cada argumento con su valor predeterminado para encontrar los que son diferentes, pero esto es poco elegante y se vuelve tedioso cuando tienes nueve argumentos. Para los puntos de bonificación, proporcionar un encantamiento que me puede decir, incluso cuando la persona que llama pasa en un argumento de palabra clave asignada su valor por defecto:

>>> func(a=None) 
a: None 

Tricksy!

Editar: La firma de función (léxica) debe permanecer intacta. Es parte de una API pública, y el valor principal de las palabras clave explícitas radica en su valor documental. Solo para hacer las cosas interesantes. :)

+0

Esta pregunta tiene un título muy similar, pero no estoy del todo seguro de si es un duplicado: http://stackoverflow.com/questions/196960/can-you-list-the-keyword-arguments-a-python -función-recibe –

+0

@AndersonGreen. La pregunta que mencionas no tiene nada que ver con esto. Pregunta acerca de cómo filtrar un diccionario al pasar con ** notación a un método que no aceptaría todas las palabras clave. –

+0

Esta es una gran pregunta. –

Respuesta

23

Me inspiré en la bondad decorador de objetos perdidos teoría, y después de jugar alrededor con él por un rato se le ocurrió esto:

def actual_kwargs(): 
    """ 
    Decorator that provides the wrapped function with an attribute 'actual_kwargs' 
    containing just those keyword arguments actually passed in to the function. 
    """ 
    def decorator(function): 
     def inner(*args, **kwargs): 
      inner.actual_kwargs = kwargs 
      return function(*args, **kwargs) 
     return inner 
    return decorator 


if __name__ == "__main__": 

    @actual_kwargs() 
    def func(msg, a=None, b=False, c='', d=0): 
     print msg 
     for arg, val in sorted(func.actual_kwargs.iteritems()): 
      print ' %s: %s' % (arg, val) 

    func("I'm only passing a", a='a') 
    func("Here's b and c", b=True, c='c') 
    func("All defaults", a=None, b=False, c='', d=0) 
    func("Nothin'") 
    try: 
     func("Invalid kwarg", e="bogon") 
    except TypeError, err: 
     print 'Invalid kwarg\n %s' % err 

que imprime esto:

 
I'm only passing a 
    a: a 
Here's b and c 
    b: True 
    c: c 
All defaults 
    a: None 
    b: False 
    c: 
    d: 0 
Nothin' 
Invalid kwarg 
    func() got an unexpected keyword argument 'e' 

estoy feliz con este. Un enfoque más flexible es pasar el nombre del atributo que desea utilizar al decorador, en lugar de codificarlo en 'real_kwargs', pero este es el enfoque más simple que ilustra la solución.

Mmm, Python es sabroso.

+0

Heh, bastante agradable. Me gusta este método también. ¡Gracias! –

5

Una posibilidad:

def f(**kw): 
    acceptable_names = set('a', 'b', 'c') 
    if not (set(kw) <= acceptable_names): 
    raise WhateverYouWantException(whatever) 
    ...proceed... 

OIA, es muy fácil de comprobar que la pasó en nombres está dentro del conjunto aceptable y de lo contrario elevar todo lo que querría Python para elevar (TypeError, supongo ;-). Bastante fácil de convertirse en un decorador, por cierto.

Otra posibilidad:

_sentinel = object(): 
def f(a=_sentinel, b=_sentinel, c=_sentinel): 
    ...proceed with checks `is _sentinel`... 

al hacer un objeto único _sentinel se quita el riesgo de que la persona que llama puede ser accidentalmente pasar None (u otros valores por defecto no únicos la persona que llama, posiblemente, podrían pasar). Esto es todo object() es bueno para, por cierto: un centinela único extremadamente ligero que no se puede confundir accidentalmente con ningún otro objeto (cuando se comprueba con el operador is).

Cualquiera de las soluciones es preferible para problemas levemente diferentes.

2

¿Qué le parece usar un decorador para validar los kwargs entrantes?

def validate_kwargs(*keys): 
    def entangle(f): 
     def inner(*args, **kwargs): 
      for key in kwargs: 
       if not key in keys: 
        raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys)) 
      return f(*args, **kwargs) 
     return inner 
    return entangle 

### 

@validate_kwargs('a', 'b', 'c') 
def func(**kwargs): 
    for arg,val in kwargs.items(): 
     print arg, "->", val 

func(b=2) 
print '----' 
func(a=3, c=5) 
print '----' 
func(d='not gonna work') 

da esta salida:

b -> 2 
---- 
a -> 3 
c -> 5 
---- 
Traceback (most recent call last): 
    File "kwargs.py", line 20, in <module> 
    func(d='not gonna work') 
    File "kwargs.py", line 6, in inner 
    raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys)) 
ValueError: Received bad kwarg: 'd', expected: ('a', 'b', 'c') 
0

Probablemente hay mejores maneras de hacer esto, pero aquí es mi opinión:

def CompareArgs(argdict, **kwargs): 
    if not set(argdict.keys()) <= set(kwargs.keys()): 
     # not <= may seem weird, but comparing sets sometimes gives weird results. 
     # set1 <= set2 means that all items in set 1 are present in set 2 
     raise ValueError("invalid args") 

def foo(**kwargs): 
    # we declare foo's "standard" args to be a, b, c 
    CompareArgs(kwargs, a=None, b=None, c=None) 
    print "Inside foo" 


if __name__ == "__main__": 
    foo(a=1) 
    foo(a=1, b=3) 
    foo(a=1, b=3, c=5) 
    foo(c=10) 
    foo(bar=6) 

y su salida:

 
Inside foo 
Inside foo 
Inside foo 
Inside foo 
Traceback (most recent call last): 
    File "a.py", line 18, in 
    foo(bar=6) 
    File "a.py", line 9, in foo 
    CompareArgs(kwargs, a=None, b=None, c=None) 
    File "a.py", line 5, in CompareArgs 
    raise ValueError("invalid args") 
ValueError: invalid args 

Este probablemente podría convertirse en un decorador, pero m y los decoradores necesitan trabajo. A la izquierda como un ejercicio para el lector: P

0

Quizás aparezca un error si pasan cualquier * args?

def func(*args, **kwargs): 
    if args: 
    raise TypeError("no positional args allowed") 
    arg1 = kwargs.pop("arg1", "default") 
    if kwargs: 
    raise TypeError("unknown args " + str(kwargs.keys())) 

Sería sencillo al factor en tomar una lista de nombres de variables o una función de análisis genérico de usar. No sería demasiado difícil de hacer esto en un decorador (Python 3.1), también:

def OnlyKwargs(func): 
    allowed = func.__code__.co_varnames 
    def wrap(*args, **kwargs): 
    assert not args 
    # or whatever logic you need wrt required args 
    assert sorted(allowed) == sorted(kwargs) 
    return func(**kwargs) 

Nota: no estoy seguro de lo bien que esto funcionaría en torno a las funciones ya envueltos o funciones que tienen *args o **kwargs ya.

9

Esta es la forma más fácil y sencilla:

def func(a=None, b=None, c=None): 
    args = locals().copy() 
    print args 

func(2, "egg") 

Esto da la salida: {'a': 2, 'c': None, 'b': 'egg'}. El motivo args debería ser una copia del diccionario locals es que los diccionarios son mutables, por lo que si creó cualquier variable local en esta función args contendría todas las variables locales y sus valores, no solo los argumentos.

Más documentación sobre la función incorporada localshere.

+0

+1 He aprendido muchas cosas buenas sobre Python de SO. –

+3

Agradable, y cercano, pero c no debería estar ahí. No me dice cuáles pasaste. –

0

La magia no es la respuesta:

def funky(a=None, b=None, c=None): 
    for name, value in [('a', a), ('b', b), ('c', c)]: 
     print name, value 
+1

... ¿qué (estás tratando de decir)? a.k.a. Hombre, I Do not Get It – n611x007

+0

No hagas nada mágico. Es simplemente confuso. –

1

Esto es más fácil de lograr con una sola instancia de un objeto de centinela:

# Top of module, does not need to be exposed in __all__ 
missing = {} 

# Function prototype 
def myFunc(a = missing, b = missing, c = missing): 
    if a is not missing: 
     # User specified argument a 
    if b is missing: 
     # User did not specify argument b 

Lo bueno de este enfoque es que, ya que estamos usando el operador "es", el que llama puede pasar un dict vacío como el valor del argumento, y aún seguiremos aprendiendo que no fue su intención pasarlo. También evitamos decoradores desagradables de esta manera, y mantenemos nuestro código un poco más limpio.

+0

Dado que la falta no está realmente destinada a ser un dict, ¿por qué no utilizar "object()"? Esa es una instancia única (que es el punto) de objeto. No utilizable (sin trucos) para nada. Ah, y falta al final del módulo, por lo que no se puede exportar, si quiere estar seguro. –

+1

No puede eliminarlo; las funciones necesitan estar en el ámbito global ya que se están refiriendo a él desde allí. Podría prefijarlo con un guión bajo si está realmente preocupado. Objeto o dict funcionaría; No estoy al tanto de ningún beneficio estricto de ninguno, que no sea {} es más escueto que object(). –

Cuestiones relacionadas