writing custom python decorators, and the magic of how they init
Question:
I am reading a very clean piece of code at http://mrcoles.com/blog/3-decorator-examples-and-awesome-python/, but the way it initializes confuses me. I see this class decorator taking ‘object’, but when it runs init, it throws view_func into itself. With view_func
not declared anywhere except in init, if it subclasses object, how did it know that view_func
was the entire function it’s decorating, and that request is the HTTP request?
from functools import wraps
class my_decorator(object):
def __init__(self, view_func):
self.view_func = view_func
wraps(view_func)(self)
def __call__(self, request, *args, **kwargs):
# maybe do something before the view_func call
response = self.view_func(request, *args, **kwargs)
# maybe do something after the view_func call
return response
# how to use it...
def foo(request): return HttpResponse('...')
foo = my_decorator(foo)
# or...
@my_decorator
def foo(request): return HttpResponse('...')
It definitely works, I’m just lost on how it’s working exactly. In my logger.py
:
class log_decorator(object):
logpath = "/home/me/logs"
def __init__(self, func):
self.func = func
wraps(func)(self)
def __call__(self, *args, **kwargs):
this_path = "{}/{}".format(logpath, self.func.__name__)
ret = self.func(*args, **kwargs)
open(this_path, 'w').close()
if ret:
with open(this_path, 'a') as myfile:
myfile.write("Arguments were: {}, {}n".format(args, kwargs))
for line in ret:
l = str(line)
myfile.write(l)
myfile.write('n')
myfile.close()
return ret
Mr. Cole’s class based style helps me write the recent output of any function to a file in loggers named after the function with just
@log_decorator
def smash_lines(lines):
My exact question would then be how does this class know what view_func
and request is, if it is extending object and doesn’t require these params? How do class based decorators initialize themselves? Thank you
Answers:
I’m not quite sure what gets you confused here, but since there is some Python magic involved, a step-by-step explantion seems to be in order:
my_decorator
is a class. my_decorator(foo)
is thus not a simple method call but object creation (which, if you want to be totally correct is also a method call, namely to the __call__()
method of the class my_decorator
). Thus type(my_decorator(foo)) == my_decorator
.
my_decorator(foo)
calls my_decorator.__init__()
which stuffs away the function foo
into self.view_func
inside this new object.
For example
decorated = my_decorator(foo)
print(foo.view_func)
you will get foo
back.
- Most important thing to note is probably that the decorator returns an object.
This means that
@my_decorator
def foo(...):
pass
replaces the original foo()
by the return value from my_decorator(foo)
which is the my_decorator
object we just created. Hence after this line, foo
is an object of type my_decorator
with the original foo()
stuffed inside that object as foo.view_func()
.
- Since
my_decorator
also emulates function calls by overriding the __call__()
method, any call-like operation like foo(request, ...)
gets to call my_decorator.__call__()
, instead. So __call__()
essentially replaced your foo()
view function.
I am reading a very clean piece of code at http://mrcoles.com/blog/3-decorator-examples-and-awesome-python/, but the way it initializes confuses me. I see this class decorator taking ‘object’, but when it runs init, it throws view_func into itself. With view_func
not declared anywhere except in init, if it subclasses object, how did it know that view_func
was the entire function it’s decorating, and that request is the HTTP request?
from functools import wraps
class my_decorator(object):
def __init__(self, view_func):
self.view_func = view_func
wraps(view_func)(self)
def __call__(self, request, *args, **kwargs):
# maybe do something before the view_func call
response = self.view_func(request, *args, **kwargs)
# maybe do something after the view_func call
return response
# how to use it...
def foo(request): return HttpResponse('...')
foo = my_decorator(foo)
# or...
@my_decorator
def foo(request): return HttpResponse('...')
It definitely works, I’m just lost on how it’s working exactly. In my logger.py
:
class log_decorator(object):
logpath = "/home/me/logs"
def __init__(self, func):
self.func = func
wraps(func)(self)
def __call__(self, *args, **kwargs):
this_path = "{}/{}".format(logpath, self.func.__name__)
ret = self.func(*args, **kwargs)
open(this_path, 'w').close()
if ret:
with open(this_path, 'a') as myfile:
myfile.write("Arguments were: {}, {}n".format(args, kwargs))
for line in ret:
l = str(line)
myfile.write(l)
myfile.write('n')
myfile.close()
return ret
Mr. Cole’s class based style helps me write the recent output of any function to a file in loggers named after the function with just
@log_decorator
def smash_lines(lines):
My exact question would then be how does this class know what view_func
and request is, if it is extending object and doesn’t require these params? How do class based decorators initialize themselves? Thank you
I’m not quite sure what gets you confused here, but since there is some Python magic involved, a step-by-step explantion seems to be in order:
my_decorator
is a class.my_decorator(foo)
is thus not a simple method call but object creation (which, if you want to be totally correct is also a method call, namely to the__call__()
method of the classmy_decorator
). Thustype(my_decorator(foo)) == my_decorator
.my_decorator(foo)
callsmy_decorator.__init__()
which stuffs away the functionfoo
intoself.view_func
inside this new object.
For example
decorated = my_decorator(foo)
print(foo.view_func)
you will get foo
back.
- Most important thing to note is probably that the decorator returns an object.
This means that
@my_decorator
def foo(...):
pass
replaces the original foo()
by the return value from my_decorator(foo)
which is the my_decorator
object we just created. Hence after this line, foo
is an object of type my_decorator
with the original foo()
stuffed inside that object as foo.view_func()
.
- Since
my_decorator
also emulates function calls by overriding the__call__()
method, any call-like operation likefoo(request, ...)
gets to callmy_decorator.__call__()
, instead. So__call__()
essentially replaced yourfoo()
view function.