functools.wraps는 무엇을합니까?


650

다른 질문 에 대한이 답변 에 대한 의견에서 누군가는 자신이 무엇 functools.wraps을하고 있는지 잘 모르겠다 고 말했습니다 . 따라서 나중에 참조 할 수 있도록 StackOverflow에 레코드가 기록되도록이 질문을하고 있습니다. functools.wraps정확히 무엇을합니까?

답변:


1069

데코레이터를 사용하면 한 기능을 다른 기능으로 대체하게됩니다. 즉, 데코레이터가 있다면

def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

그때 당신이 말할 때

@logged
def f(x):
   """does some math"""
   return x + x * x

말하는 것과 똑같습니다

def f(x):
    """does some math"""
    return x + x * x
f = logged(f)

함수 f가 함수 로 바뀝니다 with_logging. 불행히도, 이것은 당신이 그렇다면

print(f.__name__)

with_logging새 기능의 이름이기 때문에 인쇄 됩니다. 실제로에 대한 docstring을 보면 docstring 이 없기 f때문에 비어 있습니다. with_logging따라서 작성한 docstring은 더 이상 존재하지 않습니다. 또한 해당 함수의 pydoc 결과를 보면 하나의 인수를 취하는 것으로 나열되지 않습니다 x. 대신이 감수로 표시됩니다 *args**kwargs무엇이 with_logging의 때문.

데코레이터를 사용하는 것이 항상 함수에 대한 정보를 잃어 버리는 것을 의미한다면 심각한 문제가 될 것입니다. 우리가 이유 functools.wraps입니다. 이것은 데코레이터에서 사용되는 함수를 취하고 함수 이름, docstring, 인수 목록 등을 복사하는 기능을 추가합니다. 그리고 wraps자체적으로 데코레이터이기 때문에 다음 코드가 올바른 작업을 수행합니다.

from functools import wraps
def logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print(func.__name__ + " was called")
        return func(*args, **kwargs)
    return with_logging

@logged
def f(x):
   """does some math"""
   return x + x * x

print(f.__name__)  # prints 'f'
print(f.__doc__)   # prints 'does some math'

7
functools.wraps는 표준 라이브러리의 일부이므로 다른 외부 종속성을 도입하지 않으므로 데코레이터 모듈을 사용하지 않는 것이 좋습니다. 그러나 데코레이터 모듈은 실제로 도움말 문제를 해결하므로 언젠가 functools.wraps도 도움이 될 것입니다.
Eli Courtwright

6
다음은 랩을 사용하지 않을 경우 발생할 수있는 예입니다. doctools 테스트가 갑자기 사라질 수 있습니다. wraps ()와 같은 것이 복사되지 않으면 doctools가 데코 레이팅 된 함수에서 테스트를 찾을 수 없기 때문입니다.
Andrew cooke

88
왜 우리 functools.wraps는이 작업이 필요 합니까? 처음에는 데코레이터 패턴의 일부가 아니어야합니까? 때 당신은 할 수 없습니다 @wraps를 사용하려면?
wim

56
@ wim : @wraps복사 된 값에 대해 다양한 유형의 수정 또는 주석을 수행하기 위해 자체 버전을 사용하는 일부 데코레이터를 작성 했습니다. 기본적으로, 그것은 명시 적이 암시 적보다 낫고 특수한 경우가 규칙을 어길 정도로 특별하지 않다는 파이썬 철학의 확장입니다. ( @wraps특별한 옵트 아웃 메커니즘을 사용하는 대신 수동으로 제공해야하는 경우 코드가 훨씬 간단하고 이해하기 쉽습니다 .)
ssokolow

35
@LucasMalor 모든 데코레이터가 꾸미는 기능을 마무리하는 것은 아닙니다. 일부는 일종의 조회 시스템에 등록하는 등 부작용을 적용합니다.
ssokolow

22

