In Which we Discover Some Rules About Python Scoping (which we already knew)

· 323 Words

I’ve just been bitten by scoping in Python. If you gave me this code and asked me what it did, I’d probably guess that it was a trick question and look carefully at it. What would you say?

def demo():
number = 100
one = number / 100

numbers = [one, one+1, one+2]
big_numbers = [number * 100 for number in numbers]
bigger_numbers = [big_number * 100 for big_number in big_numbers]
print number

 

It’s not obvious. You’d be forgiven for saying 100. Forgiven by me, unfortunately not forgiven by the Python interpreter. When you’re reading and writing code day to day you’re not always on the look-out for trick questions. The Python interpreter specialises in looking out for trick questions (aka correct behaviour), and would give you the correct  answer of 3.

The mistake I made was to re-use a variable name in a list comprehension.

The problem here is that there is no extra scoping in the list comprehension. The variable number isn’t bound in some kind of magical special scope for the list comprehension. So the binding of number in the list comprehension is the same as number in the function closure scope. The value of number in the function closure is the last value it was assigned, in the last iteration of the loop. In many languages (Java, C#, C, Haskell, Scala) this wouldn’t happen and the function would print the number 100. This is a symptom of Python taking functional features (list comprehensions in this case) from purer languages without the attendant purity.

This closure scoping is something about Python that I really don’t like. And JavaScript for that matter. (At least in JavaScript functions are cheap and plentiful supply, and can be used as callbacks to an iteration, as in underscore.js’s _.each.) A little lexical scoping wouldn’t kill anyone.

I’ve been writing Python for years and this still bit me. Look out, you may be the next victim.

Read more