Skip to content

Commit bb04df7

Browse files
committed
Update decorators.rst
Added a section on passing parameters into decorators, and decorators as classes
1 parent 12b8cb2 commit bb04df7

1 file changed

Lines changed: 120 additions & 0 deletions

File tree

decorators.rst

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,123 @@ Logging is another area where the decorators shine. Here is an example:
321321
# Output: addition_func was called
322322
323323
I am sure you are already thinking about some clever uses of decorators.
324+
325+
Decorators with Arguments
326+
^^^^^^^^^^^^^^^^^^^^^^^^^
327+
328+
Come to think of it, isn't ``@wraps`` also a decorator? But, it takes an
329+
argument like any normal function can do. So, why can't we do that too?
330+
331+
This is because when you use the ``@my_decorator`` syntax, you are
332+
applying a wrapper function with a single function as a parameter
333+
Remember, everything in Python is an object, and this includes
334+
functions! With that in mind, we can write a function that returns
335+
a wrapper function.
336+
337+
Nesting a Decorator Within a Function
338+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
339+
340+
Let's go back to our logging example, and create a wrapper which lets
341+
us specify a logfile to output to.
342+
343+
.. code:: python
344+
345+
from functools import wraps
346+
347+
def logit(logfile='out.log'):
348+
def logging_decorator(func):
349+
@wraps(func)
350+
def wrapped_function(*args, **kwargs):
351+
log_string = func.__name__ + " was called"
352+
print(log_string)
353+
# Open the logfile and append
354+
with open(logfile, 'a') as opened_file:
355+
# Now we log to the specified logfile
356+
opened_file.write(log_string + '\n')
357+
return wrapped_function
358+
return logging_decorator
359+
360+
@logit()
361+
def myfunc1():
362+
pass
363+
364+
myfunc1()
365+
# Output: myfunc1 was called
366+
# A file called out.log now exists, with the above string
367+
368+
@logit(logfile='func2.log')
369+
def myfunc2():
370+
pass
371+
372+
myfunc2():
373+
pass
374+
375+
myfunc2()
376+
# Output: myfunc2 was called
377+
# A file called func2.log now exists, with the above string
378+
379+
Decorator Classes
380+
~~~~~~~~~~~~~~~~~
381+
382+
Now we have our logit decorator in production, but when some parts
383+
of our application are considered critical, failure might be
384+
something that needs more immediate attention. Let's say sometimes
385+
you want to just log to a file. Other times you want an email sent
386+
the problem is brought to your attention, and still keep a log
387+
for your own records. This is a case for using inheritence, but
388+
so far we've only seen functions being used to build decorators.
389+
390+
Luckily, classes can also be used to build decorators. So, let's
391+
rebuild logit as a class instead of a function.
392+
393+
.. code:: python
394+
395+
class logit(object):
396+
def __init__(self, logfile='out.log'):
397+
self.logfile = logfile
398+
399+
def __call__(self, func):
400+
log_string = func.__name__ + " was called"
401+
print(log_string)
402+
# Open the logfile and append
403+
with open(self.logfile, 'a') as opened_file:
404+
# Now we log to the specified logfile
405+
opened_file.write(log_string + '\n')
406+
# Now, send a notification
407+
self.notify()
408+
409+
def notify(self):
410+
# logit only logs, no more
411+
pass
412+
413+
This implementation has an additional advantage of being much cleaner than
414+
the nested function approach, and wrapping a function still will use
415+
the same syntax as before:
416+
417+
.. code:: python
418+
419+
@logit()
420+
def myfunc1():
421+
pass
422+
423+
Now, let's subclass logit to add email functionality (though this topic
424+
will not be covered here).
425+
426+
.. code:: python
427+
428+
class email_logit(logit):
429+
'''
430+
A logit implementation for sending emails to admins
431+
when the function is called.
432+
'''
433+
def __init__(self, email='admin@myproject.com', *args, **kwargs):
434+
self.email = email
435+
super(logit, self).__init__(*args, **kwargs)
436+
437+
def notify(self):
438+
# Send an email to self.email
439+
# Will not be implemented here
440+
pass
441+
442+
From here, ``@email_logit`` works just like ``@logit`` but sends an email
443+
to the admin in addition to logging.

0 commit comments

Comments
 (0)