답변:
데코레이터를 사용하면 한 기능을 다른 기능으로 대체하게됩니다. 즉, 데코레이터가 있다면
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'
functools.wraps
는이 작업이 필요 합니까? 처음에는 데코레이터 패턴의 일부가 아니어야합니까? 때 당신은 할 수 없습니다 @wraps를 사용하려면?
@wraps
복사 된 값에 대해 다양한 유형의 수정 또는 주석을 수행하기 위해 자체 버전을 사용하는 일부 데코레이터를 작성 했습니다. 기본적으로, 그것은 명시 적이 암시 적보다 낫고 특수한 경우가 규칙을 어길 정도로 특별하지 않다는 파이썬 철학의 확장입니다. ( @wraps
특별한 옵트 아웃 메커니즘을 사용하는 대신 수동으로 제공해야하는 경우 코드가 훨씬 간단하고 이해하기 쉽습니다 .)
나는 종종 데코레이터를 위해 함수보다는 클래스를 사용합니다. 객체에 함수에 필요한 동일한 속성이 모두 없기 때문에이 문제가 발생했습니다. 예를 들어 객체에는 속성이 없습니다 __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)
@wraps
말한다, @wraps
단지 편리한 기능입니다 functools.update_wrapper()
. 클래스 데코레이터의 경우 메소드 update_wrapper()
에서 직접 호출 할 수 있습니다 __init__()
. 그래서, 당신은 만들 필요가 없습니다 DecBase
전혀, 당신은 단지에 포함 할 수 __init__()
의 process_login
라인 : update_wrapper(self, func)
. 그게 다야.
파이썬 3.5 이상 :
@functools.wraps(f)
def g():
pass
의 별칭입니다 g = functools.update_wrapper(g, f)
. 정확히 세 가지를 수행합니다.
__module__
, __name__
, __qualname__
, __doc__
, 및 __annotations__
의 속성 f
에 g
. 이 기본 목록은 WRAPPER_ASSIGNMENTS
에 있으며 functools 소스 에서 볼 수 있습니다 .__dict__
의 g
를 업데이트합니다 f.__dict__
. ( WRAPPER_UPDATES
소스에서 참조 )__wrapped__=f
속성을 설정 합니다.g
결과 g
는와 이름, 문서 문자열, 모듈 이름 및 서명이 같은 것으로 나타납니다 f
. 유일한 문제는 서명과 관련하여 이것이 사실이 아니라는 것입니다. inspect.signature
기본적으로 래퍼 체인 을 따르는 것입니다 . doc에inspect.signature(g, follow_wrapped=False)
설명 된대로 사용하여 확인할 수 있습니다 . 이것은 성가신 결과입니다.
Signature.bind()
.functools.wraps
데코레이터를 개발하는 데 가장 빈번한 사용 사례는 함수를 래핑하는 것이기 때문에 이제는 데코레이터와 약간의 혼동이 있습니다. 그러나 둘 다 완전히 독립적 인 개념입니다. : 당신이 차이를 이해에 관심이 있다면, 나는 도우미 모두 라이브러리 구현 decopatch 쉽게 쓰기 장식, 그리고 makefun을 위한 서명 보존 교체를 제공하기 위해 @wraps
. 참고 makefun
유명한 같 검증 된 트릭에 의존 decorator
라이브러리입니다.
이것은 랩에 대한 소스 코드입니다.
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)
전제 조건 : 데코레이터를 사용하는 방법과 랩을 사용하는 방법을 알아야합니다. 이 의견 은 약간 명확하게 설명 하거나이 링크 는 꽤 잘 설명합니다.
예를 들어 @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 ()은 단순화 된 버전, 즉 래퍼 함수 자체가 아닌 래퍼 함수 안에있는 객체를 반환합니다.
간단히 말해서, functools.wraps 는 단지 일반적인 함수입니다. 이 공식적인 예를 생각해 봅시다 . 소스 코드 의 도움으로 구현 및 실행 단계에 대한 자세한 내용을 다음과 같이 볼 수 있습니다.
래퍼 = O1 .__ call __ (래퍼)
__call__ 의 구현을 확인하면 이 단계 (왼쪽) 래퍼 가 self.func (* self.args, * args, ** newkeywords)에 의해 생성 된 객체가됩니다 . __new__ 에서 O1 생성을 확인합니다. self.func 는 update_wrapper 함수 라는 것을 알고 있습니다. 그것은 매개 변수 사용 *를 인수 , 우측 래퍼 의 첫번째 파라미터로. update_wrapper 의 마지막 단계를 확인하면 오른쪽 래퍼 가 반환되고 일부 속성은 필요에 따라 수정 된 것을 볼 수 있습니다.