나는 종종 데코레이터를 위해 함수보다는 클래스를 사용합니다. 객체에 함수에 필요한 동일한 속성이 모두 없기 때문에이 문제가 발생했습니다. 예를 들어 객체에는 속성이 없습니다 __name__. 장고가 "개체에 ' __name__' 속성이 없습니다 ' 라는 오류를보고하는 위치를 추적하기가 어려웠습니다 . 불행히도 클래스 스타일 데코레이터에게는 @wrap이 그 일을 할 것이라고 믿지 않습니다. 대신 기본 데코레이터 클래스를 다음과 같이 만들었습니다.

class DecBase(object):
    func = None

    def __init__(self, func):
        self.__func = func

    def __getattribute__(self, name):
        if name == "func":
            return super(DecBase, self).__getattribute__(name)

        return self.func.__getattribute__(name)

    def __setattr__(self, name, value):
        if name == "func":
            return super(DecBase, self).__setattr__(name, value)

        return self.func.__setattr__(name, value)

이 클래스는 모든 속성이 데코레이션되는 함수를 호출하도록 프록시합니다. 따라서 다음과 같이 2 개의 인수가 지정되어 있는지 확인하는 간단한 데코레이터를 만들 수 있습니다.

class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)

7
에서이 문서로 @wraps말한다, @wraps단지 편리한 기능입니다 functools.update_wrapper(). 클래스 데코레이터의 경우 메소드 update_wrapper()에서 직접 호출 할 수 있습니다 __init__(). 그래서, 당신은 만들 필요가 없습니다 DecBase전혀, 당신은 단지에 포함 할 수 __init__()process_login라인 : update_wrapper(self, func). 그게 다야.
Fabiano

14

파이썬 3.5 이상 :

@functools.wraps(f)
def g():
    pass

의 별칭입니다 g = functools.update_wrapper(g, f). 정확히 세 가지를 수행합니다.

  • 그것을 복사 __module__, __name__, __qualname__, __doc__, 및 __annotations__의 속성 fg. 이 기본 목록은 WRAPPER_ASSIGNMENTS에 있으며 functools 소스 에서 볼 수 있습니다 .
  • 의 모든 요소로 __dict__g를 업데이트합니다 f.__dict__. ( WRAPPER_UPDATES소스에서 참조 )
  • __wrapped__=f속성을 설정 합니다.g

결과 g는와 이름, 문서 문자열, 모듈 이름 및 서명이 같은 것으로 나타납니다 f. 유일한 문제는 서명과 관련하여 이것이 사실이 아니라는 것입니다. inspect.signature기본적으로 래퍼 체인 을 따르는 것입니다 . doc에inspect.signature(g, follow_wrapped=False) 설명 된대로 사용하여 확인할 수 있습니다 . 이것은 성가신 결과입니다.

  • 제공된 인수가 유효하지 않은 경우에도 랩퍼 코드가 실행됩니다.
  • 랩퍼 코드는 수신 된 * args ** kwargs에서 이름을 사용하여 인수에 쉽게 액세스 할 수 없습니다. 실제로 모든 경우 (위치, 키워드, 기본값)를 처리해야하므로 다음과 같은 것을 사용해야합니다 Signature.bind().

functools.wraps데코레이터를 개발하는 데 가장 빈번한 사용 사례는 함수를 래핑하는 것이기 때문에 이제는 데코레이터와 약간의 혼동이 있습니다. 그러나 둘 다 완전히 독립적 인 개념입니다. : 당신이 차이를 이해에 관심이 있다면, 나는 도우미 모두 라이브러리 구현 decopatch 쉽게 쓰기 장식, 그리고 makefun을 위한 서명 보존 교체를 제공하기 위해 @wraps. 참고 makefun유명한 같 검증 된 트릭에 의존 decorator라이브러리입니다.


3

이것은 랩에 대한 소스 코드입니다.

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

WRAPPER_UPDATES = ('__dict__',)

