Following a game show format made popular by Joshua Bloch and Neal Gafter's Java Puzzlers this presentation intends to both entertain and inform. Snippets of Python code the whose behaviour is not entirely obvious are shown, the audience will then be asked to pick from a number of options what the behaviour of the program is. The correct and sometimes non-intuitive answer will then be given along with a brief explanation of the idea the puzzle exposes. Only a modest working knowledge of the Python language is required to understand the puzzles, but the puzzles may also entertain the more experienced Python programmer.
2. Introduction
Eight Python Puzzles
Short Python program with curious behaviour
What is the output? (multiple choice)
The correct answer given
How to fix the problem (if there was one)
The moral of the story
What will be covered
Language and core libraries
Python 2.6 & 3.x (some puzzles apply to 2.6 only)
7. 1. How do you fix it?
try:
raise NameError('some_name')
except (TypeError, NameError):
print ('caught exception NameError')
except Exception:
pass
>>>
caught exception NameError
8. 1. The moral of the story
When catching multiple exceptions in a single
except clause you must surround them in
parentheses
This problem is non-existent problem in Python
3.x because the problematic syntax is not
permitted:
except SomeException, variable # not valid
3.x syntax
except SomeException as variable
9. 2. Final Countdown
seconds = 10
for i in range(10):
--seconds
if seconds:
print('Wait for it.', seconds)
else:
print('Happy New Year!', seconds)
10. 2. What is the output?
seconds = 10
for i in range(10):
--seconds
if seconds:
print('Wait for it.', seconds)
else:
print('Happy New Year!', seconds)
(a) ('Wait for it.', 10)
(b) -10
(c) SyntaxError: invalid syntax
(d) ('Happy New Year!', 0)
11. 2. What is the output?
(a) ('Wait for it.', 10)
(b) -10
(c) SyntaxError: invalid syntax
(d) ('Happy New Year!', 0)
12. 2. A closer look
seconds = 10
for i in range(10):
--seconds
if seconds:
print('Wait for it.', seconds)
else:
print('Happy New Year!', seconds)
13. 2. How do you fix it?
seconds = 10
for i in range(10):
seconds -= 1
if seconds:
print('Wait for it.', seconds)
else:
print('Happy New Year!', seconds)
>>>
('Happy New Year!', 0)
14. 2. The moral of the story
There is no -- or ++ operator in Python to
achieve that effect use -= 1 and += 1
--seconds is actually the same as -(-seconds)
15. 3. Local News
def news(headline):
sports = 'Soccer'
for story in locals():
print(locals()[story])
news('Politics')
16. 3. What is the output?
def news(headline):
sports = 'Soccer'
for story in locals():
print(locals()[story])
news('Politics')
(a) Politics
Soccer
(b) {'sports': 'Soccer'}
(c) Soccer
(d) RuntimeError: dictionary changed size during
iteration
17. 3. What is the output?
(a) Politics
Soccer
(b) {'sports': 'Soccer'}
(c) Soccer
(d) RuntimeError: dictionary changed
size during iteration
18. 3. A closer look
def news(headline):
sports = 'Soccer'
for story in locals():
print(locals()[story])
news('Politics')
19. 3. How do you fix it?
def news(headline):
sports = 'Soccer'
stories = locals()
for story in stories:
print(stories[story])
news('Politics')
>>>
Politics
Soccer
20. 3. The moral of the story
When locals() is invoked it updates and returns
a dictionary representing the current local
symbol table
You should never attempt to update the locals
dictionary, however if you need to access it's
contents in a loop assign it to another name
first
21. 4. TGIF
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',
'Sun']
weekend = enumerate(days)[5:]
for day in weekend:
print(day[0], day[1])
22. 4. What is the output?
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',
'Sun']
weekend = enumerate(days)[5:]
for day in weekend:
print(day[0], day[1])
(a) (5, 'Sat') (6, 'Sun')
(b) ('Sat', 'Sun')
(c) TypeError: object is unsubscriptable
(d) (5, 6)
23. 4. What is the output?
(a) (5, 'Sat') (6, 'Sun')
(b) ('Sat', 'Sun')
(c) TypeError: object is unsubscriptable
(d) (5, 6)
24. 4. A closer look
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',
'Sun']
weekend = enumerate(days)[5:]
for day in weekend:
print(day[0], day[1])
25. 4. How do you fix it?
days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat',
'Sun']
weekend = list(enumerate(days))[5:]
for day in weekend:
print(day[0], day[1])
>>>
(5, 'Sat')
(6, 'Sun')
26. 4. The moral of the story
The enumerate built-in function is a generator,
that is it returns an iterator
Iterators are not sequences therefore they
cannot be indexed or sliced:
If you need to index or slice an iterator
you must first convert it to a list, this
loads the entire dataset into memory
Generators can represent infinite chain of
values for example itertools.count(), these
cannot be meaningfully sliced in reverse
27. 5. Rabbits everywhere
a, b = 0, 1
def fibonacci(n):
for i in range(n):
a, b = b, a + b
return a
fib8 = fibonacci(8)
print(fib8)
28. 5. What is the output?
a, b = 0, 1
def fibonacci(n):
for i in range(n):
a, b = b, a + b
return a
fib8 = fibonacci(8)
print(fib8)
(a) UnboundLocalError: local variable
(b) 21
(c) 1
(d) 0
29. 5. What is the output?
(a) UnboundLocalError: local variable
(b) 21
(c) 1
(d) 0
30. 5. A closer look
a, b = 0, 1
def fibonacci(n):
for i in range(n):
a, b = b, a + b
return a
fib8 = fibonacci(8)
print(fib8)
31. 5. How do you fix it?
a, b = 0, 1
def fibonacci(n):
global a, b
for i in range(n):
a, b = b, a + b
return a
fib8 = fibonacci(8)
print(fib8)
>>>
21
32. 5. The moral of the story
The issue is local variable optimisation.
If a variable is assigned in a function it is a local
variable, the bytecode generated to access it is
different to that for global variables.
A variable in a function can either be local or
global, but not both.
Do not mix up global and local names in this way, it
is confusing and problematic.
33. 6. The Whole Truth
w = False
h = []
o = 0,
l = None
e = {}
print(any((w, h, o, l, e)))
34. 6. What is the output?
w = False
h = []
o = 0,
l = None
e = {}
print(any((w, h, o, l, e)))
(a) True
(b) (w, h, o, l, e)
(c) (False, [], 0, None, {})
(d) False
35. 6. What is the output?
(a) True
(b) (w, h, o, l, e)
(c) (False, [], 0, None, {})
(d) False
36. 6. A closer look
w = False
h = []
O = 0,
l = None
e = {}
print(any((w, h, o, l, e)))
37. 6. How do you fix it?
w = False
h = []
O = 0
l = None
e = {}
print(any((w, h, o, l, e)))
>>> False
38. 6. The moral of the story
The comma is the tuple constructor, not the
parentheses
Though it is not required it is generally
considered good style to use parentheses when
creating a tuple:
(0,) is better than 0,
39. 7. Double or Nothing
def double(items, doubles=[]):
for item in items:
doubles.append(item * 2)
return doubles
numbers = double([1, 2, 3])
words = double(['one', 'two', 'three'])
print(words)
40. 7. What is the output?
def double(items, doubles=[]):
for item in items:
doubles.append(item * 2)
return doubles
numbers = double([1, 2, 3])
words = double(['one', 'two', 'three'])
print(words)
(a) [2, 4, 6, 'oneone', 'twotwo', 'threethree']
(b) ['oneone', 'twotwo', 'threethree']
(c) TypeError: unsupported operand type(s) for *
(d) [2, 4, 6]
41. 7. What is the output?
(a) [2, 4, 6, 'oneone', 'twotwo', 'threethree']
(b) ['oneone', 'twotwo', 'threethree']
(c) TypeError: unsupported operand type(s) for *
(d) [2, 4, 6]
42. 7. A closer look
def double(items, doubles=[]):
for item in items:
doubles.append(item * 2)
return doubles
numbers = double([1, 2, 3])
words = double(['one', 'two', 'three'])
print(words)
43. 7. How do you fix it?
def double(items, doubles=None):
if doubles is None: doubles = []
for item in items:
doubles.append(item * 2)
return doubles
numbers = double([1, 2, 3])
words = double(['one', 'two', 'three'])
print(words)
>>> ['oneone', 'twotwo', 'threethree']
44. 7. The moral of the story
Do not use mutable types as default arguments
Default arguments are evaluated when the function
is defined not when the function is called
If you want to use a mutable type as a default
argument, set the default to None and initialise it
properly inside the function
45. 8. Evening Out the Odds
nums = [01, 02, 03, 04, 05, 06, 07, 08, 09, 10]
evens = []
for num in nums:
if num % 2 != 0: # is the number odd
evens.append(num + 1)
print(evens)
46. 8. What is the output?
nums = [01, 02, 03, 04, 05, 06, 07, 08, 09, 10]
evens = []
for num in nums:
if num % 2 != 0: # is the number odd
evens.append(num + 1)
print(evens)
(a) [2, 4, 6, 8, 10]
(b) SyntaxError: invalid token
(c) [02, 04, 06, 08, 10]
(d) [2, 2, 4, 4, 6, 6, 8, 8, 10, 10]
47. 8. What is the output?
(a) [2, 4, 6, 8, 10]
(b) SyntaxError: invalid token
(c) [02, 04, 06, 08, 10]
(d) [1, 2, 3, 4, 5]
48. 8. A closer look
nums = [01, 02, 03, 04, 05, 06, 07, 08, 09, 10]
evens = []
for num in nums:
if num % 2 != 0: # is the number odd
evens.append(num + 1)
print(evens)
49. 8. How do you fix it?
nums = [01, 02, 03, 04, 05, 06, 07, 010, 011, 012]
evens = []
for num in nums:
if num % 2 != 0: # is the number odd
evens.append(oct(num + 1))
print(evens)
>>> ['02', '04', '06', '010', '012']
50. 8. The moral of the story
In Python 2.x a leading 0 specifies an octal
literal
If you want to work with octal numbers
remember the valid digits are 0 though 7
In Python 3.x octal literals are specified using
0o, which removes the ambiguity
01 # not valid 3.x syntax
0o1