2009-05-06 21 views
12

Estoy escribiendo un decorador que necesita llamar a otras funciones antes de llamar a la función que está decorando. La función decorada puede tener argumentos posicionales, pero las funciones que el decorador llamará solo pueden aceptar argumentos de palabra clave. ¿Alguien tiene una forma práctica de convertir los argumentos de posición en argumentos de palabra clave?Python convertir args en kwargs

sé que puedo obtener una lista de los nombres de las variables de la función decorada:

>>> def a(one, two=2): 
... pass 

>>> a.func_code.co_varnames 
('one', 'two') 

Pero no puedo encontrar la manera de decir lo que se aprobó en posicionalmente y lo que era como palabra clave.

Mi decorador se ve así:

class mydec(object): 
    def __init__(self, f, *args, **kwargs): 
     self.f = f 

    def __call__(self, *args, **kwargs): 
     hozer(**kwargs) 
     self.f(*args, **kwargs) 

¿Hay una manera que no sea sólo comparando kwargs y co_varnames, añadiendo a todo lo que no kwargs allí, y esperando lo mejor?

+1

¿Por qué lo que necesita saber cuáles eran args posicional? –

+1

Porque necesito convertirlos a kwargs para llamar a la función hozer. Esta función solo acepta kwargs, pero necesita conocer todos los argumentos originalmente llamados. Entonces, dependiendo de si la gente llama a la función decorada con argumentos posicionales o con nombre, la función hozer puede o no obtener todos los datos que necesita. – jkoelker

Respuesta

4

Nota: co_varnames incluirá variables locales y palabras clave. Probablemente esto no importe, ya que zip trunca la secuencia más corta, pero puede dar lugar a mensajes de error confusos si pasa la cantidad incorrecta de argumentos.

Puede evitar esto con func_code.co_varnames[:func_code.co_argcount], pero es mejor utilizar el módulo inspect. es decir:

import inspect 
argnames, varargs, kwargs, defaults = inspect.getargspec(func) 

También puede manejar el caso en que la función define **kwargs o *args (aunque sea sólo para lanzar una excepción cuando se utiliza con el decorador). Si se establecen, el segundo y tercer resultado de getargspec devolverán su nombre de variable; de ​​lo contrario, serán Ninguno.

16

Cualquier arg que se haya pasado posicionalmente se pasará a * args. Y cualquier argumento pasado como una palabra clave se pasará a ** kwargs. Si tiene valores y nombres args posicionales, puede hacerlo:

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args))) 

para convertir a todos en argumentos de palabras clave.

+1

De hecho, 'kwargs.update (zip (myfunc.func_code.co_varnames, args))' es suficiente. 'dict.update' también maneja iterables 2D. – obskyr

5

Bueno, esto puede ser exagerado. Lo escribí para el paquete dectools (en PyPi), por lo que puede obtener actualizaciones allí. Devuelve el diccionario teniendo en cuenta los argumentos posicionales, de palabra clave y por defecto. Hay un conjunto de pruebas en el paquete (test_dict_as_called.py):

def _dict_as_called(function, args, kwargs): 
""" return a dict of all the args and kwargs as the keywords they would 
be received in a real function call. It does not call function. 
""" 

names, args_name, kwargs_name, defaults = inspect.getargspec(function) 

# assign basic args 
params = {} 
if args_name: 
    basic_arg_count = len(names) 
    params.update(zip(names[:], args)) # zip stops at shorter sequence 
    params[args_name] = args[basic_arg_count:] 
else: 
    params.update(zip(names, args))  

# assign kwargs given 
if kwargs_name: 
    params[kwargs_name] = {} 
    for kw, value in kwargs.iteritems(): 
     if kw in names: 
      params[kw] = value 
     else: 
      params[kwargs_name][kw] = value 
else: 
    params.update(kwargs) 

# assign defaults 
if defaults: 
    for pos, value in enumerate(defaults): 
     if names[-len(defaults) + pos] not in params: 
      params[names[-len(defaults) + pos]] = value 

# check we did it correctly. Each param and only params are set 
assert set(params.iterkeys()) == (set(names)|set([args_name])|set([kwargs_name]) 
           )-set([None]) 

return params 
+0

He visto esto copiado y pegado en docenas de proyectos de código abierto. ¡Esto debería moverse a una función a la que las personas puedan llamar más fácilmente! –

8

Si está utilizando Python> = 2,7 inspect.getcallargs lo hace por usted fuera de la caja. Simplemente le pasará la función decorada como primer argumento, y luego el resto de los argumentos exactamente como planea llamarlo. Ejemplo:

>>> def f(p1, p2, k1=None, k2=None, **kwargs): 
...  pass 
>>> from inspect import getcallargs 

tengo la intención de hacer f('p1', 'p2', 'p3', k2='k2', extra='kx1') (k1 en cuenta que se está pasando posicionalmente como p3), así que ...

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1') 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}} 

Si conoce la función decorada no va a utilizar **kwargs , entonces esa clave no aparecerá en el dict, y listo (y estoy asumiendo que no hay *args, ya que eso rompería el requisito de que todo tenga un nombre).Si hace tener **kwargs, como lo he hecho en este ejemplo, y desea incluirlos con el resto de los argumentos con nombre, se necesita una línea más:

>>> call_args.update(call_args.pop('kwargs')) 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'} 
+0

Esta es la forma correcta de hacerlo (si tiene Python 2.7 o posterior, que prácticamente todo el mundo lo hace). –

Cuestiones relacionadas