Hassle-free creation of decorators for functions and methods, OO-style.
- One decorator to rule them all; it works on functions, methods, classmethods and staticmethods.
- Easily define parameters with a dataclass-like annotated attribute syntax.
- Runtime type-checking of decorator paremeters.
- For decorators with all-default or no parameters, parentheses are optional:
@spam() == @spam
- Inheritance is supported.
- Any decorator parameters and instance attributes are accessible (as copies) in bound methods as well; no need to worry about those pesky descriptors.
- MyPy compatible and 100% test coverage.
Python 3.8 or 3.9.
pip install classy-decorators
for better runtime type checking:
pip install classy-decorators[typeguard]
The following code can also be found in the examples.
Create a decorator by subclassing classy_decorators.Decorator
.
You can override the the decorated function or method using the
__call_inner__
method (__call__
is meant for internal use only and should
not be used for this).
from classy_decorators import Decorator
class Double(Decorator):
def __call_inner__(self, *args, **kwargs) -> float:
return super().__call_inner__(*args, **kwargs) * 2
To see it in action, let's decorate a function:
@Double
def add(a, b):
return a + b
assert add(7, 14) == 42
You can also decorate methods and classmethods:
class AddConstant:
default_constant = 319
def __init__(self, constant=default_constant):
self.constant = constant
@Double
def add_to(self, value):
return value + self.constant
@Double
@classmethod
def add_default(cls, value):
return value + cls.default_constant
assert AddConstant(7).add_to(14) == 42
assert AddConstant.add_default(14) == 666
Our Double
decorator is pretty nice, but we can do better! So let's create a
decorator that is able to multiply results by any number instead of only by 2
:
class Multiply(Decorator):
factor: int
def __call_inner__(self, *args, **kwargs) -> float:
return super().__call_inner__(*args, **kwargs) * self.factor
By simply setting the type-annotated factor
attribute, we can use it as
decorator parameter. If you are familiar with
dataclasses, you can see
that this is very similar to defining dataclass fields.
@Multiply(2)
def add_and_double(a, b):
return a + b
@Multiply(factor=3)
def add_and_triple(a, b):
return a + b
assert add_and_double(8, 15) == 46
assert add_and_triple(8, 15) == 69
It's classy to be DRY, so let's combine our Double
and Multiply
decorators
into one that multiplies by 2
, unless specified otherwise:
class DoubleOrMultiply(Multiply):
factor = 2
@DoubleOrMultiply
def add_and_double(a, b):
return a + b
@DoubleOrMultiply(factor=3)
def add_and_triple(a, b):
return a + b
assert add_and_double(7, 14) == 42
assert add_and_triple(8, 15) == 69
The Decorator
base class provided, aside from __call_inner__
, two other
interface methods you can override:
Decorator.__decorate__(self, **params)
, which is called just after a function method is decorated, with all decorator parameter values or defaults as keyword arguments, i.e.DoubleOrMultiply.__decorate__(self, factor: int = 2)
.Decorator.__bind__(self, instance_or_class)
, which is called when a method (not for functions), is bound to an instance, or when a class/static method is bound to a class.
Additionally, these properties can be used for figuring out what's been decorated:
is_function
is_method
; either an instance, class- or static methodis_instancemethod
is_classmethod
is_staticmethod
And for methods, is_bound
and is_unbound
are provided.
If you're looking for the original wrapped function, you can find it at
__func__
.
Classy, eh?