문제

내가 매우 일반적인 작업을 수행하는 데코레이터를 작성했다고 가정해 보겠습니다.예를 들어 모든 인수를 특정 유형으로 변환하고, 로깅을 수행하고, 메모를 구현하는 등의 작업을 수행할 수 있습니다.

예는 다음과 같습니다.

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

>>> funny_function("3", 4.0, z="5")
22

지금까지 모든 것이 잘되었습니다.그러나 한 가지 문제가 있습니다.장식된 함수는 원래 함수의 문서를 유지하지 않습니다.

>>> help(funny_function)
Help on function g in module __main__:

g(*args, **kwargs)

다행히 해결 방법이 있습니다.

def args_as_ints(f):
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

이번에는 함수 이름과 문서가 정확합니다.

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

하지만 여전히 문제가 있습니다.함수 서명이 잘못되었습니다."*args, **kwargs" 정보는 쓸모가 없습니다.

무엇을 해야 할까요?간단하지만 결함이 있는 두 가지 해결 방법을 생각해 볼 수 있습니다.

1 -- 문서 문자열에 올바른 서명을 포함합니다:

def funny_function(x, y, z=3):
    """funny_function(x, y, z=3) -- computes x*y + 2*z"""
    return x*y + 2*z

중복으로 인해 안좋습니다.자동으로 생성된 문서에는 여전히 서명이 제대로 표시되지 않습니다.함수를 업데이트하고 독스트링 변경을 잊어버리거나 오타를 만드는 것은 쉽습니다.[그리고 그렇습니다. 나는 Docstring이 이미 함수 본문을 복제한다는 사실을 알고 있습니다.이것을 무시하십시오;funny_function은 단지 임의의 예일 뿐입니다.]

2 -- 데코레이터를 사용하지 않거나 모든 특정 서명에 특수 목적 데코레이터를 사용하지 마십시오.

def funny_functions_decorator(f):
    def g(x, y, z=3):
        return f(int(x), int(y), z=int(z))
    g.__name__ = f.__name__
    g.__doc__ = f.__doc__
    return g

이는 동일한 서명을 가진 일련의 함수에 대해서는 잘 작동하지만 일반적으로 쓸모가 없습니다.처음에 말했듯이, 나는 데코레이터를 완전히 일반적으로 사용할 수 있기를 원합니다.

저는 완전히 일반적이고 자동적인 솔루션을 찾고 있습니다.

질문은 다음과 같습니다.장식된 함수 서명을 생성한 후 편집할 수 있는 방법이 있습니까?

그렇지 않으면, 데코레이팅된 함수를 생성할 때 함수 시그니처를 추출하고 "*kwargs, **kwargs" 대신 해당 정보를 사용하는 데코레이터를 작성할 수 있습니까?해당 정보를 어떻게 추출하나요?exec를 사용하여 장식된 함수를 어떻게 구성해야 합니까?

다른 접근법이 있나요?

도움이 되었습니까?

