Common Gotchas In Python
- Last edited on Aug 7, 2017, 5:57:43 PM by 953482 Kevin
is not is a single binary operator, and has behavior different than using
2 is not None #result: True 2 is (not None) #result: False
== in chained comparisons
Used in conjunction with
== can comprise an expression whose result differs from any variant that uses parentheses.
(42 > 23) == True #effectively equivalent to `True == True`, which is True 42 > (23 == True) #effectively equivalent to `42 > False`, which is True 42 > 23 == True #effectively equivalent to `42 > 23 and 23 == True`, which is False
A lambda expression that uses a local variable will not bind to the value the variable had when the lambda was created. Instead, it uses the value the variable has when the lambda is executed.
x = 23 f = lambda: x x = 42 print f() #expected result: 23 #actual result: 42 funcs =  for i in range(10): funcs.append(lambda: i**2) print [f() for f in funcs] #expected result: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] #actual result: [81, 81, 81, 81, 81, 81, 81, 81, 81, 81]
To get the desired result you can bind the variable as a default argument to the lambda.
x = 23 f = lambda x=x: x x = 42 print f() # 23 funcs =  for i in range(10): funcs.append(lambda i=i: i**2) print [f() for f in funcs] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Default Mutable Argument
The default value in a function definition is only evaluated once, at declaration time. This means that a mutable default argument has persistent state across multiple invocations.
def frob(x=): x.append(1) print len(x), frob() frob() #expected result: 1, 1 #actual result: 1, 2
Evaluation time discrepancy
In a generator expression, the
in clause is evaluated at declaration time, but the conditional clause is evaluated at run time.
seq = [4, 8, 15] g = (x for x in seq if seq.count(x) > 0) seq = [23, 8, 42] print(list(g)) #expected result: [4, 8, 15] #or perhaps [23, 8, 42] #actual result: 
Persistent state of multiple references to one iterator
next on an iterator will advance all instances of that iterator, not just the one you called it on.
#returns True if exactly one element of `iterable` is True #courtesy of Jon Clements at http://stackoverflow.com/a/16801605/1903116 def single_true(iterable): i = iter(iterable) return any(i) and not any(i) #returns True if exactly one element of `iterable` is False def single_false(iterable): i = iter(iterable) return not all(i) and all(i) #splits a list into evenly sized chunks def chunk(input, size): return map(None, *([iter(input)] * size)) #ok, these examples don't demonstrate how it can be used to confuse #the reader. But I promise it can. #todo: be more evil.
Identical looking variable names
Some unicode characters look identical to ascii ones, but are considered distinct by the interpreter.
value = 42 #ascii e valuе = 23 #cyrillic e print(value) #expected result: 23 #actual result: 42
Tab indentation variability
A tab can be equivalent to a variable number of spaces depending on its position on the line. See http://docs.python.org/2/reference/lexical_analysis.html#indentation
def frob(x): if x: print "A" #this next line is indented with a tab. #although it looks four spaces long in the editor, #in this context the interpreter thinks it's eight spaces long. print "B" frob(False) #expected result: "B" #actual result: no output
yield from in generator expressions
yield from is only used inside function declarations. But the interpreter won’t complain if you use it in a generator. You can even put it in the conditional clause.
g = [(1,2),(2,4),(3,8),(4,16)] print list((None for g in g if (yield from g) and False)) #result: [1, 2, 2, 4, 3, 8, 4, 16]
__dunder__ methods don’t look at instance level
class-level dunder methods are invoked by operators, even if you override them at the instance level.
class Widget(): def __init__(self): self.__eq__ = lambda self, other: True def __eq__(self, other): return False print(Widget() == Widget()) #expected: True #actual: False
Name resolution ignores class scope
Scopes nested inside class definition ignore names bound at the class level. For example, a generator expression has its own scope. Starting in 3.X, list comprehensions also have their own scope. See this
x = 23 class Fred: x = 42 y = (x for i in range(10)) print(list(Fred.y)) #expected result: 42 #actual result: 23 #3.X only: x = 23 class Fred: x = 42 y = [x for i in range(10)] print(Fred.y) #expected result: 42 #actual result: 23 (42 in 2.X)
sys.argv is the filename of the current Python file.
sys.argv to accept command line arguments to Python scripts the first argument in
sys.argv is the current Python filename. This argument is passed by default and can cause issues if you’re using filename arguments in the command line.
The below code will write over the Python file with the text
'Hello World!'. This snippet was inspired by Al.Sal <3
import sys with open(sys.argv, 'w') as f: f.write('Hello World!')