In the following minimal example decorate
is called two times. First using @decorate
, second by normal function call decorate(bar)
.
def decorate(func):
print(func.__name__)
return func
@decorate
def bar():
pass
decorate(bar)
Is it possible to see inside of decorate
if the call was invoked by using @decorate
or as a normal function call?
The @decorator
syntax is just syntactic sugar, thus both examples have identical behaviour. This also means whatever distinction you are doing between them might not be as meaningful as you thought.
Although, you can use inspect
to read your script and see how the decorator was called in the above frame.
import inspect
def decorate(func):
# See explanation below
lines = inspect.stack(context=2)[1].code_context
decorated = any(line.startswith('@') for line in lines)
print(func.__name__, 'was decorated with "@decorate":', decorated)
return func
Note that we had to specify context=2
to the inspect.stack
function. The context
argument indicates how many lines of code around the current line must be returned. In some specific cases, such as when decorating a subclass, the current line was on the class declaration instead of the decorator. The exact reason for this behaviour has been explored here.
Example
@decorate
def bar():
pass
def foo():
pass
foo = decorate(foo)
@decorate
class MyDict(dict):
pass
Output
bar was decorated with "@decorate": True
foo was decorated with "@decorate": False
MyDict was decorated with "@decorate": True
Caveat
There are still some corner cases that we can hardly overcome such as linebreaks between the decorator and a class declaration.
# This will fail
@decorate
class MyDict(dict):
pass
Olivier's answer took the thoughts right out of my head. However, as inspect.stack()
is a particularly expensive call, I would consider opting to use something along the lines of:
frame = inspect.getframeinfo(inspect.currentframe().f_back, context=1)
if frame.code_context[0][0].startswith('@'):
print('Used as @decorate: True')
else:
print("Used as @decorate: False")