해결책

  1. 설치 데코레이터 기준 치수:

    $ pip install decorator
    
  2. 정의의 적응 args_as_ints():

    import decorator
    
    @decorator.decorator
    def args_as_ints(f, *args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    
    @args_as_ints
    def funny_function(x, y, z=3):
        """Computes x*y + 2*z"""
        return x*y + 2*z
    
    print funny_function("3", 4.0, z="5")
    # 22
    help(funny_function)
    # Help on function funny_function in module __main__:
    # 
    # funny_function(x, y, z=3)
    #     Computes x*y + 2*z
    

파이썬 3.4+

functools.wraps() stdlib에서 파이썬 3.4 이후 서명을 보존합니다.

import functools


def args_as_ints(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return func(*args, **kwargs)
    return wrapper


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z


print(funny_function("3", 4.0, z="5"))
# 22
help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
#     Computes x*y + 2*z

functools.wraps() 사용할 수 있습니다 적어도 Python 이후 2.5 그러나 그것은 거기에 서명을 보존하지 않습니다.

help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(*args, **kwargs)
#    Computes x*y + 2*z

알아채다: *args, **kwargs 대신에 x, y, z=3.

다른 팁

이는 Python의 표준 라이브러리로 해결됩니다. functools 그리고 구체적으로 functools.wraps 기능은 "래핑된 함수처럼 보이도록 래퍼 함수를 ​​업데이트합니다.".그러나 동작은 아래와 같이 Python 버전에 따라 다릅니다.질문의 예에 적용하면 코드는 다음과 같습니다.

from functools import wraps

def args_as_ints(f):
    @wraps(f) 
    def g(*args, **kwargs):
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return f(*args, **kwargs)
    return g


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z

Python 3에서 실행하면 다음이 생성됩니다.

>>> funny_function("3", 4.0, z="5")
22
>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(x, y, z=3)
    Computes x*y + 2*z

유일한 단점은 Python 2에서는 함수의 인수 목록을 업데이트하지 않는다는 것입니다.Python 2에서 실행하면 다음이 생성됩니다.

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(*args, **kwargs)
    Computes x*y + 2*z

이있다 데코레이터 모듈 ~와 함께 decorator 사용할 수있는 데코레이터 :

@decorator
def args_as_ints(f, *args, **kwargs):
    args = [int(x) for x in args]
    kwargs = dict((k, int(v)) for k, v in kwargs.items())
    return f(*args, **kwargs)

그런 다음 방법의 서명과 도움이 보존됩니다.

>>> help(funny_function)
Help on function funny_function in module __main__:

funny_function(x, y, z=3)
    Computes x*y + 2*z

편집 : JF Sebastian은 내가 수정하지 않았다고 지적했습니다. args_as_ints 기능 - 지금 고정되었습니다.

살펴보십시오 데코레이터 모듈 - 특히 데코레이터 이 문제를 해결하는 데코레이터.

두 번째 옵션 :

  1. WRAPT 모듈 설치 :

$ easy_install wrapt

WRAPT에는 보너스가 있고 수업 서명을 보존하십시오.


import wrapt
import inspect

@wrapt.decorator def args_as_ints(wrapped, instance, args, kwargs): if instance is None: if inspect.isclass(wrapped): # Decorator was applied to a class. return wrapped(*args, **kwargs) else: # Decorator was applied to a function or staticmethod. return wrapped(*args, **kwargs) else: if inspect.isclass(instance): # Decorator was applied to a classmethod. return wrapped(*args, **kwargs) else: # Decorator was applied to an instancemethod. return wrapped(*args, **kwargs) @args_as_ints def funny_function(x, y, z=3): """Computes x*y + 2*z""" return x * y + 2 * z >>> funny_function(3, 4, z=5)) # 22 >>> help(funny_function) Help on function funny_function in module __main__: funny_function(x, y, z=3) Computes x*y + 2*z

위에서 언급 한 바와 같이 JFS의 대답 ; 외관 측면에서 서명에 관심이있는 경우 (help, 그리고 inspect.signature), 그런 다음 사용합니다 functools.wraps 완벽하게 괜찮습니다.

행동 측면에서 서명에 관심이있는 경우 (특히 TypeError 인수 불일치의 경우), functools.wraps 그것을 보존하지 않습니다. 오히려 사용해야합니다 decorator 이를 위해, 또는 핵심 엔진의 일반화 makefun.

from makefun import wraps

def args_as_ints(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("wrapper executes")
        args = [int(x) for x in args]
        kwargs = dict((k, int(v)) for k, v in kwargs.items())
        return func(*args, **kwargs)
    return wrapper


@args_as_ints
def funny_function(x, y, z=3):
    """Computes x*y + 2*z"""
    return x*y + 2*z


print(funny_function("3", 4.0, z="5"))
# wrapper executes
# 22

help(funny_function)
# Help on function funny_function in module __main__:
#
# funny_function(x, y, z=3)
#     Computes x*y + 2*z

funny_function(0)  
# observe: no "wrapper executes" is printed! (with functools it would)
# TypeError: funny_function() takes at least 2 arguments (1 given)

또한보십시오 이 게시물에 관한이 게시물 functools.wraps.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top