def update_wrapper(wrapper,
                   wrapped,
                   assigned = WRAPPER_ASSIGNMENTS,
                   updated = WRAPPER_UPDATES):

    """Update a wrapper function to look like the wrapped function

       wrapper is the function to be updated
       wrapped is the original function
       assigned is a tuple naming the attributes assigned directly
       from the wrapped function to the wrapper function (defaults to
       functools.WRAPPER_ASSIGNMENTS)
       updated is a tuple naming the attributes of the wrapper that
       are updated with the corresponding attribute from the wrapped
       function (defaults to functools.WRAPPER_UPDATES)
    """
    for attr in assigned:
        setattr(wrapper, attr, getattr(wrapped, attr))
    for attr in updated:
        getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    # Return the wrapper so this can be used as a decorator via partial()
    return wrapper

def wraps(wrapped,
          assigned = WRAPPER_ASSIGNMENTS,
          updated = WRAPPER_UPDATES):
    """Decorator factory to apply update_wrapper() to a wrapper function

   Returns a decorator that invokes update_wrapper() with the decorated
   function as the wrapper argument and the arguments to wraps() as the
   remaining arguments. Default arguments are as for update_wrapper().
   This is a convenience function to simplify applying partial() to
   update_wrapper().
    """
    return partial(update_wrapper, wrapped=wrapped,
                   assigned=assigned, updated=updated)

2
  1. 전제 조건 : 데코레이터를 사용하는 방법과 랩을 사용하는 방법을 알아야합니다. 이 의견 은 약간 명확하게 설명 하거나이 링크 는 꽤 잘 설명합니다.

  2. 예를 들어 @wraps 다음에 자체 래퍼 함수가 사용됩니다. 이 링크에 제공된 세부 사항에 따르면

functools.wraps는 랩퍼 함수를 ​​정의 할 때 update_wrapper ()를 함수 데코레이터로 호출하기위한 편리한 함수입니다.

partial (update_wrapper, wrapped = wrapped, assign = assigned, updated = updated)와 같습니다.

따라서 @wraps 데코레이터는 실제로 functools.partial (func [, * args] [, ** keywords])를 호출합니다.

functools.partial () 정의에 따르면

partial ()은 함수의 인수 및 / 또는 키워드의 일부를 "고정"하여 서명이 단순화 된 새 객체를 생성하는 부분 함수 응용 프로그램에 사용됩니다. 예를 들어 partial ()을 사용하여 기본 인수의 기본값이 2 인 int () 함수와 같은 동작을하는 호출 가능 객체를 만들 수 있습니다.

>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18

@wraps는 partial ()을 호출하고 래퍼 함수를 ​​매개 변수로 전달한다는 결론을 얻습니다. 결국 partial ()은 단순화 된 버전, 즉 래퍼 함수 자체가 아닌 래퍼 함수 안에있는 객체를 반환합니다.


-4

간단히 말해서, functools.wraps 는 단지 일반적인 함수입니다. 이 공식적인 예를 생각해 봅시다 . 소스 코드 의 도움으로 구현 및 실행 단계에 대한 자세한 내용을 다음과 같이 볼 수 있습니다.

  1. wraps (f) 는 객체를 반환합니다 (예 : O1) . Partial 클래스 의 객체입니다.
  2. 다음 단계는 @ O1입니다. 이것은 파이썬의 데코레이터 표기법입니다. 그 뜻은

래퍼 = O1 .__ call __ (래퍼)

__call__ 의 구현을 확인하면 이 단계 (왼쪽) 래퍼self.func (* self.args, * args, ** newkeywords)에 의해 생성 된 객체가됩니다 . __new__ 에서 O1 생성을 확인합니다. self.funcupdate_wrapper 함수 라는 것을 알고 있습니다. 그것은 매개 변수 사용 *를 인수 , 우측 래퍼 의 첫번째 파라미터로. update_wrapper 의 마지막 단계를 확인하면 오른쪽 래퍼 가 반환되고 일부 속성은 필요에 따라 수정 된 것을 볼 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.