64
Closed. This question isopinion-based. It is not currently accepting answers.

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.

ForceBru's user avatar
ForceBru
45k10 gold badges71 silver badges104 bronze badges
askedFeb 9, 2009 at 23:25
David's user avatar
7
  • 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.CommentedFeb 10, 2009 at 0:28
  • What was wrong with the list inferg.org/projects/python_gotchas.html ?CommentedFeb 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.CommentedFeb 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.CommentedFeb 10, 2009 at 18:40
  • 1
    @S.Lott -What is wrong? ferg.org link is brokenCommentedJan 16, 2018 at 20:50

23 Answers23

85

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']
answeredFeb 10, 2009 at 1:27
Garth Kidd's user avatar
Sign up to request clarification or add additional context in comments.

7 Comments

+1 Excellent point! I actually have relied on this in a similar context, but I could easily see this catching the unwary off guard!
That's the most well known gotcha, but I've never been bitten by it before knowing it.
The same is true for class level variables (an easy mistake to make when first learning python)
The Python designers made a lot of good design decisions, but this was no one of them. +1
You could use tuple instead of list as default argument. In general - default values should be of unchangeable type (NoneType, int, tuple etc.)
|
62

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
Him's user avatar
Him
5,5654 gold badges43 silver badges98 bronze badges
answeredFeb 10, 2009 at 7:12
Dzinx's user avatar

Comments

41

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
answeredFeb 10, 2009 at 7:39
Richard Levasseur's user avatar

Comments

20

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!
answeredFeb 9, 2009 at 23:43
Algorias's user avatar

3 Comments

+1 Yeah that's kind of screwed me up once or twice, any chance you could provide an example in your answer though?
I suppose so, but this was just for illustration's sake. Actual ocurrences of this type of bug tend to be a bit more involved.
You can use static checkers like PyLint to find these mistakes --tiem would be marked as an unused variable.
18

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.

answeredNov 26, 2010 at 14:00
Sven Marnach's user avatar

4 Comments

Or you could simply write:a[0].extend([43, 44]).
Wow. I consider changing and raising an exception afterwards a bug in Python. Any reason why this might be just a wart?
WOW. I expected the error, but I didn't expect it to actuallymodify the list too. That's ugly. However, I don't believe I've ever run into this because I make a habit of not using tuples with data that I want to change. Even if it points to the same list, I'd rather the values remain immutable. If I want to have positional elements that can change, I'd either use a list, dictionary or class.
It's mentioned atdocs.python.org/2/faq/…
16
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
answeredDec 9, 2010 at 22:13
user537122's user avatar

Comments

12

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.

answeredFeb 10, 2009 at 13:10
Tom Dunham's user avatar

7 Comments

Definitely a gotcha. It's gotten so than adding "fromfuture import division" to every new .py file I create is practically a reflex.
Makes sense supposing that 5 and 2 are actually variables. Otherwise you could just write 5./2
Why are you multiplying by 1.0? Wouldn't it be just as easy to make 5 be 5.0 or float(5) in case 5 is hidden in a variable.
"The correct work-around is subtle: casting an argument to float() is wrong if it could be a complex number; adding 0.0 to an argument doesn't preserve the sign of the argument if it was minus zero. The only solution without either downside is multiplying an argument (typically the first) by 1.0. This leaves the value and sign unchanged for float and complex, and turns int and long into a float with the corresponding value." (PEP 238 -python.org/dev/peps/pep-0238)
Using float() instead of*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?).
|
11

Not including an__init__.py in your packages. That one still gets me sometimes.

answeredMay 20, 2009 at 23:58
Jason Baker's user avatar

Comments

11

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.

answeredMay 13, 2011 at 7:28
Viktiglemma's user avatar

3 Comments

I agree. It really hides those one-off bugs.
This one is actually quite predictable for Python and is useful when iterating over empty slices.
I don't think this is undesirable behavior. If you think about the logic behind slicing, this is something predictable and desirable... it's a bit like doing list comprehension with no list index satisfying the constraint that you put on the list to define it. That's useful!
9

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/

