blog

Python Gotchas

by

There are only 2 gotchas in Python. The stack overflow post lists them, but also a bunch of other stuff. I ran into both of the gotchas in the space of a week recently and spent some time trying to figure out where the bug was. Here they are in case you find yourself running into them too.

1. Default arguments are evaluated when the function is defined.
Which seems reasonable. Except it leads to things that you created at different times saying they were created at the same time:
[sourcecode language=”python”]
from datetime import datetime
import time
class Thing(object):
def __init__(self, createdDate=datetime.now()):
self.createdDate = createdDate
a = Thing()
time.sleep(1)
b = Thing()
print a.createdDate
print b.createdDate
# 2013-04-08 08:52:31.658985
# 2013-04-08 08:52:31.658985
[/sourcecode]
and nodes all having the same children (because they have the same list instance in self.children):
[sourcecode language=”python”]
class Node(object):
def __init__(self, name, children=[]):
self.name = name
self.children = children
def __repr__(self):
return self.name
a = Node(‘parent’)
b = Node(‘child’)
a.children.append(b)
print a.children
# [child]
print b.children
# [child]
[/sourcecode]
The typical pattern for not running into this is to have empty default args be None, and then check for None:
[sourcecode language=”python”]
class Node(object):
def __init__(self, name, children=None):
self.name = name
self.children = children
if self.children is None:
self.children = []
def __repr__(self):
return self.name
a = Node(‘parent’)
b = Node(‘child’)
a.children.append(b)
print a.children
# [child]
print b.children
# []
[/sourcecode]
2. Variables in loops and functions or lambas are bound by name.
If you want to make functions in a loop, you have to be careful about how you write the function. If you use a variable from the loop in your function it will have the value of the last thing it gets set to.
[sourcecode language=”python”]
ns = []
for fruit in [‘apple’, ‘banana’, ‘orange’]:
def aFunction():
print fruit
functions.append(aFunction)
for fn in functions:
fn()
# orange
# orange
# orange
lambdas = [lambda: x for x in [1, 2, 3]]
for fn in lambdas:
print fn()
# 3
# 3
# 3
x = 57
for fn in lambdas:
print fn()
# 57
# 57
# 57
[/sourcecode]
Which is another way of saying that if you use an outside variable in a function when you call the function it’ll look up whatever the value of the variable is now, and use that. Which isn’t so surprising:
[sourcecode language=”python”]
what = ‘hello’
def sayWhat():
print what
sayWhat()
# hello
what = ‘how are you?’
sayWhat()
# how are you?
what = ‘toodle pip’
sayWhat()
# toodle pip
[/sourcecode]
And of course setting a variable in a loop changes whatever that variable was before:
[sourcecode language=”python”]
x = 27
for x in range(3):
pass
print x
# 2
[/sourcecode]
so maybe this isn’t really a gotcha so much. You can work around this with default arguments (which are as you may have heard evaluated at function definition time):
[sourcecode language=”python”]
lambdas = [lambda myNum=i: myNum for i in [1, 7, 13]]
for l in lambdas:
print l()
# 1
# 7
# 13
[/sourcecode]
So:
Default arguments are evaluated when the the function definition is evaluated. Not when the function is run.
and
A function using a variable from an outer scope will look up what the value of that variable is when the function is run. Which might catch you off guard if you’re creating functions in loops.

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more