From fd9f0db1cefcecee9579ab9b38d04d38ddecce28 Mon Sep 17 00:00:00 2001 From: Julian Berman Date: Mon, 3 Dec 2012 22:26:40 -0500 Subject: [PATCH 1/2] First gotcha. --- docs/contents.rst.inc | 1 + docs/writing/gotchas.rst | 61 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 docs/writing/gotchas.rst diff --git a/docs/contents.rst.inc b/docs/contents.rst.inc index ca36983..31151a0 100644 --- a/docs/contents.rst.inc +++ b/docs/contents.rst.inc @@ -31,6 +31,7 @@ This part of the guide focuses on best practices for writing Python code. writing/style writing/documentation writing/tests + writing/gotchas writing/license diff --git a/docs/writing/gotchas.rst b/docs/writing/gotchas.rst new file mode 100644 index 0000000..e5e2d9b --- /dev/null +++ b/docs/writing/gotchas.rst @@ -0,0 +1,61 @@ +Common Gotchas +============== + +For the most part, Python aims to be a clean and consistent language that +avoid surprises, but there are a few cases where newcomers to the language +often get tripped up. + +Some of these are intentional but surprising. Some could arguably be considered +language warts. In general though, what follows is a collection of potentially +tricky behavior that might seem strange at first glance, but is generally +sensible despite surprise after learning of its existence. + + +Mutable Default Arguments +------------------------- + +Seemingly the *most* common surprise new Python programmers encounter is +Python's treatment of mutable default arguments in function definitions. + +**What You Wrote** + +.. code-block:: python + + def append_to(element, to=[]): + to.append(element) + return to + +**What You Might Have Expected to Happen** + +A new list is created each time the function is called if a second argument +isn't provided. + +**What Does Happen** + +A new list is created *once* when the function is defined, and the same list is +used in each successive call. + +Python's default arguments are evaluated *once* when the function is defined, +not each time the function is called (like it is in say, Ruby). This means that +if you use a mutable default argument and mutate it, you *will* and have +mutated that object for all future calls to the function as well. + +**What You Should Do Instead** + +Create a new object each time the function is called, by using a default arg to +signal that no argument was provided (``None`` is often a good choice). + +.. code-block:: python + + def append_to(element, to=None): + if to is None: + to = [] + to.append(element) + return to + + +**When the Gotcha Isn't a Gotcha** + +Sometimes you specifically can "exploit" (read: use as intended) this behavior +to maintain state between calls of a function. This is often done when writing +a caching function. From 4675c91fc598cdded4b2e7a235e947eda3d7db24 Mon Sep 17 00:00:00 2001 From: Julian Berman Date: Tue, 4 Dec 2012 00:39:23 -0500 Subject: [PATCH 2/2] Late binding closures --- docs/writing/gotchas.rst | 71 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/docs/writing/gotchas.rst b/docs/writing/gotchas.rst index e5e2d9b..15e9004 100644 --- a/docs/writing/gotchas.rst +++ b/docs/writing/gotchas.rst @@ -5,12 +5,14 @@ For the most part, Python aims to be a clean and consistent language that avoid surprises, but there are a few cases where newcomers to the language often get tripped up. -Some of these are intentional but surprising. Some could arguably be considered -language warts. In general though, what follows is a collection of potentially -tricky behavior that might seem strange at first glance, but is generally -sensible despite surprise after learning of its existence. +Some of these are intentional but potentially surprising. Some could arguably +be considered language warts. In general though, what follows is a collection +of potentially tricky behavior that might seem strange at first glance, but is +generally sensible once you're aware of the underlying cause for the surprise. +.. _default_args: + Mutable Default Arguments ------------------------- @@ -59,3 +61,64 @@ signal that no argument was provided (``None`` is often a good choice). Sometimes you specifically can "exploit" (read: use as intended) this behavior to maintain state between calls of a function. This is often done when writing a caching function. + + +Late Binding Closures +--------------------- + +Another common source of confusion is the way Python binds its variables in +closures (or in the surrounding global scope). + +**What You Wrote** + +.. code-block:: python + + def create_adders(): + return [lambda x : i * x for i in range(5)] + +**What You Might Have Expected to Happen** + +A list containing five functions that each have their own closed-over ``i`` +variable that multiplies their argument. + +**What Does Happen** + +Five functions are created, but all of them just multiply ``x`` by 4. + +Python's closures are *late binding*. This means that names within closures are +looked up at the time the inner function is *called*. + +Here, whenever *any* of the returned functions are called, the value of ``i`` +is looked up in the surrounding scope at call time, when by then the loop has +completed and ``i`` is left with its final value of 4. + +What's particularly nasty about this gotcha is the seemingly prevalent +misinformation that this has something to do with ``lambda``\s in Python. +Functions created with a ``lambda`` expression are in no way special, and in +fact the same exact behavior is exhibited by just using an ordinary ``def``: + +.. code-block:: python + + def create_adders(): + for i in range(5): + def adder(x): + return i * x + yield adder + +**What You Should Do Instead** + +Well. Here the general solution is arguably a bit of a hack. Due to Python's +afformentioned behavior concerning evaluating default arguments to functions +(see :ref:`default_args`), you can create a closure that binds immediately to +its arguments by using a default arg like so: + +.. code-block:: python + + def create_adders(): + return [lambda x, i=i : i * x for i in range(5)] + +**When the Gotcha Isn't a Gotcha** + +When you want your closures to behave this way. Late binding is good in lots of +situations. Looping to create unique functions is unfortunately a case where +they can cause hiccups.