Want to improve this question? Because this question may lead to opinionated discussion, debate, and answers, it has been closed. You mayedit the question if you feel you can improve it so that it requires answers that include facts and citations or a detailed explanation of the proposed solution. If edited, the question will be reviewed and might be reopened.
Closed3 years ago.
The community reviewed whether to reopen this question3 years ago and left it closed:
Original close reason(s) were not resolved
The purpose of my question is to strengthen my knowledge base with Python and get a better picture of it, which includes knowing its faults and surprises. To keep things specific, I'm only interested in the CPython interpreter.
I'm looking for something similar to what learned from myPHP landminesquestion where some of the answers were well known to me but a couple were borderline horrifying.
Update: Apparently one maybe two people are upset that I asked a question that's already partially answered outside of Stack Overflow. As some sort of compromise here's the URLhttp://www.ferg.org/projects/python_gotchas.html
Note that one or two answers here already are original from what was written on the site referenced above.
- Not sure if there are much 'gotchas' moving from 2.5 to 2.6, if your intention is the python 2.X series in general, it may be better to change the title to 2.X.monkut– monkut2009-02-10 00:28:27 +00:00CommentedFeb 10, 2009 at 0:28
- What was wrong with the list inferg.org/projects/python_gotchas.html ?S.Lott– S.Lott2009-02-10 02:19:53 +00:00CommentedFeb 10, 2009 at 2:19
- @S. Lott - Nothing wrong with it, just that I didn't know about it and no one's asked this question in SO.David– David2009-02-10 04:29:26 +00:00CommentedFeb 10, 2009 at 4:29
- 2@hop The top rated answer right now for this question isn't mentioned in the ferg.org page. Maybe if Guido had written the ferg.org page and I had known about it, then I wouldn't have bothered, but no one singular person knows everything.David– David2009-02-10 18:40:53 +00:00CommentedFeb 10, 2009 at 18:40
- 1@S.Lott -What is wrong? ferg.org link is brokenPeter M. - stands for Monica– Peter M. - stands for Monica2018-01-16 20:50:51 +00:00CommentedJan 16, 2018 at 20:50
23 Answers23
Expressions in default arguments are calculated when the function is defined,not when it’s called.
Example: consider defaulting an argument to the current time:
>>>import time>>> def report(when=time.time()):... print when...>>> report()1210294387.19>>> time.sleep(5)>>> report()1210294387.19
Thewhen
argument doesn't change. It is evaluated when you define the function. It won't change until the application is re-started.
Strategy: you won't trip over this if you default arguments toNone
and then do something useful when you see it:
>>> def report(when=None):... if when is None:... when = time.time()... print when...>>> report()1210294762.29>>> time.sleep(5)>>> report()1210294772.23
Exercise: to make sure you've understood: why is this happening?
>>> def spam(eggs=[]):... eggs.append("spam")... return eggs...>>> spam()['spam']>>> spam()['spam', 'spam']>>> spam()['spam', 'spam', 'spam']>>> spam()['spam', 'spam', 'spam', 'spam']
7 Comments
You should be aware of how class variables are handled in Python. Consider the following class hierarchy:
class AAA(object): x = 1class BBB(AAA): passclass CCC(AAA): pass
Now, check the output of the following code:
>>> print AAA.x, BBB.x, CCC.x1 1 1>>> BBB.x = 2>>> print AAA.x, BBB.x, CCC.x1 2 1>>> AAA.x = 3>>> print AAA.x, BBB.x, CCC.x3 2 3
Surprised? You won't be if you remember that class variables are internally handled as dictionaries of a class object.For read operations, if a variable name is not found in the dictionary of current class, the parent classes are searched for it. So, the following code again, but with explanations:
# AAA: {'x': 1}, BBB: {}, CCC: {}>>> print AAA.x, BBB.x, CCC.x1 1 1>>> BBB.x = 2# AAA: {'x': 1}, BBB: {'x': 2}, CCC: {}>>> print AAA.x, BBB.x, CCC.x1 2 1>>> AAA.x = 3# AAA: {'x': 3}, BBB: {'x': 2}, CCC: {}>>> print AAA.x, BBB.x, CCC.x3 2 3
Same goes for handling class variables in class instances (treat this example as a continuation of the one above):
>>> a = AAA()# a: {}, AAA: {'x': 3}>>> print a.x, AAA.x3 3>>> a.x = 4# a: {'x': 4}, AAA: {'x': 3}>>> print a.x, AAA.x4 3
Comments
Loops and lambdas (or any closure, really): variables are bound byname
funcs = []for x in range(5): funcs.append(lambda: x)[f() for f in funcs]# output:# 4 4 4 4 4
A work around is either creating a separate function or passing the args by name:
funcs = []for x in range(5): funcs.append(lambda x=x: x)[f() for f in funcs]# output:# 0 1 2 3 4
Comments
Dynamic binding makes typos in your variable names surprisingly hard to find. It's easy to spend half an hour fixing a trivial bug.
EDIT: an example...
for item in some_list: ... # lots of code... # more codefor tiem in some_other_list: process(item) # oops!
3 Comments
tiem
would be marked as an unused variable.One of the biggest surprises I ever had with Python is this one:
a = ([42],)a[0] += [43, 44]
This works as one might expect, except for raising a TypeError after updating the first entry of the tuple! Soa
will be([42, 43, 44],)
after executing the+=
statement, but there will be an exception anyway. If you try this on the other hand
a = ([42],)b = a[0]b += [43, 44]
you won't get an error.
4 Comments
a[0].extend([43, 44])
.try: int("z")except IndexError, ValueError: pass
reason this doesn't work is because IndexError is the type of exception you're catching, and ValueError is the name of the variable you're assigning the exception to.
Correct code to catch multiple exceptions is:
try: int("z")except (IndexError, ValueError): pass
Comments
There was a lot of discussion on hidden language features a while back:hidden-features-of-python. Where some pitfalls were mentioned (and some of the good stuff too).
Also you might want to check outPython Warts.
But for me, integer division's a gotcha:
>>> 5/22
You probably wanted:
>>> 5*1.0/22.5
If you really want this (C-like) behaviour, you should write:
>>> 5//22
As that will work with floats too (and it will work when you eventually go toPython 3):
>>> 5*1.0//22.0
GvR explains how integer division came to work how it does onthe history of Python.
7 Comments
*1.0
in the vast majority of cases in which complex numbers cannot be involved is better style IMHO because it states what you really intent. Multiplying by 1.0 to achieve this is kind of obfuscating what you want and any unaware reader of the code might be lures to think that the 1.0 might maybe just be a typo (maybe it should 10.0 instead?).Not including an__init__
.py in your packages. That one still gets me sometimes.
Comments
List slicing has caused me a lot of grief. I actually consider the following behavior a bug.
Define a list x
>>> x = [10, 20, 30, 40, 50]
Access index 2:
>>> x[2]30
As you expect.
Slice the list from index 2 and to the end of the list:
>>> x[2:][30, 40, 50]
As you expect.
Access index 7:
>>> x[7]Traceback (most recent call last): File "<stdin>", line 1, in <module>IndexError: list index out of range
Again, as you expect.
However, try to slice the list from index 7 until the end of the list:
>>> x[7:][]
???
The remedy is to put a lot of tests when using list slicing. I wish I'd just get an error instead. Much easier to debug.
3 Comments
The only gotcha/surprise I've dealt with is with CPython's GIL. If for whatever reason you expect python threads in CPython to run concurrently... well they're not and this is pretty well documented by the Python crowd and even Guido himself.
A long but thorough explanation of CPython threading and some of the things going on under the hood and why true concurrency with CPython isn't possible.http://jessenoller.com/2009/02/01/python-threads-and-the-global-interpreter-lock/
2 Comments
James Dumay eloquently reminded me of another Python gotcha:
Not all of Python's “included batteries” are wonderful.
James’ specific example was the HTTP libraries:httplib
,urllib
,urllib2
,urlparse
,mimetools
, andftplib
. Some of the functionality is duplicated, and some of the functionality you'd expect is completely absent, e.g. redirect handling. Frankly, it's horrible.
If I ever have to grab something via HTTP these days, I use theurlgrabber module forked from the Yum project.
4 Comments
Floats are not printed at full precision by default (withoutrepr
):
x = 1.0 / 3y = 0.333333333333print x #: 0.333333333333print y #: 0.333333333333print x == y #: False
repr
prints too many digits:
print repr(x) #: 0.33333333333333331print repr(y) #: 0.33333333333300003print x == 0.3333333333333333 #: True
1 Comment
Unintentionallymixing oldstyle and newstyle classes can cause seemingly mysterious errors.
Say you have a simple class hierarchy consisting of superclass A and subclass B. When B is instantiated, A's constructor must be called first. The code below correctly does this:
class A(object): def __init__(self): self.a = 1class B(A): def __init__(self): super(B, self).__init__() self.b = 1b = B()
But if you forget to make A a newstyle class and define it like this:
class A: def __init__(self): self.a = 1
you get this traceback:
Traceback (most recent call last): File "AB.py", line 11, in <module> b = B() File "AB.py", line 7, in __init__ super(B, self).__init__()TypeError: super() argument 1 must be type, not classobj
Two other questions relating to this issue are489269 and770134
Comments
def f(): x += 1x = 42f()
results in anUnboundLocalError
, because local names are detected statically. A different example would be
def f(): print x x = 43x = 42f()
5 Comments
You cannot use locals()['x'] = whatever to change local variable values as you might expect.
This works:>>> x = 1>>> x1>>> locals()['x'] = 2>>> x2BUT:>>> def test():... x = 1... print x... locals()['x'] = 2... print x # *** prints 1, not 2 ***...>>> test()11
This actually burnt me in an answer here on SO, since I had tested it outside a function and got the change I wanted. Afterwards, I found it mentioned and contrasted to the case of globals() in "Dive Into Python." See example 8.12. (Though it does not note that the change via locals() will work at the top level as I show above.)
1 Comment
x += [...]
is not the same asx = x + [...]
whenx
is a list`
>>> x = y = [1,2,3]>>> x = x + [4]>>> x == yFalse>>> x = y = [1,2,3]>>> x += [4]>>> x == yTrue
One creates a new list while the other modifies in place
Comments
List repetition with nested lists
This caught me out today and wasted an hour of my time debugging:
>>> x = [[]]*5>>> x[0].append(0)# Expect x equals [[0], [], [], [], []]>>> x[[0], [0], [0], [0], [0]] # Oh dear
Explanation:Python list problem
Comments
Using class variables when you want instance variables. Most of the time this doesn't cause problems, but if it's a mutable value it causes surprises.
class Foo(object): x = {}
But:
>>> f1 = Foo()>>> f2 = Foo()>>> f1.x['a'] = 'b'>>> f2.x{'a': 'b'}
You almost always want instance variables, which require you to assign inside__init__
:
class Foo(object): def __init__(self): self.x = {}
Python 2 has some surprising behaviour with comparisons:
>>> print x0>>> print y1>>> x < yFalse
What's going on?repr()
to the rescue:
>>> print "x: %r, y: %r" % (x, y)x: '0', y: 1
4 Comments
TypeError: unorderable types: str() < int()
.print
what comes as a surprise, than what happens in the comparison?x + y
would also lead to something rather unexpected here as all kinds of operations betweenx
andy
.TypeError
in Python 3.5.2. Just try'0' < 1
.If you assign to a variable inside a function, Python assumes that the variable is defined inside that function:
>>> x = 1>>> def increase_x():... x += 1... >>> increase_x()Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in increase_xUnboundLocalError: local variable 'x' referenced before assignment
Useglobal x
(ornonlocal x
in Python 3) to declare you want to set a variable defined outside your function.
Comments
The values ofrange(end_val)
are not only strictly smaller thanend_val
, but strictly smaller thanint(end_val)
. For afloat
argument torange
, this might be an unexpected result:
from future.builtins import rangelist(range(2.89))[0, 1]
7 Comments
range(2.0)
causesTypeError: 'float' object cannot be interpreted as an integer
in both Python 2.7.12 and 3.5.2.future
module'srange
is used.from future.builtins import range
producesImportError: No module named future.builtins
on Python 2.7 and 3.5. Nobody that I know does anything like that anyway. I recommend deletion of the answer. Perhaps it was true fifteen years ago, but that shouldn't matter now.future
package, obviously. The package deals with transitions and compatibility between Python 2 and 3, hence not quite outdated since fifteen years, seepython-future.org. The package allows to write Python 3 code which remains Python 2 compatible. Without any other constraints, this is what everybody should do at this point in time, 2016, IMHO.range
on Python 3.5.2 either. The question is about Python 2.x gotchas, and basically your answer is not.Due to 'truthiness' this makes sense:
>>>bool(1)True
but you might not expect it to go the other way:
>>>float(True)1.0
This can be a gotcha if you're converting strings to numeric and your data has True/False values.
Comments
If you create a list of list this way:
arr = [[2]] * 5 print arr [[2], [2], [2], [2], [2]]
Then this creates an array with all elements pointing to the same object ! This might create a real confusion. Consider this:
arr[0][0] = 5
then if you print arr
print arr[[5], [5], [5], [5], [5]]
The proper way of initializing the array is for example with a list comprehension:
arr = [[2] for _ in range(5)]arr[0][0] = 5print arr[[5], [2], [2], [2], [2]]
Comments
Explore related questions
See similar questions with these tags.