-
Notifications
You must be signed in to change notification settings - Fork 0
/
decorators.html
185 lines (185 loc) · 13.9 KB
/
decorators.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Python decorators</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
</style>
<style>
a.sourceLine { display: inline-block; line-height: 1.25; }
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
a.sourceLine:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode { white-space: pre; position: relative; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
code.sourceCode { white-space: pre-wrap; }
a.sourceLine { text-indent: -1em; padding-left: 1em; }
}
pre.numberSource a.sourceLine
{ position: relative; left: -4em; }
pre.numberSource a.sourceLine::before
{ content: attr(title);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; pointer-events: all; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
a.sourceLine::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
</style>
<link rel="stylesheet" href="style.css" />
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
</head>
<body>
<h1 id="python-decorators">Python decorators</h1>
<p>After working with Python for a while, I decided to do the online introduction to programming and computer science called <a href="http://composingprograms.com/">Composing Programs</a>. In section 1.6.9, the use of decorators is described. However, this is done in a very concise manner and left me with some questions. In this blog post, I will first explain the basic concept of decorator functions and then answer the questions I had.</p>
<h2 id="a-simple-example">A simple example</h2>
<p>Say you have a function called <code>say_hello</code> with the sole purpose of printing “hello” to the console.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb1-1" title="1"><span class="kw">def</span> say_hello():</a>
<a class="sourceLine" id="cb1-2" title="2"> <span class="bu">print</span>(<span class="st">"hello"</span>)</a>
<a class="sourceLine" id="cb1-3" title="3"></a>
<a class="sourceLine" id="cb1-4" title="4">say_hello()</a></code></pre></div>
<p>Running this code, will produce the expected output of <code>hello</code>.</p>
<h3 id="adding-a-decorator">Adding a decorator</h3>
<p>We can add a decorator to this function by using the <code>@</code>-symbol followed by a function name.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb2-1" title="1"><span class="kw">def</span> wrap_function(func):</a>
<a class="sourceLine" id="cb2-2" title="2"> <span class="cf">return</span> func</a>
<a class="sourceLine" id="cb2-3" title="3"></a>
<a class="sourceLine" id="cb2-4" title="4"><span class="at">@wrap_function</span></a>
<a class="sourceLine" id="cb2-5" title="5"><span class="kw">def</span> say_hello():</a>
<a class="sourceLine" id="cb2-6" title="6"> <span class="bu">print</span>(<span class="st">"hello"</span>)</a></code></pre></div>
<p>While this code still produces the same output, the decorator is called. Adding <code>@wrap_function</code> on the line above the definition of <code>say_hello</code> means the <code>say_hello</code> function is passed to <code>wrap_function</code> first and the result is used instead of the <code>say_hello</code> function.</p>
<h3 id="without-decorator">Without decorator</h3>
<p>The code above can also be written in a different way without using decorators, making it easier to understand:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb3-1" title="1"><span class="kw">def</span> wrap_function(func):</a>
<a class="sourceLine" id="cb3-2" title="2"> <span class="cf">return</span> func</a>
<a class="sourceLine" id="cb3-3" title="3"></a>
<a class="sourceLine" id="cb3-4" title="4"><span class="kw">def</span> say_hello():</a>
<a class="sourceLine" id="cb3-5" title="5"> <span class="bu">print</span>(<span class="st">"hello"</span>)</a>
<a class="sourceLine" id="cb3-6" title="6"></a>
<a class="sourceLine" id="cb3-7" title="7">say_hello <span class="op">=</span> wrap_function(say_hello)</a></code></pre></div>
<p>So when we call <code>say_hello</code>, we are actually calling <code>wrap_function</code> with <code>say_hello</code> as an argument. Because the <code>wrap_function</code> function just returns its only argument, the behaviour does not change.</p>
<h3 id="changing-behaviour">Changing behaviour</h3>
<p>We are free to do other things using the decorator, as long as we return a function that can be called in the same way the decorated function (<code>say_hello</code>) can be. For example, we could return a different function:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb4-1" title="1"><span class="kw">def</span> wrap_function(func):</a>
<a class="sourceLine" id="cb4-2" title="2"> <span class="kw">def</span> different_function():</a>
<a class="sourceLine" id="cb4-3" title="3"> <span class="bu">print</span>(<span class="st">"hi"</span>)</a>
<a class="sourceLine" id="cb4-4" title="4"></a>
<a class="sourceLine" id="cb4-5" title="5"> <span class="cf">return</span> different_function</a>
<a class="sourceLine" id="cb4-6" title="6"></a>
<a class="sourceLine" id="cb4-7" title="7"><span class="at">@wrap_function</span></a>
<a class="sourceLine" id="cb4-8" title="8"><span class="kw">def</span> say_hello():</a>
<a class="sourceLine" id="cb4-9" title="9"> <span class="bu">print</span>(<span class="st">"hello"</span>)</a>
<a class="sourceLine" id="cb4-10" title="10"></a>
<a class="sourceLine" id="cb4-11" title="11">say_hello()</a></code></pre></div>
<p>This would output <code>hi</code> instead of <code>hello</code> when we call <code>say_hello</code>.</p>
<h3 id="adding-behaviour">Adding behaviour</h3>
<p>Throwing away the original function and replacing it would an entirely new function doesn’t make much sense outside testing. What if we want to add to the behaviour of the function?</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb5-1" title="1"><span class="kw">def</span> wrap_function(func):</a>
<a class="sourceLine" id="cb5-2" title="2"> <span class="kw">def</span> different_function():</a>
<a class="sourceLine" id="cb5-3" title="3"> <span class="bu">print</span>(<span class="st">"hi"</span>)</a>
<a class="sourceLine" id="cb5-4" title="4"> func()</a>
<a class="sourceLine" id="cb5-5" title="5"></a>
<a class="sourceLine" id="cb5-6" title="6"> <span class="cf">return</span> different_function</a>
<a class="sourceLine" id="cb5-7" title="7"></a>
<a class="sourceLine" id="cb5-8" title="8"><span class="at">@wrap_function</span></a>
<a class="sourceLine" id="cb5-9" title="9"><span class="kw">def</span> say_hello():</a>
<a class="sourceLine" id="cb5-10" title="10"> <span class="bu">print</span>(<span class="st">"hello"</span>)</a>
<a class="sourceLine" id="cb5-11" title="11"></a>
<a class="sourceLine" id="cb5-12" title="12">say_hello()</a></code></pre></div>
<p>Now our new function prints its own message first before calling the original function.</p>
<h3 id="other-things">Other things</h3>
<p>Other interesting things you could do:</p>
<h4 id="call-the-original-function-multiple-times">Call the original function multiple times</h4>
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb6-1" title="1"><span class="kw">def</span> wrap_function(func)</a>
<a class="sourceLine" id="cb6-2" title="2"> <span class="kw">def</span> different_function():</a>
<a class="sourceLine" id="cb6-3" title="3"> func()</a>
<a class="sourceLine" id="cb6-4" title="4"> func()</a>
<a class="sourceLine" id="cb6-5" title="5"></a>
<a class="sourceLine" id="cb6-6" title="6"> <span class="cf">return</span> different_function</a></code></pre></div>
<h4 id="modify-the-output-of-the-original-function">Modify the output of the original function</h4>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb7-1" title="1"><span class="kw">def</span> wrap_function(func):</a>
<a class="sourceLine" id="cb7-2" title="2"> <span class="kw">def</span> different_function():</a>
<a class="sourceLine" id="cb7-3" title="3"> <span class="cf">return</span> <span class="st">"My name is "</span> <span class="op">+</span> func()</a>
<a class="sourceLine" id="cb7-4" title="4"></a>
<a class="sourceLine" id="cb7-5" title="5"> <span class="cf">return</span> different_function</a>
<a class="sourceLine" id="cb7-6" title="6"></a>
<a class="sourceLine" id="cb7-7" title="7"><span class="at">@wrap_function</span></a>
<a class="sourceLine" id="cb7-8" title="8"><span class="kw">def</span> get_name():</a>
<a class="sourceLine" id="cb7-9" title="9"> <span class="cf">return</span> <span class="st">"Tim"</span></a>
<a class="sourceLine" id="cb7-10" title="10"></a>
<a class="sourceLine" id="cb7-11" title="11"><span class="bu">print</span>(get_name())</a></code></pre></div>
<h2 id="why-do-i-need-a-second-function-definition-cant-i-just-use-the-body-of-the-wrapper">Why do I need a second function definition? Can’t I just use the body of the wrapper?</h2>
<p>Imagine you have a function. Before you run that function, you want to execute a different piece of code. A perfect scenario for using decorators!</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><a class="sourceLine" id="cb8-1" title="1"><span class="kw">def</span> wrap_function(func):</a>
<a class="sourceLine" id="cb8-2" title="2"> <span class="bu">print</span>(<span class="st">"I'm inside wrap_function!"</span>)</a>
<a class="sourceLine" id="cb8-3" title="3"> <span class="cf">return</span> func</a>
<a class="sourceLine" id="cb8-4" title="4"></a>
<a class="sourceLine" id="cb8-5" title="5"><span class="at">@wrap_function</span></a>
<a class="sourceLine" id="cb8-6" title="6"><span class="kw">def</span> say_hello():</a>
<a class="sourceLine" id="cb8-7" title="7"> <span class="bu">print</span>(<span class="st">"hello"</span>)</a>
<a class="sourceLine" id="cb8-8" title="8"></a>
<a class="sourceLine" id="cb8-9" title="9">say_hello()</a></code></pre></div>
<p>This would produce the following output:</p>
<pre><code>I'm inside wrap_function!
hello</code></pre>
<p>Seems like the behaviour we expected, right? However, if we take out the last line containing the <code>say_hello</code> function call, we get the following output:</p>
<pre><code>I'm inside wrap_function!</code></pre>
<p>So what’s going on here? We’re seeing this message because the body of <code>wrap_function</code> is executed when the <code>@wrap_function</code> statement is encountered in the code, not when we call the <code>say_hello</code> function. If you want to run a piece of code when the <code>say_hello</code> function is called, is has to be part of the function that is returned by <code>wrap_function</code>.</p>
<h1 id="read-more">Read more</h1>
<ul>
<li><a href="http://composingprograms.com/pages/16-higher-order-functions.html">Composing Programs</a> (section 1.6.9)</li>
<li><a href="https://stackoverflow.com/a/25827070">Decorators with arguments</a></li>
<li><a href="https://docs.python.org/3/reference/compound_stmts.html#function">Python3 docs on function definitions</a></li>
</ul>
</body>
</html>