@@ -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