In a comment on this answer to another question, someone said that they weren't sure what functools.wraps
was doing. So, I'm asking this question so that there will be a record of it on StackOverflow for future reference: what does functools.wraps
do, exactly?
相关问题
- how to define constructor for Python's new Nam
- streaming md5sum of contents of a large remote tar
- How to get the background from multiple images by
- Evil ctypes hack in python
- Correctly parse PDF paragraphs with Python
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
So @wraps decorator actually gives a call to functools.partial(func[,*args][, **keywords]).
The functools.partial() definition says that
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:
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.
this is the source code about wraps:
When you use a decorator, you're replacing one function with another. In other words, if you have a decorator
then when you say
it's exactly the same as saying
and your function
f
is replaced with the function with_logging. Unfortunately, this means that if you then sayit will print
with_logging
because that's the name of your new function. In fact, if you look at the docstring forf
, it will be blank becausewith_logging
has no docstring, and so the docstring you wrote won't be there anymore. Also, if you look at the pydoc result for that function, it won't be listed as taking one argumentx
; instead it'll be listed as taking*args
and**kwargs
because that's what with_logging takes.If using a decorator always meant losing this information about a function, it would be a serious problem. That's why we have
functools.wraps
. This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And sincewraps
is itself a decorator, the following code does the correct thing:I very often use classes, rather than functions, for my decorators. I was having some trouble with this because an object won't have all the same attributes that are expected of a function. For example, an object won't have the attribute
__name__
. I had a specific issue with this that was pretty hard to trace where Django was reporting the error "object has no attribute '__name__
'". Unfortunately, for class-style decorators, I don't believe that @wrap will do the job. I have instead created a base decorator class like so:This class proxies all the attribute calls over to the function that is being decorated. So, you can now create a simple decorator that checks that 2 arguments are specified like so: