diff options
author | Prabhu Ramachandran | 2018-05-16 12:58:27 +0530 |
---|---|---|
committer | Prabhu Ramachandran | 2018-05-16 12:58:27 +0530 |
commit | 36723bdf52e88a49e89e0bb9da92cfaeccdbd70c (patch) | |
tree | 82cad779e4a50a4fca56e4bd40a14fe7622dbdfb /advanced_python | |
parent | f8344f67c22db46cd8bb6a9ed0a0b4c03e06a59e (diff) | |
download | python-workshops-36723bdf52e88a49e89e0bb9da92cfaeccdbd70c.tar.gz python-workshops-36723bdf52e88a49e89e0bb9da92cfaeccdbd70c.tar.bz2 python-workshops-36723bdf52e88a49e89e0bb9da92cfaeccdbd70c.zip |
Adding material on decorators.
Diffstat (limited to 'advanced_python')
-rw-r--r-- | advanced_python/15_decorators.tex | 457 | ||||
-rw-r--r-- | advanced_python/Makefile | 3 |
2 files changed, 459 insertions, 1 deletions
diff --git a/advanced_python/15_decorators.tex b/advanced_python/15_decorators.tex new file mode 100644 index 0000000..77ae8a7 --- /dev/null +++ b/advanced_python/15_decorators.tex @@ -0,0 +1,457 @@ +\documentclass[14pt,compress,aspectratio=169]{beamer} + +\usepackage{hyperref} +\input{macros.tex} + + +\title[Decorators]{Advanced Python} +\subtitle{Decorators} + +\author[FOSSEE] {The FOSSEE Group} + +\institute[IIT Bombay] {Department of Aerospace Engineering\\IIT Bombay} +\date[] {Mumbai, India} + +\begin{document} + +\begin{frame} + \titlepage +\end{frame} + +\begin{frame}[fragile] + \frametitle{Overview} + Decorators: + \begin{itemize} + \item transform a function/method using a function + \item are higher order functions + \item provide a handy syntax + \item are a powerful feature + \item used by many libraries + \end{itemize} +\end{frame} + +\begin{frame}[fragile] + \frametitle{Trivial example} +\begin{lstlisting} +def deco(func): + return func + +@deco +def greet(): + print("Namaste!") + +# @deco is equivalent to: +greet = deco(greet) +\end{lstlisting} +\end{frame} + +\begin{frame}[fragile] + \frametitle{Understanding a bit more} +\begin{lstlisting} +def deco(func): + print("deco") # <-- + return func + +@deco +def greet(): + print("Namaste!") + +greet = deco(greet) +\end{lstlisting} +\end{frame} + +\begin{frame} + \frametitle{Observations} + \begin{itemize} + \item Decorator is a convenient syntax + \item Called when the function is defined + \item Not called every time the decorated function is called + \end{itemize} +\end{frame} + +\begin{frame}[fragile, plain] + \frametitle{Non-trivial example} +Modify the function to print ``Hello'' before the function is called +\begin{lstlisting} +def deco(func): + def new_func(*args, **kw): + print("Hello") + return func(*args, **kw) + return new_func +\end{lstlisting} +\pause +\begin{lstlisting} +@deco +def greet(): + '''Print greeting.''' + print("Namaste!") + +In []: greet() + +\end{lstlisting} +\end{frame} + +\begin{frame} + \frametitle{Observations} + \begin{itemize} + \item Notice the use of the \py{*args, **kw} + \item The target function may have any arguments + \item We replace the original function with \py{new_func} + \item So does this cause any problems? + \end{itemize} +\end{frame} + +\begin{frame}[fragile] + \frametitle{Issues} + \begin{lstlisting} + In []: greet.__name__ + Out[]: 'new_func' + + In []: greet? + + In []: greet.__doc__ + + In []: greet.__module__ + \end{lstlisting} +\end{frame} + +\begin{frame}[fragile] + \frametitle{Using \py{wraps}} + \begin{itemize} + \item Using \py{functools.wraps} prevents these problems + \end{itemize} + + \begin{lstlisting} +from functools import wraps # <-- + +def deco(func): + @wraps(func) # <-- + def new_func(*args, **kw): + print("Hello") + return func(*args, **kw) + return new_func +\end{lstlisting} + +Now try again. +\end{frame} + + +\begin{frame}[fragile] + \frametitle{Decorators taking arguments} + \begin{itemize} + \item Sometimes you want to customize the decorator + \item You wish to pass arguments to the decorator + \end{itemize} +\pause +First attempt: + \begin{lstlisting} +from functools import wraps + +def deco(func, greet='Hello'): + @wraps(func) + def new_func(*args, **kw): + print(greet) + return func(*args, **kw) + return new_func +\end{lstlisting} +\end{frame} + +\begin{frame}[fragile] + \frametitle{Let us try} + \small + \begin{lstlisting} +@deco +def f(): + print('Hi') + +In []: f() +Hello +Hi +\end{lstlisting} +\pause +\begin{lstlisting} +@deco(greet='Namaste') +def f(): + print('Hi') + +TypeError: deco() got an unexpected keyword argument 'greet' + \end{lstlisting} +\end{frame} + +\begin{frame} + \frametitle{Fixing the problem} + \begin{itemize} + \item \py{deco(greet='Namaste')} is not passed the function! + \item So \py{deco(greet='Namaste')} should return a decorator + \vspace*{0.2in} + \end{itemize} +\pause + \begin{block}{Solution} + \begin{itemize} + \item Must return a decorator when called without a function + \item When passed a function just call the decorator + \end{itemize} + \end{block} +\end{frame} + +\begin{frame}[fragile, plain] + \frametitle{Fixing the problems} + \small + \begin{lstlisting} +def deco(func=None, greet='Hello'): + def wrapper(func): # <-- a decorator + @wraps(func) + def new_func(*args, **kw): + print(greet) + return func(*args, **kw) + return new_func + + if func is None: + return wrapper + else: + return wrapper(func) + + \end{lstlisting} +\end{frame} + +\begin{frame}[fragile] + \frametitle{Let us try} + \small + \begin{lstlisting} +@deco +def f(): + print('Hi') + +In []: f() +Hello +Hi +\end{lstlisting} +\pause +\begin{lstlisting} +@deco(greet='Namaste') +def f(): + print('Hi') + +In []: f() +Namaste +Hi + + \end{lstlisting} +\end{frame} + +\begin{frame} + \frametitle{Summary} + \begin{itemize} + \item Creating simple decorators + \item Using \py{functools.wraps} + \item Decorators taking arguments + \end{itemize} +\end{frame} + +\begin{frame}[plain, fragile] + \frametitle{Exercise: simplest decorator} + \begin{block}{} + Write a decorator called \py{greet} that prints \py{'Hello'} before a + the function is called. + \end{block} + \begin{lstlisting} + @greet + def f(x): + print(x) + + In []: f(1) + Hello + 1 +\end{lstlisting} +\end{frame} + + +\begin{frame}[plain, fragile] + \frametitle{Solution} +\begin{lstlisting} +def greet(func): + def new_func(*args, **kw): + print("Hello") + return func(*args, **kw) + return new_func +\end{lstlisting} +\end{frame} + +\begin{frame}[plain, fragile] + \frametitle{Exercise: add goodbye} + \begin{block}{} + Modify decorator \py{greet} to print ``goodbye'' after executing the + function. + \end{block} + \begin{lstlisting} +from functools import wraps +def greet(func): + @wraps(func) + def new_func(*args, **kw): + print("Hello") + return func(*args, **kw) + return new_func +\end{lstlisting} + +\end{frame} + +\begin{frame}[fragile] + \frametitle{Solution} +\begin{lstlisting} +def greet(func): + @wraps(func) + def new_func(*args, **kw): + print("Hello") + result = func(*args, **kw) + print("goodbye") + return result + return new_func +\end{lstlisting} +\end{frame} + +\begin{frame}[plain, fragile] + \frametitle{Exercise: print function name} + \begin{block}{} + Write a decorator called \py{debug()} that prints the name of the function + before it is called. \textbf{Hint:} Recall \py{__name__} + \end{block} + +\begin{lstlisting} + @debug + def my_func(x): + print(x) + + In []: f(1) + my_func + 1 +\end{lstlisting} +\end{frame} + + +\begin{frame}[plain, fragile] + \frametitle{Solution} +\begin{lstlisting} +from functools import wraps + +def debug(func): + @wraps(func) + def new_func(*args, **kw): + print(func.__name__) + return func(*args, **kw) + return new_func +\end{lstlisting} +\end{frame} + + +\begin{frame}[plain, fragile] + \frametitle{Exercise: print function name with optional message} + \begin{block}{} + Write a decorator called \py{debug(f, message='')} that prints the name of + the function before it is called along with an optional message. + \end{block} + +\begin{lstlisting} + @debug(message='DEBUG: ') + def my_func(x): + print(x) + + In []: f(1) + DEBUG: my_func + 1 +\end{lstlisting} +\end{frame} + + +\begin{frame}[plain, fragile] + \frametitle{Solution} + \vspace*{-0.1in} + \small +\begin{lstlisting} +from functools import wraps + +def debug(func=None, message=''): + def wrapper(func) + @wraps(func) + def new_func(*args, **kw): + if message: + print(message, func.__name__) + else: + print(func.__name__) + return func(*args, **kw) + return new_func + if func: + return wrapper(func) + else: + return wrapper +\end{lstlisting} +\end{frame} + + +\begin{frame}[plain, fragile] + \frametitle{Exercise: track number of calls} + \begin{block}{} + Write a decorator called \py{counter()} that keeps track of the number of + times a decorated function is called. Write another function called + \py{show_counts()} which lists all the called functions. + \end{block} + + \small +\begin{lstlisting} +@counter +def sq(x): return x*x + +@counter +def g(x): return x+2 + +In []: for i in range(10): g(sq(i)) +In []: show_counts() +sq : 10 +g : 10 + +\end{lstlisting} +\end{frame} + + +\begin{frame}[plain, fragile] + \frametitle{Solution} +\begin{lstlisting} +_counter_data = {} + +def counter(func): + @wraps(func) + def new_func(*args, **kw): + if func in _counter_data: + _counter_data[func] += 1 + else: + _counter_data[func] = 1 + return func(*args, **kw) + return new_func +\end{lstlisting} +\end{frame} + +\begin{frame}[plain, fragile] + \frametitle{Solution} +\begin{lstlisting} +def show_counts(): + for f, c in _counter_data.items(): + print(f.__name__, ':', c) +\end{lstlisting} +\end{frame} + +\begin{frame} + \frametitle{Homework} + \begin{block}{} + Convert the above into a class so that the global \py{_counter_data} and + the decorator is encapsulated into the class.\\ + + Hints: + \begin{itemize} + \item A decorator can be any callable, even a method + \item You can override the \py{__call__} special method to make the object callable + \end{itemize} + + \end{block} +\end{frame} + + +\end{document} diff --git a/advanced_python/Makefile b/advanced_python/Makefile index 8e7ccd5..05796a7 100644 --- a/advanced_python/Makefile +++ b/advanced_python/Makefile @@ -17,7 +17,8 @@ SLIDES= 01_intro.pdf \ 11_oop_misc.pdf \ 12_oop_simple_special_methods.pdf \ 13_oop_special_methods.pdf \ - 14_oop_multiple_inheritance.pdf + 14_oop_multiple_inheritance.pdf \ + 15_decorators.pdf all: $(SLIDES) |