answeredFeb 9, 2009 at 23:31
David's user avatar

2 Comments

check out the new multiprocessing module available in 2.6 for thread-like handling using separate processes if the GIL is bothering you.docs.python.org/library/multiprocessing.html
@David - must have been pyprocessing which has been made part of the standard libraries under the guise of multiprocessing
9

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.

answeredFeb 11, 2009 at 6:02
Garth Kidd's user avatar

4 Comments

I remember a couple years back giving up trying to accomplish what I wanted with the suite of tools above and ended up using pyCurl.
The fact that there's a module named urllib and a module named urllib2 still gets under my skin.
This is probably the real reason for Python 3 :) They got to the point of, 'wait, where's the... let's start over'.
Python 3 is better organized with regard to these libraries.
7

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
answeredMay 20, 2009 at 23:49
pts's user avatar

1 Comment

This is a compromise so that the float string is reasonably portable across python's platforms, since python uses hardware floats.
7

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

answeredJul 19, 2009 at 20:12
Dawie Strauss's user avatar

Comments

7
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()
answeredNov 26, 2010 at 13:43
Sven Marnach's user avatar

5 Comments

If you use global x inside of f(), that will allow you to reference variables outside of f's scope.
@David: I know why you get an error. The point of the post is that most people don'texpect to get an error here, so it's a gotcha.
I was commenting more for the sake of others, not all but most of the gotchas here have solutions. For myself, I didn't even know about the global symbol/operator until two years after I started using Python.
Using global variables such as that is a bad code smell, anyways. I'm glad you didn't come across it till 2 years in. Means you're not getting functions to perform on global scope variables.
@ZoranPavlovic: You get the same issue when using closures instead of global variables. A counter implemented as a closure would be perfectly reasonable, but you have to be careful.
5

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.)

Bill the Lizard's user avatar
Bill the Lizard
407k213 gold badges577 silver badges892 bronze badges
answeredJul 26, 2009 at 9:13
Anon's user avatar

1 Comment

locals() at module level is the same thing as globals() anywhere in the module, is it not? It notes that globals() will take the change.
5

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

David's user avatar
David
18.4k10 gold badges74 silver badges100 bronze badges
answeredMar 22, 2017 at 17:22
mchen's user avatar

Comments

4

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

answeredJun 12, 2015 at 12:24
mchen's user avatar

Comments

2

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 = {}
answeredMay 15, 2014 at 13:00
Wilfred Hughes's user avatar

2 Comments

I've seen a few people caught off guard with this problem, especially those going from PHP.
For immutable variables, you almost always want them to be class variables and in uppercase :)
2

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
answeredMay 16, 2014 at 16:06
Wilfred Hughes's user avatar

4 Comments

This is actually fixed in Python 3:TypeError: unorderable types: str() < int().
I could not replicate this in Python 2.7.7
Isn't rather the result ofprint 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.
@hello_there_andy The issue absolutely does exist in Python 2.7.12, although it raisesTypeError in Python 3.5.2. Just try'0' < 1.
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.

answeredMay 16, 2014 at 16:10
Wilfred Hughes's user avatar

Comments

0

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]
answeredMar 19, 2015 at 15:12
jolvi's user avatar

7 Comments

I cannot replicate.range(2.0) causesTypeError: 'float' object cannot be interpreted as an integer in both Python 2.7.12 and 3.5.2.
Thanks, I updated the example. It "works" only if thefuture 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.
You need to have installed thefuture 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.
You're going in circles considering that I already noted that the answer cannot be replicated withrange on Python 3.5.2 either. The question is about Python 2.x gotchas, and basically your answer is not.
|
0

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.

answeredJun 13, 2017 at 21:01
Bryan S's user avatar

Comments

-1

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]]
answeredMar 29, 2019 at 8:50
Bendriss Jaâfar's user avatar

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.