Do all decorator classes need __call__
?
decorator class should contain __call__
or __new__
method
Not all decorator classes need to implement __call__
.
It's only required when we want to call the decorated object with ()
.
A decorator class that takes a callable to produce a callable has to implement __call__
.
In this example, __call__
is implemented because we want to do data.calculate()
.
# Decorator to call and cache the function immediately
class PreCompute:
def __init__(self, func):
self.value = func()
def __call__(self, *args, **kwds):
return self.value
class Data:
@PreCompute
def calculate():
print("Data.calculate called")
return 42
data = Data()
# This actually calls PreCompute's __call__
print(data.calculate())
The definition of class Data
here is roughly desugared to something like this,
so when calling data.calculate()
we're actually calling the __call__
function from class PreCompute
.
class Data:
def calculate():
print("Data.calculate called")
return 42
calculate = PreCompute(calculate)
A decorator class that takes a callable but does not produce a callable does not have to implement __call__
.
For example, we can modify the class Precompute
decorator to the following code, which allows us to access data.calculate
as if it's an attribute.
For more information about what __get__
does, see Descriptor HowTo Guide from Python docs.
class PreCompute:
def __init__(self, func):
self.value = func()
def __get__(self, instance, owner):
return self.value
class Data:
@PreCompute
def calculate():
print("Data.calculate called")
return 42
data = Data()
# Access .calculate like an attribute
print(data.calculate)
What about __new__
?
I'm not sure how OP got the impression that decorator classes must define either __call__
or __new__
. I've seen __new__
being defined for use cases like @singleton
decorator for classes, but as discussed in the previous section about __call__
, this is also not strictly required. The only function we must define is an __init__
that receives the object to be decorated.
How does @functools.cached_property
work, then?
Now going back to the question, notice from the documentation of @functools.cached_property
that
it "transform a method of a class into a property", which is to be accessed without the parentheses ()
.
Therefore, class cached_property
implements __get__
but not __call__
, which is similar to the second example above.