When we decorate function, we use functools.wraps to make decorated function look like original.
Is there any wat to do same, when we want to decorate class?
def some_class_decorator(cls_to_decorate):
class Wrapper(cls_to_decorate):
"""Some Wrapper not important doc."""
pass
return Wrapper
@some_class_decorator
class MainClass:
"""MainClass important doc."""
pass
help(MainClass)
Output:
class Wrapper(MainClass)
| Some Wrapper not important doc.
|
| Method resolution order:
| Wrapper
| MainClass
| builtins.object
|
# ... no MainClass important doc below.
I tried to write wraps equivalent for class decorator based on functools.wraps
source code, but my implementation doesn't work correct:
import functools
def wraps_cls(original_cls):
def wrapper(wrapper_cls):
"""Update wrapper_cls to look like original_cls."""
for attr in functools.WRAPPER_ASSIGNMENTS:
try:
value = getattr(original_cls, attr)
except AttributeError:
pass
else:
setattr(wrapper_cls, attr, value)
return wrapper_cls
return wrapper
def some_class_decorator(cls_to_decorate):
@wraps_cls(cls_to_decorate)
class Wrapper(cls_to_decorate):
"""Some Wrapper not important doc."""
pass
return Wrapper
@some_class_decorator
class MainClass:
"""MainClass important doc."""
pass
help(MainClass)
Output:
class MainClass(MainClass)
| MainClass important doc.
|
| Method resolution order:
| MainClass
| MainClass
| builtins.object
|
# ... MainClass doc is here but "Method resolution order" is broken.
Is there anyway to completely replace decorated MainClass help output with not decorated MainClass help output?
wraps() is a decorator that is applied to the wrapper function of a decorator. It updates the wrapper function to look like wrapped function by copying attributes such as __name__, __doc__ (the docstring), etc. Parameters: wrapped: The function name that is to be decorated by wrapper function.
functools. wraps is convenience function for invoking update_wrapper() as a function decorator, when defining a wrapper functionwrapper functionA wrapper function is a subroutine (another word for a function) in a software library or a computer program whose main purpose is to call a second subroutine or a system call with little or no additional computation.https://en.wikipedia.org › wiki › Wrapper_functionWrapper function - Wikipedia. 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]).
__wrapped__ in Python decorators As we can see from the code of the functools module 1, when decorating an object, there is an attribute named __wrapped__ that holds the reference to the original one. So now if we use this, we can access it directly without having to resort to the old quirks.
The functools module, part of Python's standard Library, provides useful features that make it easier to work with high order functionshigh order functionsIn mathematics and computer science, a higher-order function (HOF) is a function that does at least one of the following: takes one or more functions as arguments (i.e. a procedural parameter, which is a parameter of a procedure that is itself a procedure), returns a function as its result.https://en.wikipedia.org › wiki › Higher-order_functionHigher-order function - Wikipedia (a function that returns a function or takes another function as an argument ).
No, there isn't, assuming your decorator really subclasses the wrapped class like some_class_decorator
does. The output of help
is defined by the pydoc.Helper
class, which for classes ultimately calls pydoc.text.docclass
, which contains this code:
# List the mro, if non-trivial.
mro = deque(inspect.getmro(object))
if len(mro) > 2:
push("Method resolution order:")
for base in mro:
push(' ' + makename(base))
push('')
You can see that it is hard-coded to display the class's real MRO. This is as it should be. The MRO displayed in your last example is not "broken", it is correct. By making your wrapper class inherit from the wrapped class, you added an additional class to the inheritance hierarchy. It would be misleading to show an MRO that left that out, because there really is a class there. In your example, this wrapper class doesn't provide any behavior of its own, but a realistic wrapper class would (or else why would you be doing the wrapping at all?), and you would want to know which behavior came from the wrapper class and which from the wrapped class.
If you wanted, you could make a decorator that dynamically renamed the wrapper class with some name derived from the original, so the MRO would show something like DecoratorWrapper_of_MainClass
in the appropriate position. Whether this would be more readable than just having Wrapper
there is debatable.
Oh, I guess now I understand what are your trying to achieve.
You want to attach new methods from a "wrapper" using a class decorator.
Here is a workng example:
class Wrapper(object):
"""Some Wrapper not important doc."""
def another_method(self):
"""Another method."""
print 'Another method'
def some_class_decorator(cls_to_decorate):
return type(cls_to_decorate.__name__, cls_to_decorate.__bases__, dict
(cls_to_decorate.__dict__, another_method=Wrapper.__dict__['another_method']))
class MainClass(object):
"""MainClass important doc."""
def method(self):
"""A method."""
print "A method"
help(MainClass)
_MainClass = some_class_decorator(MainClass)
help(_MainClass)
_MainClass().another_method()
MainClass().another_method()
This example creates a new class not modifying the old class.
But I guess you could also just inject the given methods in the old class, modifying it in place.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With