복붙노트

[PYTHON] 함수 호출 세부 정보를 출력하는 데코레이터 - 매개 변수 이름과 유효 값

PYTHON

함수 호출 세부 정보를 출력하는 데코레이터 - 매개 변수 이름과 유효 값

다른 함수의 데코레이터가 함수 호출 세부 정보 (매개 변수 이름과 유효 값)를 인쇄하는 함수를 만들고 싶습니다. 현재 나의 구현은 이것이다.

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)

Kinda는 작동하지만 몇 가지 버그가 있습니다.

통화의 경우

테스트 (1, 2, 3, 4, 5, d = 6, g = 12.9)

인쇄하다

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

예상되는 결과는 다음과 같습니다.

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

나는 여기 붙어있어. 올바른 솔루션을 찾도록 도와 줄 수 있습니까?

해결법

  1. ==============================

    1.죄송합니다 좀 지저분 해. http://wiki.python.org/moin/PythonDecoratorLibrary#Easy_Dump_of_Function_Arguments에서 일부 코드를 수정했습니다.

    죄송합니다 좀 지저분 해. 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
    

    테스트 (1, 2, 3, 4, 5, d = 6, g = 12.9)

    산출:

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

  2. ==============================

    2.다음은 Python 3.6 이상을위한 업데이트 된 버전입니다.

    다음은 Python 3.6 이상을위한 업데이트 된 버전입니다.

    import inspect
    
    def dump_args(func):
        """Decorator to print function call details - parameters names and effective values.
        """
        def wrapper(*args, **kwargs):
            func_args = inspect.signature(func).bind(*args, **kwargs).arguments
            func_args_str =  ', '.join('{} = {!r}'.format(*item) for item in 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)
    

    구 버전

    기본값이있는 작업 버전 :

    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)
    

    결과:

    >>> 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} )
    
  3. ==============================

    3.@ warvariuc의 대답, 파이썬 3으로 업그레이드 :

    @ warvariuc의 대답, 파이썬 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)
    
  4. ==============================

    4.여기에 파이썬 3에서 어떻게 해결했는지, aliteralmind의 답을 바탕으로, 내가 그렇게 말할 수 있다면 좀 더 깔끔하게 (PEP8) 넣으십시오. 대청소의 영감의 대다수는 로버트 킹 (Robert King)이 (현재) 받아 들인 대답에서 나온 것이다.

    여기에 파이썬 3에서 어떻게 해결했는지, aliteralmind의 답을 바탕으로, 내가 그렇게 말할 수 있다면 좀 더 깔끔하게 (PEP8) 넣으십시오. 대청소의 영감의 대다수는 로버트 킹 (Robert King)이 (현재) 받아 들인 대답에서 나온 것이다.

    암호:

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

    산출:

    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()!
    

    주 : 출력의 래퍼 문자열은 logging.X () 메시지 호출을 만드는 함수의 이름을 나타냅니다.

    사용중인 예 :

    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]:
    

    "시간과 에너지"라고 알려진 마법의 자원을 얻으려면 LOG_FORMAT을 가지고 놀고, 함수 호출의 파일 이름과 줄 번호를 사용하여 래퍼 부분 문자열을 어떻게 대체 할 수 있는지 알아보십시오.)

  5. ==============================

    5.그것의 조금 오래 된 게시물하지만 내 비트를 추가 싶 었 어 요. warvariuc가 제공 한 해결책은 모든 경우에 작동하지 않습니다. 메소드가 호출하는 동안 이름 지정된 인수를 전송할뿐만 아니라 메소드가 기본값을 가지면 적절한 출력을 제공하지 않습니다. 예 : 우리는 b의 두 값을 얻습니다.

    그것의 조금 오래 된 게시물하지만 내 비트를 추가 싶 었 어 요. warvariuc가 제공 한 해결책은 모든 경우에 작동하지 않습니다. 메소드가 호출하는 동안 이름 지정된 인수를 전송할뿐만 아니라 메소드가 기본값을 가지면 적절한 출력을 제공하지 않습니다. 예 : 우리는 b의 두 값을 얻습니다.

    test(1, b = 5)
    test (a = 1, b = 4, c = 'blah-blah', kwargs = {'b': 5} )
    

    수정 된 코드 추가.

    def print_args(func):
    """
    Function to print all args of decorated function
    """
    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)
        new_arg_list = [list(i) for i in params]
        for key in func_kwargs:
            for param in new_arg_list:
                if key == param[0]:
                    param[1] = func_kwargs[key]
        new_arg_list = [tuple(i) for i in new_arg_list]
        print func.func_name + ' (' + ', '.join('%s = %r' % p for p in new_arg_list) + ' )'
        return func(*func_args, **func_kwargs)
    return wrapper
    
    
    @print_args
    def test_params(a=7,b=5):
        pass
    
    test_params(a=3)
    

    산출

    test_params (a = 3, b = 5 )
    
  6. from https://stackoverflow.com/questions/6200270/decorator-to-print-function-call-details-parameters-names-and-effective-values by cc-by-sa and MIT license