Decorator
Syntax
@dec
def foo(...):
...
# Still called by
>>> foo()
@ is a syntax sugar. Under the hood, it is:
foo = dec(foo)
>>> foo()
Attention
This syntax sugar assignment is executed during decorated function definition not on function call.
Decorator doesn’t change function name, foo is still foo. So the thing below @dec is the input of decorator. In above example, it accepts a func and return a new func. Decorator also accept arguments.
@dec(param=something)
def func(...):
...
>>> func()
It could be translated to:
true_dec = dec(param=something)
func = true_dec(func)
>>> func()
It reveals the logic under the hood is calculating function call dec(a=something) first which returns the new decorator for later process.
Chained decorators
@outside
@inside
def func(...):
...
Again, this will translate to thing below during func() definition. Intuitively, the rule is parsing the inside decorator first and then outside.
func = outside(inside(func))
Write a decorator
When writing a decorator dec, Let’s assume: it accepts a func and returns a func. So initially, it should looks like:
def dec(func):
...
return newfunc
newfunc is a function which needs to be defined inside dec. So it looks like:
def dec(func):
...
def newfunc(...):
...
return newfunc
Since we return newfunc and newfunc is the function we will use later, this raise two considerations:
newfuncis a wrapper(from the iteral meaning of decorator) offunc, sofuncusually is called insidenewfunc.Keep in mind
newfuncis the function returned at the end. In definition,newfuncneed to pass any arguments it accepts tofunc. So*argsand**kwargsshould be used.
#1 makes it look like:
def dec(func):
...
def newfunc(...):
...
func(...)
...
return newfunc
Address #2:
def dec(func):
...
def newfunc(*args, **kwargs):
...
func(*args, **kwargs)
...
return newfunc
Eventually, a runable decorator example shows below:
def dec(func):
def newfunc(*arg, **kwargs):
print('calling func')
func(*arg, **kwargs)
print('end func')
return newfunc
@dec
def foo():
print('abc')
>>> foo()
calling func
abc
end func
Write a decorator with parameters
Again, It runs the function with parameters first which return true decorator. So the definition looks like:
def dec(param):
# Write a decorator definition inside and return it.
return newdec
If we put true decorator definition inside, it will looks like. (... might involve using param).
def dec(param):
...
def newdec(func)
...
def newfunc(*arg, **kwargs)
...
func(*arg, **kwargs)
...
return newfunc
...
...
return newdec
Class decorator
Class decorator is nothing more. If a function’s decorator is a function that accepts a function and returns a new function. Then class’s decorator is a function that accepts a class and returns a new class.
def dec(cls):
class newcls(cls):
attr = 100
return newcls
@dec
class C:
pass
>>> obj = C()
>>> C.attr
100
Make any callable become a decorator
Decorator syntax is nothing more than x = dec(x). dec has to be callable, clearly function is callable, so in above example decorator is a function/class that accepts X and return a new-X. However, pure function is not the only callable thing. When construct a instance, You use CLASS in a callable way.
class Dec1:
def __init__(self, func):
print('__init__')
self.func = func
def __call__(self, *args, **kwargs):
print('__call__')
self.func(*args, **kwargs)
@Dec1
def foo(a, b):
print('foo', a, b)
>>> foo(2, 3)
# __init__
# __call__
# foo 2 3
In this example, we implicit have foo = Dec1(foo) on global level. foo become a instance of Dec1.
If func = dec(func) happend inside Class, on the left hand side, func become a class attribute. When you use func, instead of call func(), you need obj.func. This gives us a chance to sneak true function object inside return value of __get__, aka non-data descriptor. This is exactly logic under the hood of staticmethod.
class Dec2:
def __init__(self, func):
print('__init__')
self.func = func
def __get__(self, obj, type=None):
print('__get__')
return self.func
class C:
@Dec2
def func2(a, b):
print('func2', a, b)
>>> obj = C()
>>> obj.func2(2, 3)
In this example, we implicit have func2 = Dec2(func2) inside class C. func2 is a class attribute in C. And most important func2 is a non-data descriptor defined by __get__ method in class Dec2.
When you write a decorator e.g. Dec, Dec has to be callable to achive Dec(...). But what is returned and how to use returned value is depend on you. If you want, you could have this(btw, `...` below isn’t emoji, but it kind of reflect correct feeling):
def dimensionality_reduction_attack(func):
return 1
@dimensionality_reduction_attack
def very_funcy_function():
...
>>> very_funcy_function
1
Finally, I put simplest decorator here for comparasion.
def dec3(func):
print("newfunc")
def newfunc(*args, **kwargs):
func(*args, **kwargs)
return newfunc
@dec3
def func3(a, b):
print('func3', a, b)
func3(2, 3)
# newfunc
# func3 2 3
Again, decorator is nothing more than X = dec(X), literally and visually.
Don’t use this code
This is LEGAL in Python.
@(lambda call: lambda func: (lambda *args, **kwargs: getattr(func(*args, **kwargs), call)()))('upper')
def greet():
return 'hello world'
>>> greet()
'HELLO WORLD'