다른 질문 에 대한이 답변 에 대한 의견에서 누군가는 자신이 무엇 functools.wraps
을하고 있는지 잘 모르겠다 고 말했습니다 . 따라서 나중에 참조 할 수 있도록 StackOverflow에 레코드가 기록되도록이 질문을하고 있습니다. functools.wraps
정확히 무엇을합니까?
답변
데코레이터를 사용하면 한 기능을 다른 기능으로 대체하게됩니다. 즉, 데코레이터가 있다면
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'
답변
나는 종종 데코레이터를 위해 함수보다는 클래스를 사용합니다. 객체에 함수에 필요한 동일한 속성이 모두 없기 때문에이 문제가 발생했습니다. 예를 들어 객체에는 속성이 없습니다 __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)
답변
파이썬 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)
설명 된대로 사용하여 확인할 수 있습니다 . 이것은 성가신 결과입니다.
- 제공된 인수가 유효하지 않은 경우에도 랩퍼 코드가 실행됩니다.
- 랩퍼 코드는 수신 된 * args ** kwargs에서 이름을 사용하여 인수에 쉽게 액세스 할 수 없습니다. 실제로 모든 경우 (위치, 키워드, 기본값)를 처리해야하므로 다음과 같은 것을 사용해야합니다
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 는 단지 일반적인 함수입니다. 이 공식적인 예를 생각해 봅시다 . 소스 코드 의 도움으로 구현 및 실행 단계에 대한 자세한 내용을 다음과 같이 볼 수 있습니다.
- wraps (f) 는 객체를 반환합니다 (예 : O1) . Partial 클래스 의 객체입니다.
- 다음 단계는 @ O1입니다. 이것은 파이썬의 데코레이터 표기법입니다. 그 뜻은
래퍼 = O1 .__ call __ (래퍼)
__call__ 의 구현을 확인하면 이 단계 (왼쪽) 래퍼 가 self.func (* self.args, * args, ** newkeywords)에 의해 생성 된 객체가됩니다 . __new__ 에서 O1 생성을 확인합니다. self.func 는 update_wrapper 함수 라는 것을 알고 있습니다. 그것은 매개 변수 사용 *를 인수 , 우측 래퍼 의 첫번째 파라미터로. update_wrapper 의 마지막 단계를 확인하면 오른쪽 래퍼 가 반환되고 일부 속성은 필요에 따라 수정 된 것을 볼 수 있습니다.