2011-06-01 12 views
12

Quiero hacer una función que ser un decorador a otra función que se imprimirá detalles de la llamada función - nombres de los parámetros y valores eficaces. Mi implementación actual es esto.decorador para imprimir detalles de la llamada función - nombres de los parámetros y valores eficaces

def describeFuncCall(func): 
    '''Decorator to print function call details - parameters names and effective values''' 
    def wrapper(*func_args, **func_kwargs): 
     print 'func_code.co_varnames =', func.func_code.co_varnames 
     print 'func_code.co_argcount =', func.func_code.co_argcount 
     print 'func_args =', func_args 
     print 'func_kwargs =', func_kwargs 
     params = [] 
     for argNo in range(func.func_code.co_argcount): 
      argName = func.func_code.co_varnames[argNo] 
      argValue = func_args[argNo] if argNo < len(func_args) else func.func_defaults[argNo - func.func_code.co_argcount] 
      params.append((argName, argValue)) 
     for argName, argValue in func_kwargs.items(): 
      params.append((argName, argValue)) 
     params = [ argName + ' = ' + repr(argValue) for argName, argValue in params] 
     print(func.__name__ + ' (' + ', '.join(params) + ')') 
     return func(*func_args, **func_kwargs) 
    return wrapper 


@describeFuncCall 
def test(a, b = 4, c = 'blah-blah', *args, **kwargs): 
    pass 


test(1) 
#test(1, 3) 
#test(1, d = 5) 
test(1, 2, 3, 4, 5, d = 6, g = 12.9) 

Un poco funciona, pero con algunos errores:

Para llamada

test(1, 2, 3, 4, 5, d = 6, g = 12.9)

imprime

test (a = 1, b = 2, c = 3, d = 6, g = 12.9).

El resultado esperado es

test (a = 1, b = 2, c = 3, args = [4, 5], kwargs = {'d': 6, 'g': 12.9})

me quedé atrapado aquí. ¿Me puede ayudar a encontrar la solución correcta?

+0

Si esto es para la depuración, ¿no sería útil también mostrar el valor de retorno de la función? – Curt

+0

@Curt sí, claro – warvariuc

Respuesta

11

este momento es un poco desordenado. He modificado algún código de http://wiki.python.org/moin/PythonDecoratorLibrary#Easy_Dump_of_Function_Arguments

def dump_args(func): 
    "This decorator dumps out the arguments passed to a function before calling it" 
    argnames = func.func_code.co_varnames[:func.func_code.co_argcount] 
    fname = func.func_name 
    def echo_func(*args,**kwargs): 
     print fname, "(", ', '.join(
      '%s=%r' % entry 
      for entry in zip(argnames,args[:len(argnames)])+[("args",list(args[len(argnames):]))]+[("kwargs",kwargs)]) +")" 
    return echo_func 

@dump_args 
def test(a, b = 4, c = 'blah-blah', *args, **kwargs): 
    pass 

de ensayo (1, 2, 3, 4, 5, d = 6, g = 12,9)

salida:

prueba (a = 1, b = 2, c = 3, args = [4, 5], kwargs = { 'd': 6 'g': 12,9})

+0

Tiene algunos errores que me fijo, pero no tiene en cuenta los valores por defecto: 'de prueba (1)': de prueba (a = 1, args =(), kwargs = {}) – warvariuc

6

versión de trabajo con los valores predeterminados:

def dumpArgs(func): 
    '''Decorator to print function call details - parameters names and effective values''' 
    def wrapper(*func_args, **func_kwargs): 
     arg_names = func.func_code.co_varnames[:func.func_code.co_argcount] 
     args = func_args[:len(arg_names)] 
     defaults = func.func_defaults or() 
     args = args + defaults[len(defaults) - (func.func_code.co_argcount - len(args)):] 
     params = zip(arg_names, args) 
     args = func_args[len(arg_names):] 
     if args: params.append(('args', args)) 
     if func_kwargs: params.append(('kwargs', func_kwargs)) 
     print func.func_name + ' (' + ', '.join('%s = %r' % p for p in params) + ')' 
     return func(*func_args, **func_kwargs) 
    return wrapper 

