29 Stimmen

Decorator, der Funktionsaufrufdetails (Parameternamen und effektive Werte) ausgibt?

Ich möchte eine Funktion erstellen, die als Dekorator einer anderen Funktion die Details des Funktionsaufrufs ausgibt - Parameternamen und effektive Werte. Meine aktuelle Implementierung ist diese.

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)

Irgendwie funktioniert es, aber mit einigen Fehlern:

Für Anrufe

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

Es druckt

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

Das erwartete Ergebnis ist

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

Ich bin hier hängengeblieben. Können Sie mir helfen, die richtige Lösung zu finden?

30voto

warvariuc Punkte 53231

Hier ist die aktualisierte Version für Python 3.6+

import inspect

def dump_args(func):
    """
    Decorator to print function call details.

    This includes parameters names and effective values.
    """

    def wrapper(*args, **kwargs):
        func_args = inspect.signature(func).bind(*args, **kwargs).arguments
        func_args_str = ", ".join(map("{0[0]} = {0[1]!r}".format, func_args.items()))
        print(f"{func.__module__}.{func.__qualname__} ( {func_args_str} )")
        return func(*args, **kwargs)

    return wrapper

@dump_args
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)

Alte Fassung


Arbeitsversion mit Standardwerten:

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)

Ergebnis:

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

13voto

robert king Punkte 15261

Tut mir leid, dass es ein bisschen chaotisch ist. Ich habe einige Codes aus Einfacher Dump von Funktionsargumenten im PythonDecoratorLibrary .

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

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

Ausgabe:

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

7voto

x10an14 Punkte 179

Hier ist, wie ich es in Python 3 gelöst habe, basierend auf aliteralmind's Antwort, etwas deutlicher formuliert (PEP8), wenn ich das sagen darf. Der größte Teil der Inspiration für die Bereinigung kam aus dem (derzeit) akzeptierte Antwort por Robert König .

Code ( test.py ):

#!/usr/bin/env python3

import functools
import inspect
import logging
import time

class CustomFormatter(logging.Formatter):
    """
    Custom formatter, overrides funcName with value of name_override if it exists

    Inspired by; https://stackoverflow.com/a/7004565/1503549
    """
    def format(self, record):
        if hasattr(record, 'func_name'):
            record.funcName = record.func_name
        return super(CustomFormatter, self).format(record)

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

    @functools.wraps(decorated_function)
    def wrapper(*dec_fn_args, **dec_fn_kwargs):
        # Log function entry
        func_name = decorated_function.__name__
        name_dict = dict(func_name=func_name)
        logging.info(f"Entering {func_name}()...", extra=name_dict)

        # Log function params (args and kwargs)
        func_args = inspect.signature(decorated_function).bind(*dec_fn_args, **dec_fn_kwargs).arguments
        func_args_str = ', '.join(
            f"{var_name} = {var_value}"
            for var_name, var_value
            in func_args.items()
        )
        logging.debug(f"\t{func_args_str}", extra=name_dict)

        # Execute wrapped (decorated) function:
        out = decorated_function(*dec_fn_args, **dec_fn_kwargs)

        time.sleep(1)   # Test to ensure timestamp is real
        logging.info(f"Done running {func_name}()!", extra=name_dict)

        return out
    return wrapper

@log_function_entry_and_exit
def func2(*args, **kwargs):
    print('\t\thello')

@log_function_entry_and_exit
def func1(a, b, c):
    func2(1, 2, 3, 4, b=5)
    print('Hello2!')

if __name__ == '__main__':
    log = logging.getLogger()

    # Must get handler to set format
    handler = logging.StreamHandler()   # Get default root logger
    handler.setFormatter(
        CustomFormatter(
            (
                '[%(asctime)s]'
                ' %(levelname)s:%(funcName)s():%(lineno)s>'
                ' %(message)s'
            ),
            datefmt='%Y-%m-%dT%H:%M:%S',
        )
    )

    # Set logLevel
    log.setLevel(logging.DEBUG)
    handler.setLevel(logging.DEBUG)

    # Combine the two again
    log.addHandler(handler)

    log.info('yolo!', extra=dict(func_name='__main__'))

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

