functools.wraps는 무엇을합니까?
다른 질문 에 대한이 답변 에 대한 의견에서 누군가가 무엇 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
새 함수의 이름이기 때문에 인쇄 됩니다. 사실에 대한 독 스트링을 보면 독 스트링 이 없기 f
때문에 공백이되므로 with_logging
작성한 독 스트링은 더 이상 존재하지 않습니다. 또한 해당 함수에 대한 pydoc 결과를 보면 하나의 인수를 취하는 것으로 나열되지 않습니다 x
. 대신이 감수로 표시됩니다 *args
와 **kwargs
무엇이 with_logging의 때문.
데코레이터를 사용하는 것이 항상 함수에 대한이 정보를 잃어 버리는 것을 의미한다면 심각한 문제가 될 것입니다. 그것이 우리가 functools.wraps
. 이것은 데코레이터에서 사용되는 함수를 취하고 함수 이름, 독 스트링, 인수 목록 등을 복사하는 기능을 추가합니다. 그리고 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__
. Django가 "object has no attribute ' __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)
. 정확히 세 가지를 수행합니다.
- it copies the
__module__
,__name__
,__qualname__
,__doc__
, and__annotations__
attributes off
ong
. This default list is inWRAPPER_ASSIGNMENTS
, you can see it in the functools source. - it updates the
__dict__
ofg
with all elements fromf.__dict__
. (seeWRAPPER_UPDATES
in the source) - it sets a new
__wrapped__=f
attribute ong
The consequence is that g
appears as having the same name, docstring, module name, and signature than f
. The only problem is that concerning the signature this is not actually true: it is just that inspect.signature
follows wrapper chains by default. You can check it by using inspect.signature(g, follow_wrapped=False)
as explained in the doc. This has annoying consequences:
- the wrapper code will execute even when the provided arguments are invalid.
- the wrapper code can not easily access an argument using its name, from the received *args, **kwargs. Indeed one would have to handle all cases (positional, keyword, default) and therefore to use something like
Signature.bind()
.
Now there is a bit of confusion between functools.wraps
and decorators, because a very frequent use case for developing decorators is to wrap functions. But both are completely independent concepts. If you're interested in understanding the difference, I implemented helper libraries for both: decopatch to write decorators easily, and makefun to provide a signature-preserving replacement for @wraps
. Note that makefun
relies on the same proven trick than the famous decorator
library.
this is the source code about wraps:
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)
Prerequisite: You must know how to use decorators and specially with wraps. This comment explains it a bit clear or this link also explains it pretty well.
Whenever we use For eg: @wraps followed by our own wrapper function. As per the details given in this link , it says that
functools.wraps is convenience function for invoking update_wrapper() as a function decorator, when defining a wrapper function.
It is equivalent to partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated).
So @wraps decorator actually gives a call to functools.partial(func[,*args][, **keywords]).
The functools.partial() definition says that
The partial() is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature. For example, partial() can be used to create a callable that behaves like the int() function where the base argument defaults to two:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
Which brings me to the conclusion that, @wraps gives a call to partial() and it passes your wrapper function as a parameter to it. The partial() in the end returns the simplified version i.e the object of what's inside the wrapper function and not the wrapper function itself.
In short, functools.wraps is just a regular function. Let's consider this official example. With the help of the source code, we can see more details about the implementation and the running steps as follows:
- wraps(f) returns an object, say O1. It is an object of the class Partial
- The next step is @O1... which is the decorator notation in python. It means
wrapper=O1.__call__(wrapper)
Checking the implementation of __call__, we see that after this step, (the left hand side )wrapper becomes the object resulted by self.func(*self.args, *args, **newkeywords) Checking the creation of O1 in __new__, we know self.func is the function update_wrapper. It uses the parameter *args, the right hand side wrapper, as its 1st parameter. Checking the last step of update_wrapper, one can see the right hand side wrapper is returned, with some of attributes modified as needed.
참고URL : https://stackoverflow.com/questions/308999/what-does-functools-wraps-do
'your programing' 카테고리의 다른 글
Notepad ++에서 중복 행 제거 (0) | 2020.10.03 |
---|---|
밀리 초 동안 절전 모드 (0) | 2020.10.03 |
잘못된 Git 브랜치에 대한 커밋을 수정하는 방법은 무엇입니까? (0) | 2020.10.03 |
Vim에서 수직 분할에서 수평 분할로 빠르게 전환하려면 (0) | 2020.10.03 |
정적 HTML 페이지에 파비콘 추가 (0) | 2020.10.03 |