your programing

functools.wraps는 무엇을합니까?

lovepro 2020. 10. 3. 11:24
반응형

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 of f on g. This default list is in WRAPPER_ASSIGNMENTS, you can see it in the functools source.
  • it updates the __dict__ of g with all elements from f.__dict__. (see WRAPPER_UPDATES in the source)
  • it sets a new __wrapped__=f attribute on g

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)

  1. 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.

  2. 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:

  1. wraps(f) returns an object, say O1. It is an object of the class Partial
  2. 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

반응형