@dumpArgs 
def test(a, b = 4, c = 'blah-blah', *args, **kwargs): 
    pass 

test(1) 
test(1, 3) 
test(1, d = 5) 
test(1, 2, 3, 4, 5, d = 6, g = 12.9) 

Resultado:

>>> test ( a = 1, b = 4, c = 'blah-blah') 
test ( a = 1, b = 3, c = 'blah-blah') 
test ( a = 1, b = 4, c = 'blah-blah', kwargs = {'d': 5}) 
test ( a = 1, b = 2, c = 3, args = (4, 5), kwargs = {'d': 6, 'g': 12.9}) 
+2

Esta pregunta es antigua pero sigue siendo útil. Debe tener en cuenta que la biblioteca principal expone una forma estándar de realizar esta asignación desde '* args' y' ** kwargs' a nombres de parámetros reales: [Signature.bind] (https://docs.python.org/3/library /inspect.html#inspect.Signature.bind) – Demurgos

4

@ respuesta de warvariuc, actualizado a Python 3:

def dumpArgs(func): 
    '''Decorator to print function call details - parameters names and effective values''' 
    def wrapper(*func_args, **func_kwargs): 
     arg_names = func.__code__.co_varnames[:func.__code__.co_argcount] 
     args = func_args[:len(arg_names)] 
     defaults = func.__defaults__ or() 
     args = args + defaults[len(defaults) - (func.__code__.co_argcount - len(args)):] 
     params = list(zip(arg_names, args)) 
     args = func_args[len(arg_names):] 
     if args: params.append(('args', args)) 
     if func_kwargs: params.append(('kwargs', func_kwargs)) 
     print(func.__name__ + ' (' + ', '.join('%s = %r' % p for p in params) + ')') 
     return func(*func_args, **func_kwargs) 
    return wrapper 

@dumpArgs 
def test(a, b = 4, c = 'blah-blah', *args, **kwargs): 
    pass 

test(1) 
test(1, 3) 
test(1, d = 5) 
test(1, 2, 3, 4, 5, d = 6, g = 12.9) 
+0

Gracias a @Ffisegydd por la parte 'list (zip (...))'. – aliteralmind

4

Así es como lo solucioné en Python 3, basado en aliteralmind's respuesta, puse más limpia (PEP8) si se puede decir así. La mayoría de la inspiración para la limpieza vino de la (actual) accepted answer por Robert King.

Código:

import logging 


def log_function_entry_and_exit(decorated_function): 
    """ 
    Function decorator logging entry + exit and parameters of functions. 

    Entry and exit as logging.info, parameters as logging.DEBUG. 
    """ 
    from functools import wraps 

    @wraps(decorated_function) 
    def wrapper(*dec_fn_args, **dec_fn_kwargs): 
     # Log function entry 
     func_name = decorated_function.__name__ 
     log = logging.getLogger(func_name) 
     log.info('Entering {}()...'.format(func_name)) 

     # get function params (args and kwargs) 
     arg_names = decorated_function.__code__.co_varnames 
     params = dict(
      args=dict(zip(arg_names, dec_fn_args)), 
      kwargs=dec_fn_kwargs) 

     log.debug(
      "\t" + ', '.join([ 
       '{}={}'.format(str(k), repr(v)) for k, v in params.items()])) 
     # Execute wrapped (decorated) function: 
     out = decorated_function(*dec_fn_args, **dec_fn_kwargs) 
     log.info('Done running {}()!'.format(func_name)) 

     return out 
    return wrapper 


@log_function_entry_and_exit 
def func1(a, b, c): 
    print("\n\ty'elo2!\n") 
@log_function_entry_and_exit 
def a(x, y, z): 
    print("\n\ty'elo!\n") 

LOG_FORMAT = '[{}] !%(levelname)s! %(funcName)s: %(message)s'.format(
    _get_current_time_string(just_time_string=True)) 
logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG) 

