Skip to content

Commit 4248d13

Browse files
author
Kenneth Reitz
committed
Merge pull request realpython#224 from Julian/gotchas
Started writing about common gotchas with two quick examples
2 parents d8c7b67 + 4675c91 commit 4248d13

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

docs/contents.rst.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ This part of the guide focuses on best practices for writing Python code.
3131
writing/style
3232
writing/documentation
3333
writing/tests
34+
writing/gotchas
3435
writing/license
3536

3637

docs/writing/gotchas.rst

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
Common Gotchas
2+
==============
3+
4+
For the most part, Python aims to be a clean and consistent language that
5+
avoid surprises, but there are a few cases where newcomers to the language
6+
often get tripped up.
7+
8+
Some of these are intentional but potentially surprising. Some could arguably
9+
be considered language warts. In general though, what follows is a collection
10+
of potentially tricky behavior that might seem strange at first glance, but is
11+
generally sensible once you're aware of the underlying cause for the surprise.
12+
13+
14+
.. _default_args:
15+
16+
Mutable Default Arguments
17+
-------------------------
18+
19+
Seemingly the *most* common surprise new Python programmers encounter is
20+
Python's treatment of mutable default arguments in function definitions.
21+
22+
**What You Wrote**
23+
24+
.. code-block:: python
25+
26+
def append_to(element, to=[]):
27+
to.append(element)
28+
return to
29+
30+
**What You Might Have Expected to Happen**
31+
32+
A new list is created each time the function is called if a second argument
33+
isn't provided.
34+
35+
**What Does Happen**
36+
37+
A new list is created *once* when the function is defined, and the same list is
38+
used in each successive call.
39+
40+
Python's default arguments are evaluated *once* when the function is defined,
41+
not each time the function is called (like it is in say, Ruby). This means that
42+
if you use a mutable default argument and mutate it, you *will* and have
43+
mutated that object for all future calls to the function as well.
44+
45+
**What You Should Do Instead**
46+
47+
Create a new object each time the function is called, by using a default arg to
48+
signal that no argument was provided (``None`` is often a good choice).
49+
50+
.. code-block:: python
51+
52+
def append_to(element, to=None):
53+
if to is None:
54+
to = []
55+
to.append(element)
56+
return to
57+
58+
59+
**When the Gotcha Isn't a Gotcha**
60+
61+
Sometimes you specifically can "exploit" (read: use as intended) this behavior
62+
to maintain state between calls of a function. This is often done when writing
63+
a caching function.
64+
65+
66+
Late Binding Closures
67+
---------------------
68+
69+
Another common source of confusion is the way Python binds its variables in
70+
closures (or in the surrounding global scope).
71+
72+
**What You Wrote**
73+
74+
.. code-block:: python
75+
76+
def create_adders():
77+
return [lambda x : i * x for i in range(5)]
78+
79+
**What You Might Have Expected to Happen**
80+
81+
A list containing five functions that each have their own closed-over ``i``
82+
variable that multiplies their argument.
83+
84+
**What Does Happen**
85+
86+
Five functions are created, but all of them just multiply ``x`` by 4.
87+
88+
Python's closures are *late binding*. This means that names within closures are
89+
looked up at the time the inner function is *called*.
90+
91+
Here, whenever *any* of the returned functions are called, the value of ``i``
92+
is looked up in the surrounding scope at call time, when by then the loop has
93+
completed and ``i`` is left with its final value of 4.
94+
95+
What's particularly nasty about this gotcha is the seemingly prevalent
96+
misinformation that this has something to do with ``lambda``\s in Python.
97+
Functions created with a ``lambda`` expression are in no way special, and in
98+
fact the same exact behavior is exhibited by just using an ordinary ``def``:
99+
100+
.. code-block:: python
101+
102+
def create_adders():
103+
for i in range(5):
104+
def adder(x):
105+
return i * x
106+
yield adder
107+
108+
**What You Should Do Instead**
109+
110+
Well. Here the general solution is arguably a bit of a hack. Due to Python's
111+
afformentioned behavior concerning evaluating default arguments to functions
112+
(see :ref:`default_args`), you can create a closure that binds immediately to
113+
its arguments by using a default arg like so:
114+
115+
.. code-block:: python
116+
117+
def create_adders():
118+
return [lambda x, i=i : i * x for i in range(5)]
119+
120+
**When the Gotcha Isn't a Gotcha**
121+
122+
When you want your closures to behave this way. Late binding is good in lots of
123+
situations. Looping to create unique functions is unfortunately a case where
124+
they can cause hiccups.

0 commit comments

Comments
 (0)