Ausgabe:

[2020-06-11 22:22:15] 0 x10an14@x10-desktop:~/Desktop/testy
-> $ python3 test.py 
[2020-06-11T22:22:53] INFO:__main__():88> yolo!
[2020-06-11T22:22:53] INFO:func1():33> Entering func1()...
[2020-06-11T22:22:53] DEBUG:func1():42>     a = 2, b = y, c = {'z': 4}
[2020-06-11T22:22:53] INFO:func2():33> Entering func2()...
[2020-06-11T22:22:53] DEBUG:func2():42>     args = (1, 2, 3, 4), kwargs = {'b': 5}
        hello
[2020-06-11T22:22:54] INFO:func2():48> Done running func2()!
Hello2!
[2020-06-11T22:22:55] INFO:func1():48> Done running func1()!
[2020-06-11 22:22:55] 0 x10an14@x10-desktop:~/Desktop/testy
-> $ 

Wenn ich die magischen Ressourcen "Zeit und Energie" zur Verfügung habe, möchte ich mich mit folgenden Themen beschäftigen LOG_FORMAT und herausfinden, wie ich die wrapper Teilstring mit z. B. Dateiname und Zeilennummer des Funktionsaufrufs =)

EDITAR (2020-06-11): Behoben wrapper Problem nach Aufforderung durch den Kommentar von @Gahan (vom 2020-06-10).

Das scheint unmöglich zu sein (vgl. https://stackoverflow.com/a/8339710/1503549 ), um einen Wrapper-Bericht (über die Option logging Modul) die Zeilennummer der umschlossenen/dekorierten Funktion. Vielleicht verpackt zu diesem Zweck genutzt werden könnten?

4voto

aliteralmind Punkte 19059

Die Antwort von @warvariuc, aktualisiert auf 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)

1voto

Pascal T. Punkte 3572

Hier ist eine Version, die auch für native Funktionen funktioniert (nur Python 3)

import logging
import inspect

def dump_args(func):
    """
    Decorator to print function call details.
    This includes parameters names and effective values.
    """
    def wrapper(*args, **kwargs):
        try:
            # For standard functions, inspect the signature
            signature = inspect.signature(func)
            func_args = signature.bind(*args, **kwargs).arguments
            func_args_str = ", ".join(map("{0[0]} = {0[1]!r}".format, func_args.items()))
            msg =  f"{func.__module__}.{func.__qualname__} ( {func_args_str} )"
        except ValueError:
            # For native functions, the signature cannot be inspected
            args_strs = map(lambda arg: f"arg_{arg[0]} = {arg[1]}", enumerate(args) )
            kwargs_strs = map(lambda kwarg: f"{kwarg[0]} = {kwarg[1]}", kwargs )
            func_args_str = ", ".join(list(args_strs) + list(kwargs_strs))
            msg =  f"{func.__module__}.{func.__name__} ( {func_args_str} )"
        logging.debug(msg)
        return func(*args, **kwargs)
    return wrapper

Und ich kann sie auf Modulebene wie folgt aktivieren:

__init__.py

_DEBUG_MY_MODULE = True

if _DEBUG_MY_MODULE:
    from .decorators import dump_args
    SDL_CreateWindow = dump_args(SDL_CreateWindow)

Und ich bekomme Ausgaben wie diese:

DEBUG:root:ctypes.SDL_CreateWindow ( arg_0 = b'', arg_1 = 863, arg_2 = 548, arg_3 = 150, arg_4 = 150, arg_5 = 8226 )

Eine vollständigere Version mit Rückgabewerten, Ausgabeparametern und ausgefallenen Ausgaben finden Sie unter dieser Kern

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X