Tue, 08 Sep 2009
Buggy Python code?
I'm looking for examples of frustratingly simple-yet-wrong Python code, suitable for an undergrad class to debug. I'd prefer things that don't rely on tricky features of Python (like shared list references), but rather code where subtly bad logic or program flow leads to bad behavior.
Comment below, or e-mail me; I'll post the ones I pick later. thanks!
--titus
posted at: 19:13 | path: /sep-09 | 27 comments
Comments:
Posted by Peter Boothe at Tue Sep 8 19:42:06 2009:
<pre>
# Python3 code. Easily translated to 2.x
import random
secret = random.randint(1,100)
guess = -1
while guess != secret:
guess = input("Guess a number!")
guess = int(guess)
if guess > secret:
print("Too high!")
else:
print("Too low!")
print("You got it!)
</pre>
Posted by Nick at Tue Sep 8 20:18:20 2009:
A condensed version of something that cost me a small chunk of time today (adding DB queries and multiple modules into the mix to obscure things). I feel a bit stupid.
python -c "x=3; print 3==x; x="3"; print 3==x"
output:
True
True
Posted by Michele Simionato at Tue Sep 8 20:21:16 2009:
This one is extremely common and I saw it in our codebase at work just yesterday:
def make_list(...):
ls = []
... add something to ls ...
return ls.sort() # instead of sorted(ls)
Posted by Brandon Craig Rhodes at Tue Sep 8 21:00:34 2009:
Gads, Nick! You just wasted more than five minutes of my time. And panicked me that there was something basic that I didn't know about Python. :-)
Posted by John Eikenberry at Tue Sep 8 21:06:58 2009:
# missing comma
>>> list_of_tuples = [
... (1, 'one'),
... (2, 'two')
... (3, 'three')
... ]
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
TypeError: 'tuple' object is not callable
Posted by Adam Vandenberg at Tue Sep 8 21:15:25 2009:
Improperly recursive _getattr_ is always a good one, but is a bit "advanced".
Posted by Richard Jones at Tue Sep 8 22:28:51 2009:
Sadly I have nothing to offer. I would very much appreciate it if you could post a followup with any useful snippets you do find though :)
Posted by Ben Bass at Wed Sep 9 00:36:41 2009:
Not 'wrong', but equivalent has bitten me sometimes, especially if data size varies; looks like the program crashes if suddenly a large data packet comes along...
def eat_chunk(data):
n = get_packet_size(data) # O(1)
packet, leftover = data[:n], data[n:]
process(packet)
return leftover
data = [some large list]
while data:
data = eat_chunk(data)
which is effectively equivalent to this:
data = [some large list]
while data:
a, data = data[0], data[1:]
Problem is O(n**2) running time, but this isn't obvious without knowledge of list implementation (or rather deletions from start being O(n)...)
Posted by Cal at Wed Sep 9 00:53:40 2009:
# Manipulating a list being iterated confuses newbies to no end
r=[1,2,3,4]
for n in r:
print n
r.remove(n)
Posted by Tim Golden at Wed Sep 9 01:22:51 2009:
This, and similar _setattr_ gotchas:
class X (object):
def _init_ (self):
self._attrs = {}
def _setattr_ (self, attr, value):
self._attrs[attr]= value
x = X ()
TJG
Posted by ASG9000 at Wed Sep 9 03:11:42 2009:
Couple of gotchas from a presentation I gave at work:
import time
def pretty_time(when=time.time()):
return time.strftime("%Y/%m/%d %H:%M:%S", time.gmtime(when))
#Another variation of Michele Simionato's post:
for k in d.keys().sort(): print k
#Scoping:
x = 10
def print_x():
print x
def inc_x():
x += 1
print_x()
inc_x()
print_x()
Other ideas:
Use of isinstance() breaking duck typing?
Use of decorators changing method names?
Attempts to modify immutable types, strings, tuples?
Something that happily attempts to use mutable types as keys to a dictionary (e.g. a less fussy version of memoize decorator)?
Using the logical operators to do suprising things?:
s = "duck"; num = 2; s += num > 1 and 's' or ''; print s
Posted by ASG9000 at Wed Sep 9 03:18:10 2009:
Stack overflow is a good place to find newbie python questions, e.g.
http://stackoverflow.com/questions/1395603/trouble-with-simple-python-code
OK, I am going to leave you alone and go back to work now.
Posted by Carl T. at Wed Sep 9 07:21:14 2009:
if somethingistrue:
# declare a variable while doing a bunch of stuff
x = 4
# now do something with variable
y = 20 * x
Often you'll get a NameError for x if the condition up top is brittle.
A consultant I worked with used to do this all of the time. Not really an error, but if you're writing huge 2000 line chunks of code in the outer scope (in and of itself bad practice), this sort of thing can drive you batty.
Carl T.
Posted by j_king at Wed Sep 9 09:11:10 2009:
In some training sessions I've seen some small things bother a few people:
- List comprehensions leak their variable names out of their scope
[x for x in range(10)]
print x # will print '9'
- Ternary operators in Python are different than most other languages
x = x if some_condition(x) else y
- Some people get tripped up on difference between logic and identity operators: 'is', ==, 'and', etc (also sometimes eager evaluations)
Posted by Steffen Oschatz at Wed Sep 9 10:10:38 2009:
#mutable default param
def func(default=[]):
return default
result = func()
result.append(1)
result1 = func()
assert not result1, 'I got you'
assert result is not result1, 'Argh!'
Posted by John at Wed Sep 9 10:44:40 2009:
This has bitten me:
callbacks = []
for val in 1, 2, 3:
def cb():
print val
callbacks.append(cb)
for cb in callbacks:
cb()
To me the logical output seemed to be 1, 2, 3 but instead the output is 3, 3, 3. A workaround is:
def cb(val=val):
print val
John.
Posted by Colbrac at Wed Sep 9 12:19:41 2009:
Some dataprocessing code to split up a big list of data into an unknown number of sublists. The first two list entries are the header (description and unit) and I want those appended in front of each sublist as all functions that work on the data expect them. So (from memory not tested for errors):
def splitData(data):
header = data[:2]
oldtime = data[2][3]
output = [header]
idx = 0
for entry in data[2:]:
if entry[3]-oldtime < 10:
output[idx].append(entry)
else:
output.append(header)
idx += 1
output[idx].append(entry)
return output
Say a delta t of 10 should split up the data in 10 sublists, written like this I end up with an output that is 10 identical sublists, e.g. the same as the original input.
Solution: replace output.append(header) with output.append(header[:]) to create a new instance of the initial header list in memory instead of linking back to the original instance in memory.
P.S. If there is an easier way to split datalists like this, I'm all ears.
Posted by Titus Brown at Wed Sep 9 18:29:39 2009:
Here's what I've come up with so far; thanks for all the help!
http://class.ged.idyll.org/svn/files/lab1/
--titus
(I'm hoping not just to demonstrate that weird things happen, but rather to give them debugging techniques they can use to "attack" the problem. I'll let you all know how it works out.)
Posted by Titus Brown at Thu Sep 10 08:06:52 2009:
p.s. No posting solutions -- I have positive evidence that some of the students are reading this blog...
Posted by David at Thu Sep 10 08:58:45 2009:
The ever-popular copying lists
list_a = ["a", "b", "c"]
list_b = list_a
list_a[1] = "**"
print list_b
['a', '**', 'c']
A good idea BTW which will hopefully nip some bad habits before they get started.
Posted by David at Thu Sep 10 09:07:58 2009:
You see variations on this from experienced programmers. Run the program as is, and then call "some_func()" from another program.
[CODE]def some_func():
print "\nthis function just prints some stuff"
print "to show how a loop works"
outside_ctr = 0
stop = 5
for ctr in range(0, stop):
print ctr, outside_ctr
outside_ctr += incr
if _name_ == "_main_":
incr = 2
some_func() [/CODE]
Posted by David at Thu Sep 10 10:14:42 2009:
This turns up on the forums more than it should (expecting 5 iterations and why does it print these numbers?).
[CODE]x = 0
for x in range(0, 10):
x += 2
print x [/CODE]
Along similar lines
[CODE]empty_list = []
list_of_lists = []
for x in range(0, 5):
list_of_lists.append(empty_list) [/CODE]
My first college professor got our young minds to think about blocks of memory, and away from what would be the word processing mentality today, where there are nice lines in document in a folder (none of which exist unless the programmer creates them). If your students understand blocks of memory instead of variable names, it will save a lot of headaches down the road.
Also, a well placed print statement or try/except is very enlightening.
There are discussions on the forums sometimes about the maximum length of a function or block of code. 10 to 20 lines seems to be the consensus. Smaller blocks of code are much easier to debug.
Posted by David at Thu Sep 10 11:25:05 2009:
This turns up on the forums more than it should (expecting 5 iterations and why does it print these numbers?).
[CODE]x = 0
for x in range(0, 10):
x += 2
print x [/CODE]
Along similar lines
[CODE]empty_list = []
list_of_lists = []
for x in range(0, 5):
list_of_lists.append(empty_list) [/CODE]
My first college professor got our young minds to think about blocks of memory, and away from what would be the word processing mentality today, where there are nice lines in document in a folder (none of which exist unless the programmer creates them). If your students understand blocks of memory instead of variable names, it will save a lot of headaches down the road.
Also, a well placed print statement or try/except is very enlightening.
There are discussions on the forums sometimes about the maximum length of a function or block of code. 10 to 20 lines seems to be the consensus. Smaller blocks of code are much easier to debug.
Posted by David at Thu Sep 10 11:36:45 2009:
This turns up on the forums more than it should (expecting 5 iterations and why does it print these numbers?).
[CODE]x = 0
for x in range(0, 10):
x += 2
print x [/CODE]
Along similar lines
[CODE]empty_list = []
list_of_lists = []
for x in range(0, 5):
list_of_lists.append(empty_list) [/CODE]
My first college professor got our young minds to think about blocks of memory, and away from what would be the word processing mentality today, where there are nice lines in document in a folder (none of which exist unless the programmer creates them). If your students understand blocks of memory instead of variable names, it will save a lot of headaches down the road.
Also, a well placed print statement or try/except is very enlightening.
There are discussions on the forums sometimes about the maximum length of a function or block of code. 10 to 20 lines seems to be the consensus. Smaller blocks of code are much easier to debug.
Posted by David at Thu Sep 10 11:56:34 2009:
This turns up on the forums more than it should (expecting 5 iterations and why does it print these numbers?).
[CODE]x = 0
for x in range(0, 10):
x += 2
print x [/CODE]
Along similar lines
[CODE]empty_list = []
list_of_lists = []
for x in range(0, 5):
list_of_lists.append(empty_list) [/CODE]
My first college professor got our young minds to think about blocks of memory, and away from what would be the word processing mentality today, where there are nice lines in document in a folder (none of which exist unless the programmer creates them). If your students understand blocks of memory instead of variable names, it will save a lot of headaches down the road.
Also, a well placed print statement or try/except is very enlightening.
There are discussions on the forums sometimes about the maximum length of a function or block of code. 10 to 20 lines seems to be the consensus. Smaller blocks of code are much easier to debug.
Posted by Jiri Barton at Thu Sep 17 02:40:29 2009:
identity versus copy
a = [[]] * 3
a[0].append(1)
print a
[[1], [1], [1]]
But you wanted [[1], [], []]!!