I was reading Ian Goodfellow's GAN source code in Github (link https://github.com/goodfeli/adversarial/blob/master/deconv.py). In particular, at line 40/41, the code is:
@functools.wraps(Model.get_lr_scalers)
def get_lr_scalers(self):
It's a rather unfamiliar way of using wraps
, and it seems the goal is to replace the get_lr_scalers
with a user defined function. But in that case, we don't really need a wrapper for that, right? I don't really know the purpose of wraps
in this case.
wraps
copies a number of attributes from another function onto this function—by default, __module__
, __name__
, __qualname__
, __annotations__
and __doc__
.
The most obviously useful one to copy over is the __doc__
. Consider this simpler example:1
class Base:
def spam(self, breakfast):
"""spam(self, breakfast) -> breakfast with added spam
<29 lines of detailed information here>
"""
class Child:
@functools.wraps(Base.spam)
def spam(self, breakfast):
newbreakfast = breakfast.copy()
newbreakfast.meats['spam'] + 30
return newbreakfast
Now if someone wants to use help(mychild.spam)
, they'll get the 29 lines of useful information. (Or, if they autocomplete mychild.spam
in PyCharm, it'll pop up the overlay with the documentation, etc.) All without me having to manually copy and paste it. And, even better, if Base
came from some framework that I didn't write, and my user upgrades from 1.2.3 to 1.2.4 of that framework, and there's a better docstring, they'll see that better docstring.
In the most common case, Child
would be a subclass of Base
, and spam
would be an override.2 But that isn't actually required—wraps
doesn't care whether you're subtyping via inheritance, or duck typing by just implementing an implicit protocol; it's equally useful for both cases. As long as Child
is intended to implement the spam
protocol from Base
, it makes sense for Child.spam
to have the same docstring (and maybe other metadata attributes).
Others attributes probably aren't quite as useful as docstrings. For example, if you're using type annotations, their benefit in reading the code is probably at least as high as their benefit in being able to run Mypy for static type checking, so just copying them over dynamically from another method often isn't all that useful. And __module__
and __qualname__
are primarily used for reflection/inspection, and are more likely to be misleading than helpful in this case (although you could probably come up with an example of a framework where you'd want people to read the code in Base
instead of the code in Child
, that isn't true for the default obvious example). But, unless they're actively harmful, the readability cost of using @functools.wraps(Base.spam, assigned=('__doc__',))
instead of just the defaults may not be worth it.
1. If you're using Python 2, change these classes to inherit from object
; otherwise they'll be old-style classes, which just complicates things in an irrelevant way. If Python 3, there are no old-style classes, so this issue can't even arise.
2. Or maybe a "virtual subclass" of an ABC, declared via a register
call, or via a subclass hook.