Python Puzzlers Feb 17, 2016 Nandan Sawant, Ryan Rueth
Python PuzzlersFeb 17, 2016
Nandan Sawant, Ryan Rueth
1. Defaulter
Defaulter
def extend_list(val, l=[]):
l.append(val)
return l
list1 = extend_list(10)
list2 = extend_list(123,[])
list3 = extend_list('a')
print "list1 = %s" % list1
print "list2 = %s" % list2
print "list3 = %s" % list3
Defaulter
def extend_list(val, l=[]):
l.append(val)
return l
list1 = extend_list(10)
list2 = extend_list(123,[])
list3 = extend_list('a')
print "list1 = %s" % list1
print "list2 = %s" % list2
print "list3 = %s" % list3
list1 = [10, 'a']
list2 = [123]
list3 = [10, 'a']
Defaulter
From the python documentation:https://docs.python.org/2/reference/compound_stmts.html#function-definitions
● Avoid mutable default arguments● If you use mutable default arguments,
○ don’t mutate them○ have a good reason to mutate them (e.g. caching)
Defaulter
def extend_list(val, l=None):
if l is None:
l = []
l.append(val)
return l
list1 = extend_list(10)
list2 = extend_list(123,[])
list3 = extend_list('a')
print "list1 = %s" % list1
print "list2 = %s" % list2
print "list3 = %s" % list3
list1 = [10]
list2 = [123]
list3 = ['a']
2. Who’s late?
Who’s late?
multipliers = [lambda x: (i+1)*x for i in range(10)]
def print_multiplication_table_for(number):
print [multiplier(number) for multiplier in multipliers]
print_multiplication_table_for(2)
Who’s late?
multipliers = [lambda x: (i+1)*x for i in range(10)]
def print_multiplication_table_for(number):
print [multiplier(number) for multiplier in multipliers]
print_multiplication_table_for(2)
[20, 20, 20, 20, 20, 20, 20, 20, 20, 20]
Who’s late?
multipliers = []
for i in range(10):
def multiplier(x):
return (i+1) * x
multipliers.append(multiplier)
def print_multiplication_table_for(number):
print [multiplier(number) for multiplier in multipliers]
print_multiplication_table_for(2)
Who’s late?
multipliers = []
for i in range(10):
def multiplier(x):
return (i+1) * x
multipliers.append(multiplier)
def print_multiplication_table_for(number):
print [multiplier(number) for multiplier in multipliers]
print_multiplication_table_for(2)
[20, 20, 20, 20, 20, 20, 20, 20, 20, 20]
● A closure occurs when a function has access to a local variable from an enclosing scope that has finished its execution
● Closures in python are late-binding
● This means that the values of variables used in closures are looked up at the time the closure is called
● This behavior is NOT specific to lambdas
Who’s late?
Who’s late?
multipliers = [lambda x, i=i: (i+1)*x for i in range(10)]
def print_multiplication_table_for(number):
print [multiplier(number) for multiplier in multipliers]
print_multiplication_table_for(2)
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Who’s late?
from functools import partial
from operator import mul
multipliers = [partial(mul, i+1) for i in range(10)]
def print_multiplication_table_for(number):
print [multiplier(number) for multiplier in multipliers]
print_multiplication_table_for(2)
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
3. Western Addition
Western Addition (A)
Western Addition (A)
a = b = [1,2,3]
a += [4]
print a, b
a = a + [5]
print a, b
Western Addition (A)
a = b = [1,2,3]
a += [4]
print a, b
a = a + [5]
print a, b
[1, 2, 3, 4] [1, 2, 3, 4]
[1, 2, 3, 4, 5] [1, 2, 3, 4]
● a + b uses __add__ while a += b uses __iadd__
● An object's __add__ method is regular addition: it takes two parameters, returns their sum, and doesn't modify either parameter
● An object's __iadd__ method also takes two parameters, but makes the change in-place, modifying the contents of the first parameter
Western Addition (A)
Western Addition (B)
Western Addition (B)
a = b = 'western'
a += 'addition'
print a, b
a = a + 'sf'
print a, b
Western Addition (B)
a = b = 'western'
a += 'addition'
print a, b
a = a + 'sf'
print a, b
westernaddition western
westernadditionsf western
● a + b uses __add__ while a += b uses __iadd__, if it exists
● An object's __add__ method is regular addition: it takes two parameters, returns their sum, and doesn't modify either parameter
● An object's __iadd__ method also takes two parameters, but makes the change in-place, modifying the contents of the first parameter
● Because this requires object mutation, immutable types (like Strings!) don’t have an __iadd__ method
Western Addition (B)
Western Addition (C)
Western Addition (C)
a = ([1,2,3],)
a[0] += [4]
print a
Western Addition (C)
a = ([1,2,3],)
a[0] += [4]
print a
TypeError: 'tuple' object does
not support item assignment
([1, 2, 3, 4],)
● An object's __iadd__ method takes two parameters, but makes the change in-place, modifying the contents of the first parameter and returns the first parameter
● The code above is equivalent to:
Western Addition (C)
a = ([1,2,3],)
x = a[0]
x = x.__iadd__([4]) # returns x itself
a[0] = x
Western Addition (D)
Western Addition (D)
a = b = 500
print a is b
a -= 200
b -= 200
print a is b
a = a - 200
b = b - 200
print a is b
--a
--b
print a is b
Western Addition (D)
a = b = 500
print a is b
a -= 200
b -= 200
print a is b
a = a - 200
b = b - 200
print a is b
--a
--b
print a is b
True
False
True
True
From the python documentation:https://docs.python.org/2/c-api/int.html
Western Addition (D)
● There is no decrement (--) operator in python● -- is simply two negation operators
4. Crossing the Boundary
Crossing the Boundary (A)
Crossing the Boundary (A)
a, b = 0, 1
def fib(n):
for i in range(n):
a, b = b, a + b
return a
print fib(4)
Crossing the Boundary (A)
a, b = 0, 1
def fib(n):
for i in range(n):
a, b = b, a + b
return a
print fib(4)
UnboundLocalError: local
variable 'b' referenced before
assignment
Crossing the Boundary (A)
x = 1
def f1():
print x
f1()
print x
x = 1
def f2():
x = 2
print x
f2()
print x
x = 1
def f3():
print x
x = 3
f3()
print x
Crossing the Boundary (A)
x = 1
def f1():
print x
f1()
print x
x = 1
def f2():
x = 2
print x
f2()
print x
x = 1
def f3():
print x
x = 3
f3()
print x
1
1
2
1
UnboundLocalError: local
variable 'x' referenced
before assignment
1
● If we want to access a global variable from inside a function, it is possible
● The assignment statement inside a function creates variables in the local scope
● If a local variable has the same name as a global variable the local variable will always take precedence
● The assignment inside the function does not modify the global variable
● We may not refer to both a global variable and a local variable by the same name inside the same function, it gives us an error
● Deep Dive http://eli.thegreenplace.net/2011/05/15/understanding-unboundlocalerror-in-python
Crossing the Boundary (A)
Crossing the Boundary (A)
a, b = 0, 1
def fib(n):
for i in range(n):
global a, b
a, b = b, a + b
return a
print fib(4) 3
Crossing the Boundary (B)
Crossing the Boundary (B)
Crossing the Boundary (B)
Crossing the Boundary (C)
Crossing the Boundary (C)
main.py
try:
import quitter
except:
exit(0)
quitter.quit()
quitter.py
def quit():
exit(0)
# Add one indented line here
# to make main.py crash
python main.py
Crossing the Boundary (C)
main.py
try:
import quitter
except:
exit(0)
quitter.quit()
quitter.py
def quit():
exit(0)
exit = None
python main.py
Crossing the Boundary (C)
main.py
try:
import quitter
except:
exit(0)
quitter.quit()
quitter.py
def quit():
exit(0)
exit = None
UnboundLocalError: local variable
'exit' referenced before assignment
python main.py
5. Kids These Days
Kids These Days (A)
Kids These Days (A)
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print Parent.x, Child1.x, Child2.x
Child1.x = 2
print Parent.x, Child1.x, Child2.x
Parent.x = 3
print Parent.x, Child1.x, Child2.x
Kids These Days (A)
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print Parent.x, Child1.x, Child2.x
Child1.x = 2
print Parent.x, Child1.x, Child2.x
Parent.x = 3
print Parent.x, Child1.x, Child2.x
1 1 1
1 2 1
3 2 3
● In Python, class variables are internally handled as dictionaries.
● If a variable name is not found in the dictionary of the current class, the class hierarchy (i.e., its parent classes) are searched until the referenced variable name is found
● If the referenced variable name is not found in the class itself or anywhere in its hierarchy, an AttributeError occurs
Kids These Days (A)
Kids These Days (B)
Kids These Days (B)
class Parent:
x = 1
class Child1(Parent):
pass
class Child2(Parent):
x = 2
class GrandChild(Child1, Child2):
pass
print GrandChild.x
Kids These Days (B)
class Parent:
x = 1
class Child1(Parent):
pass
class Child2(Parent):
x = 2
class GrandChild(Child1, Child2):
pass
print GrandChild.x 1
Kids These Days (B)
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
x = 2
class GrandChild(Child1, Child2):
pass
print GrandChild.x 2
● If you don’t inherit from ‘object’, you’re defining an old style class
● MRO is Method Resolution Order. Lookup __mro__
● MRO for old style classes follows a native DFS approachGrandchild -> Child 1 -> Parent -> Child 2 -> Parent
● MRO for new style classes is more sensibleGrandchild -> Child 1 -> Child 2 -> Parent
● Don’t use old-style classes
Kids These Days (B)
6. Exceptional Circumstances
Exceptional Circumstances
user_id = 'a'
try:
user_id = int(user_id)
print user_id
except TypeError, ValueError:
print "Oops!"
(a) a(b) 65(c) Oops!(d) raises TypeError(e) raises ValueError
Exceptional Circumstances
user_id = 'a'
try:
user_id = int(user_id)
print user_id
except TypeError, ValueError:
print "Oops!"
(a) a(b) 65(c) Oops!(d) raises TypeError(e) raises ValueError
ValueError: invalid literal
for int() with base 10: 'a'
Exceptional Circumstances
user_id = 'a'
try:
user_id = int(user_id)
print user_id
except (TypeError, ValueError):
print "Oops!"
(a) a(b) 65(c) Oops!(d) raises TypeError(e) raises ValueError
● If you’re catching multiple exceptions in python 2.x, you must enclose them in parentheses
● If you do not, the second argument to except is treated as a reference to the exception object. E.g. this may look familiar:except Exception, e
● This problem goes away in python 3.x as except Exception, e is not a valid syntax. It is replaced byexcept Exception as e
Exceptional Circumstances
7. So long!
So long!
x = set([type(1), type(1L), type(1.0)])
y = set([1.__class__, 1L.__class__, 1.0.__class__])
print x == y
(a) True(b) False(c) TypeError(d) None of the above
So long!
x = set([type(1), type(1L), type(1.0)])
y = set([1.__class__, 1L.__class__, 1.0.__class__])
print x == y
(a) True(b) False(c) TypeError(d) None of the above
y = set([1.__class__, 1L.__class__, 1.0.__class__])
^
SyntaxError: invalid syntax
It is a tokenization issue!
The . is parsed as the beginning of the fractional part of a floating point number. When it encounters something other than a number, it will throw an error.
So long!
So long!
x = set([type(1), type(1L), type(1.0)])
y = set([(1).__class__, 1L.__class__, 1.0.__class__])
print x == y
(a) True(b) False(c) TypeError
(d) None of the above
8. Zilch
Zilch
z = False
i = []
l = 0,
c = None
h = {}
print any((z, i, l, c, h))
(a) True(b) False(c) (False, False, False, False, False)
(d) None of the above
Zilch
z = False
i = []
l = 0,
c = None
h = {}
print any((z, i, l, c, h))
(a) True(b) False(c) (False, False, False, False, False)
(d) None of the above
Zilch
z = False
i = []
l = 0,
c = None
h = {}
print any((z, i, l, c, h))
(a) True(b) False(c) (False, False, False, False, False)
(d) None of the above
When creating a tuple, in all cases except the empty tuple, the comma is the important thing.
Parentheses are only required for other syntactic reasons: to distinguish a tuple from a set of function arguments, operator precedence, or to allow line breaks.
Zilch
https://docs.python.org/2/tutorial/datastructures.html#tuples-and-sequences
9. It’s 2016!
It’s 2016!
print 2,016 * 2,016(a) 4064256(b) 4,064,256(c) 4 256(d) 2 32 16(e) None of the above
It’s 2016!
print 2,016 * 2,016(a) 4064256(b) 4,064,256(c) 4 256(d) 2 32 16(e) None of the above
2 28 14
The 0 is an outdated prefix that Python 2.x uses to represent Octal numbers.
In Python 3, you must write: 0o16 instead.
It’s 2016!
10. Finally
Finally
def find_inverse(n):
inverse = 1
try:
inverse = 1 / k
finally:
return inverse
fi = find_inverse
print fi(-1) + fi(0) + fi(1)
(a) 0(b) 1(c) 3(d) raises ZeroDivisionError(e) raises NameError(f) None of the above
Finally
def find_inverse(n):
inverse = 1
try:
inverse = 1 / k
finally:
return inverse
fi = find_inverse
print fi(-1) + fi(0) + fi(1)
(a) 0(b) 1(c) 3(d) raises ZeroDivisionError(e) raises NameError(f) None of the above
Finally
return in finally will swallow the actual return value in the try block
return in finally will swallow the exception
Finally
References
● How to format code within a presentation
● Python Puzzlers by Tendayi Mawushe at PyCon Ireland 2010
● Python Puzzlers by Alan Pierce at Khan Academy
● Python Epiphanies by Stuart Williams at PyCon Montreal 2015
● Python Interview Questions
● Python Gotchas
● Raymon Hettinger’s twitter feed @raymondh