a(x=1, y="b", z={'c': 2}) 
func1(2, b="y", c={'z': 4}) 
func1(2, "y", {'z': 4}) 

Salida:

In [6]: a(x=1, y="b", z={'c': 2}) 
    ...: func1(2, b="y", c={'z': 4}) 
    ...: func1(2, "y", {'z': 4}) 
    ...: 
[2016.09.22 - 17:31:48] !INFO! wrapper: Entering a()... 
[2016.09.22 - 17:31:48] !DEBUG! wrapper:  kwargs={'x': 1, 'z': {'c': 2}, 'y': 'b'}, args={} 

     y'elo! 

[2016.09.22 - 17:31:48] !INFO! wrapper: Done running a()! 
[2016.09.22 - 17:31:48] !INFO! wrapper: Entering func1()... 
[2016.09.22 - 17:31:48] !DEBUG! wrapper:  kwargs={'c': {'z': 4}, 'b': 'y'}, args={'a': 2} 

     y'elo2! 

[2016.09.22 - 17:31:48] !INFO! wrapper: Done running func1()! 
[2016.09.22 - 17:31:48] !INFO! wrapper: Entering func1()... 
[2016.09.22 - 17:31:48] !DEBUG! wrapper:  kwargs={}, args={'c': {'z': 4}, 'a': 2, 'b': 'y'} 

     y'elo2! 

[2016.09.22 - 17:31:48] !INFO! wrapper: Done running func1()! 

Nota: La cadena wrapper en la salida representa el nombre de función de lo que hace la llamada logging.X() mensaje.

Ejemplo de uso:

In [1]: from meh import execute_os_command 

In [2]: from meh import LOG_FORMAT 

In [3]: import logging 

In [4]:  logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) 
    ...: 
    ...:  logging.info("Entered script...\n") 
    ...: 
    ...:  result = execute_os_command(cmd=["echo", "trololol"]) 
    ...:  print("\n{}\n".format(result)) 
    ...:  execute_os_command(cmd=["echo", "trololol"], dry_run=True) 
    ...: 
    ...:  logging.info("Exiting script...\n") 
    ...: 
[2016.09.22 - 17:42:19] !INFO! <module>: Entered script... 

[2016.09.22 - 17:42:19] !INFO! wrapper: Entering execute_os_command()... 
[2016.09.22 - 17:42:19] !INFO! execute_os_command: Executing: 
[2016.09.22 - 17:42:19] !INFO! execute_os_command:  echo trololol 
[2016.09.22 - 17:42:19] !INFO! execute_os_command: Waiting for above command to finish execution... 
[2016.09.22 - 17:42:19] !INFO! wrapper: Done running execute_os_command()! 

{'stderr': '', 'stdout': 'trololol\n', 'command': ['echo', 'trololol'], 'returncode': 0, 'stdin': None, 'timedout': False} 

[2016.09.22 - 17:42:19] !INFO! wrapper: Entering execute_os_command()... 
[2016.09.22 - 17:42:19] !INFO! execute_os_command: Would have executed: 
[2016.09.22 - 17:42:19] !INFO! execute_os_command:  echo trololol 
[2016.09.22 - 17:42:19] !INFO! execute_os_command: Exiting execute_os_command()... 
[2016.09.22 - 17:42:19] !INFO! wrapper: Done running execute_os_command()! 
[2016.09.22 - 17:42:19] !INFO! <module>: Exiting script... 


In [5]: 

Cuando llego a los recursos mágicos conocidos como "tiempo y energía", estoy interesado en jugar con LOG_FORMAT, y averiguar cómo puedo reemplazar la subcadena con wrapper say filename y linenumber of function invocation =)

Cuestiones relacionadas