Slide 1

Slide 1 text

Python Puzzlers Alan Pierce This talk was given to the other devs at Khan Academy on October 8, 2014. These blue-bordered boxes weren’t in the original slides, and provide a little more context for internet readers.

Slide 2

Slide 2 text

Credit: Java Puzzlers This talk is a tribute to Java Puzzlers, not a ripoff of it. :-)

Slide 3

Slide 3 text

Format/Rules ● I give you code, you figure out what it does. ● You vote on 4 possibilities (3 lies, one truth). ● Each puzzler has a moral. ● Running Python 2.7 (on my machine). ● All code in one file, unless otherwise specified. ● Options are in a random.shuffled order.

Slide 4

Slide 4 text

Puzzler 0: Zero (Warmup) Difficulty:

Slide 5

Slide 5 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!'

Slide 6

Slide 6 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' What does it print?

Slide 7

Slide 7 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! What does it print?

Slide 8

Slide 8 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! (b)SyntaxError What does it print?

Slide 9

Slide 9 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! (b)SyntaxError (c)Don't be so negative! What does it print?

Slide 10

Slide 10 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print?

Slide 11

Slide 11 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? And the answer is... (I always give a warning like the one above, so you don’t have to worry that clicking will reveal the answer before you’re ready.)

Slide 12

Slide 12 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? And the answer is... (I always give a warning like the one above, so you don’t have to worry that clicking will reveal the answer before you’re ready.)

Slide 13

Slide 13 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? And the answer is... (I always give a warning like the one above, so you don’t have to worry that clicking will reveal the answer before you’re ready.)

Slide 14

Slide 14 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? And the answer is... Ok, that was kind of dumb, so let’s try it again with double quotes. (I always give a warning like the one above, so you don’t have to worry that clicking will reveal the answer before you’re ready.)

Slide 15

Slide 15 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print "Don't be so negative!" (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? I promise that the code no longer raises a SyntaxError. What happens instead?

Slide 16

Slide 16 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print "Don't be so negative!" (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? And the answer is... I promise that the code no longer raises a SyntaxError. What happens instead?

Slide 17

Slide 17 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print "Don't be so negative!" (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? And the answer is... I promise that the code no longer raises a SyntaxError. What happens instead?

Slide 18

Slide 18 text

nope = False zilch = 0 nada = [] if (nope, zilch and nada): print 'How positive!' else: print "Don't be so negative!" (a)How positive! (b)SyntaxError (c)Don't be so negative! (d)TypeError What does it print? And the answer is... Non-empty tuple! I promise that the code no longer raises a SyntaxError. What happens instead?

Slide 19

Slide 19 text

Moral ● I am trying to trick you; don't trust the syntax highlighting. ● Watch out for accidental tuples. o Parentheses not required! ● Learn the semantics for truthy/falsy values.

Slide 20

Slide 20 text

Puzzler 1: Truthsum Difficulty:

Slide 21

Slide 21 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum

Slide 22

Slide 22 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] (a)TypeError list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum

Slide 23

Slide 23 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] (a)TypeError (b)List3's sum: 10 list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum

Slide 24

Slide 24 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] (a)TypeError (b)List3's sum: 10 list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum (c)List3's sum: 2

Slide 25

Slide 25 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] (a)TypeError (b)List3's sum: 10 list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum (c)List3's sum: 2 (d)List3's sum: True

Slide 26

Slide 26 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] (a)TypeError (b)List3's sum: 10 list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum (c)List3's sum: 2 (d)List3's sum: True And the answer is...

Slide 27

Slide 27 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] (a)TypeError (b)List3's sum: 10 list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum (c)List3's sum: 2 (d)List3's sum: True And the answer is...

Slide 28

Slide 28 text

class SmartList(object): """List that maintains its sum.""" def __init__(self, arr=[], z=0): self.arr = arr self.sum = sum(arr, z) def append(self, elem): self.sum += elem self.arr.append(elem) def __getitem__(self, item): return self.arr[item] (a)TypeError (b)List3's sum: 10 list1 = SmartList() list1.append(3) list1.append(5) list2 = SmartList( arr=[['Hello'], ['World']], z=[]) list3 = SmartList(z=False) list3.append(False) list3.append(True) list3.append(True) print "List3's sum: %s" % list3.sum (c)List3's sum: 2 (d)List3's sum: True And the answer is... Mutable list shared across all instances

Slide 29

Slide 29 text

Moral ● Never use a mutable value for a default argument. ● bool is a subclass of int, and True and False behave similar to 1 and 0 in many ways.

Slide 30

Slide 30 text

Puzzler 2 Interpret This! Difficulty:

Slide 31

Slide 31 text

Try to make the program crash! try: import quitter except: exit(0) quitter.do_not_crash() def do_not_crash(): exit(0) # Add one indented line here quit_util.py quitter.py Command being run: python quit_util.py

Slide 32

Slide 32 text

Try to make the program crash! try: import quitter except: exit(0) quitter.do_not_crash() def do_not_crash(): exit(0) # Add one indented line here quit_util.py quitter.py Command being run: python quit_util.py And the answer is...

Slide 33

Slide 33 text

Try to make the program crash! try: import quitter except: exit(0) quitter.do_not_crash() def do_not_crash(): exit(0) exit = None quit_util.py quitter.py Command being run: python quit_util.py And the answer is...

Slide 34

Slide 34 text

Try to make the program crash! try: import quitter except: exit(0) quitter.do_not_crash() def do_not_crash(): exit(0) exit = None quit_util.py quitter.py Command being run: python quit_util.py Raises UnboundLocalError And the answer is...

Slide 35

Slide 35 text

Moral ● Not much of one. ● Python looks ahead to determine the set of locals in each function, so it may not be as line-by-line as you think.

Slide 36

Slide 36 text

Puzzler 3 The Pythagorean Puzzler Difficulty:

Slide 37

Slide 37 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points]

Slide 38

Slide 38 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0]

Slide 39

Slide 39 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0]

Slide 40

Slide 40 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0] (c)[5, 13, 5, 13, 0]

Slide 41

Slide 41 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0] (c)[5, 13, 5, 13, 0] (d)[13, 13, 13, 13, 13]

Slide 42

Slide 42 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0] (c)[5, 13, 5, 13, 0] (d)[13, 13, 13, 13, 13] And the answer is...

Slide 43

Slide 43 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0] (c)[5, 13, 5, 13, 0] (d)[13, 13, 13, 13, 13] And the answer is...

Slide 44

Slide 44 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0] (c)[5, 13, 5, 13, 0] (d)[13, 13, 13, 13, 13] And the answer is... Requires explicit self

Slide 45

Slide 45 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0] (c)[5, 13, 5, 13, 0] (d)[13, 13, 13, 13, 13] And the answer is... Requires explicit self Only accessible through self

Slide 46

Slide 46 text

import math class PolarPoint(object): x, y = 0, 0 def __init__(self, base_point): if base_point: x, y = base_point def radius(self): return math.sqrt(x**2 + y**2) def angle(self): return math.atan2(y, x) points = [(3, 4), (5, 12)] rev_points = [ (y, x) for x, y in points] all_points = map( PolarPoint, points + rev_points + [None]) print [int(p.radius()) for p in all_points] (a)[0, 0, 0, 0, 0] (b)[5, 12, 5, 12, 0] (c)[5, 13, 5, 13, 0] (d)[13, 13, 13, 13, 13] And the answer is... Requires explicit self Only accessible through self Leaked globals

Slide 47

Slide 47 text

Moral ● List comprehensions leak their variables. o Dictionary, set, and generator comprehensions don't. ● Limit usage of global variables (including list comprehensions in global scope). ● Methods don't have implicit access to values declared in the class directly.

Slide 48

Slide 48 text

Puzzler 4 Not the Rule Difficulty:

Slide 49

Slide 49 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] !

Slide 50

Slide 50 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! ERROR

Slide 51

Slide 51 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] !

Slide 52

Slide 52 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! ERROR

Slide 53

Slide 53 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] !

Slide 54

Slide 54 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! That message means that this puzzler is guaranteed to raise an error, and you have to decide which error.

Slide 55

Slide 55 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] !

Slide 56

Slide 56 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz

Slide 57

Slide 57 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz (b)UnboundLocalError

Slide 58

Slide 58 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz (b)UnboundLocalError (c)SyntaxError

Slide 59

Slide 59 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz (b)UnboundLocalError (c)SyntaxError (d)TypeError

Slide 60

Slide 60 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz (b)UnboundLocalError (c)SyntaxError (d)TypeError And the answer is...

Slide 61

Slide 61 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz (b)UnboundLocalError (c)SyntaxError (d)TypeError And the answer is...

Slide 62

Slide 62 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz (b)UnboundLocalError (c)SyntaxError (d)TypeError And the answer is... Exception assigned to "Buzz" local

Slide 63

Slide 63 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise Fizz, Buzz if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except Fizz, Buzz: return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] ! (a)Buzz (b)UnboundLocalError (c)SyntaxError (d)TypeError And the answer is... Exception assigned to "Buzz" local Ok, we’ll fix the problem by putting parens around both places where we use “Fizz, Buzz”.

Slide 64

Slide 64 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]]

Slide 65

Slide 65 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] (a)['FizzBuzz', 'FizzBuzz', 'FizzBuzz']

Slide 66

Slide 66 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] (a)['FizzBuzz', 'FizzBuzz', 'FizzBuzz'] (b)['Fizz', 'Buzz', 'FizzBuzz']

Slide 67

Slide 67 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] (a)['FizzBuzz', 'FizzBuzz', 'FizzBuzz'] (b)['Fizz', 'Buzz', 'FizzBuzz'] (c)['Fizz', 'Buzz', 'Fizz']

Slide 68

Slide 68 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] (a)['FizzBuzz', 'FizzBuzz', 'FizzBuzz'] (b)['Fizz', 'Buzz', 'FizzBuzz'] (c)['Fizz', 'Buzz', 'Fizz'] (d)TypeError

Slide 69

Slide 69 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] (a)['FizzBuzz', 'FizzBuzz', 'FizzBuzz'] (b)['Fizz', 'Buzz', 'FizzBuzz'] (c)['Fizz', 'Buzz', 'Fizz'] (d)TypeError And the answer is...

Slide 70

Slide 70 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] (a)['FizzBuzz', 'FizzBuzz', 'FizzBuzz'] (b)['Fizz', 'Buzz', 'FizzBuzz'] (c)['Fizz', 'Buzz', 'Fizz'] (d)TypeError And the answer is...

Slide 71

Slide 71 text

class Fizz(Exception): pass class Buzz(Exception): pass ! def classify(val): if val % 3 == val % 5 == 0: raise (Fizz, Buzz) if val % 3 == 0: raise Fizz if val % 5 == 0: raise Buzz def fizzbuzz(val): try: classify(val) except Fizz: return 'Fizz' except Buzz: return 'Buzz' except (Fizz, Buzz): return 'FizzBuzz' else: return val print [fizzbuzz(i) for i in [3, 5, 15]] (a)['FizzBuzz', 'FizzBuzz', 'FizzBuzz'] (b)['Fizz', 'Buzz', 'FizzBuzz'] (c)['Fizz', 'Buzz', 'Fizz'] (d)TypeError And the answer is... Equivalent to raise Fizz(Buzz)

Slide 72

Slide 72 text

Moral ● Watch out for weird legacy syntax. o Python 3 fixes many of these warts. ● Always use "as" to assign exceptions. ● Avoid "advanced" uses of exceptions when possible.

Slide 73

Slide 73 text

Puzzler 5 Hello? Yes, this is Dog. Difficulty:

Slide 74

Slide 74 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' porky, wilbur = Pig(), Pig() pluto, rex = Dog(), Dog() porky.__dict__['name'] = 'Porky' pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() if speak is pluto.speak: yield '!!' ! for animal in porky, wilbur, pluto, rex: print '%s: %s' % (animal.name, ''.join( make_some_noise(animal.speak)))

Slide 75

Slide 75 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' porky, wilbur = Pig(), Pig() pluto, rex = Dog(), Dog() porky.__dict__['name'] = 'Porky' pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() if speak is pluto.speak: yield '!!' ! for animal in porky, wilbur, pluto, rex: print '%s: %s' % (animal.name, ''.join( make_some_noise(animal.speak))) Porky: Oink Porky: Oink Pluto: Bark!! Dog: Bark

Slide 76

Slide 76 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' porky, wilbur = Pig(), Pig() pluto, rex = Dog(), Dog() porky.__dict__['name'] = 'Porky' pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() if speak is pluto.speak: yield '!!' ! for animal in porky, wilbur, pluto, rex: print '%s: %s' % (animal.name, ''.join( make_some_noise(animal.speak))) Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Porky: Oink Porky: Oink Pluto: Bark!! Dog: Bark

Slide 77

Slide 77 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' porky, wilbur = Pig(), Pig() pluto, rex = Dog(), Dog() porky.__dict__['name'] = 'Porky' pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() if speak is pluto.speak: yield '!!' ! for animal in porky, wilbur, pluto, rex: print '%s: %s' % (animal.name, ''.join( make_some_noise(animal.speak))) Porky: $Oink Pig: Oink Pluto: Bark!! Dog: Bark Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Porky: Oink Porky: Oink Pluto: Bark!! Dog: Bark

Slide 78

Slide 78 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' porky, wilbur = Pig(), Pig() pluto, rex = Dog(), Dog() porky.__dict__['name'] = 'Porky' pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() if speak is pluto.speak: yield '!!' ! for animal in porky, wilbur, pluto, rex: print '%s: %s' % (animal.name, ''.join( make_some_noise(animal.speak))) Porky: $Oink Pig: Oink Pluto: Bark!! Dog: Bark Porky: $Oink Pig: Oink Dog: Bark Dog: Bark Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Porky: Oink Porky: Oink Pluto: Bark!! Dog: Bark

Slide 79

Slide 79 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' porky, wilbur = Pig(), Pig() pluto, rex = Dog(), Dog() porky.__dict__['name'] = 'Porky' pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() if speak is pluto.speak: yield '!!' ! for animal in porky, wilbur, pluto, rex: print '%s: %s' % (animal.name, ''.join( make_some_noise(animal.speak))) Porky: $Oink Pig: Oink Pluto: Bark!! Dog: Bark Porky: $Oink Pig: Oink Dog: Bark Dog: Bark Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Porky: Oink Porky: Oink Pluto: Bark!! Dog: Bark And the answer is...

Slide 80

Slide 80 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' porky, wilbur = Pig(), Pig() pluto, rex = Dog(), Dog() porky.__dict__['name'] = 'Porky' pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() if speak is pluto.speak: yield '!!' ! for animal in porky, wilbur, pluto, rex: print '%s: %s' % (animal.name, ''.join( make_some_noise(animal.speak))) Porky: $Oink Pig: Oink Pluto: Bark!! Dog: Bark Porky: $Oink Pig: Oink Dog: Bark Dog: Bark Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Porky: Oink Porky: Oink Pluto: Bark!! Dog: Bark And the answer is...

Slide 81

Slide 81 text

class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' pluto, rex = Dog(), Dog() pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): yield speak() if speak is pluto.speak: yield '!!' Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Dog pluto rex speak (a BarkNoise) name ('Dog') name ('Pluto') Let’s focus on the Dog class first. I’ve removed all Pig- related code. When thinking about attribute accesses, there are really three Python objects we should be thinking about.

Slide 82

Slide 82 text

class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' pluto, rex = Dog(), Dog() pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): yield speak() if speak is pluto.speak: yield '!!' Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Dog pluto rex speak (a BarkNoise) name ('Dog') name ('Pluto')

Slide 83

Slide 83 text

class Dog(object): class BarkNoise(): def __call__(self): return 'Bark' speak = BarkNoise() name = 'Dog' pluto, rex = Dog(), Dog() pluto.__dict__['name'] = 'Pluto' def make_some_noise(speak): yield speak() if speak is pluto.speak: yield '!!' Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Dog pluto rex speak (a BarkNoise) name ('Dog') name ('Pluto') pluto.speak and rex.speak refer to the same BarkNoise object

Slide 84

Slide 84 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' porky, wilbur = Pig(), Pig() porky.__dict__['name'] = 'Porky' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Pig porky wilbur speak (a function) name (property returning 'Pig') name ('Porky') Now let’s focus on the Pig class. This part depends on the details of Python’s descriptor protocol, which controls both the “binding behavior” and the precedence rules when doing attribute lookup.

Slide 85

Slide 85 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' porky, wilbur = Pig(), Pig() porky.__dict__['name'] = 'Porky' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Pig porky wilbur speak (a function) name (property returning 'Pig') name ('Porky')

Slide 86

Slide 86 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' porky, wilbur = Pig(), Pig() porky.__dict__['name'] = 'Porky' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Pig porky wilbur speak (a function) name (property returning 'Pig') name ('Porky') New bound speak method created on every access

Slide 87

Slide 87 text

class Pig(object): def speak(self): return 'Oink' @property def name(self): return 'Pig' porky, wilbur = Pig(), Pig() porky.__dict__['name'] = 'Porky' def make_some_noise(speak): if speak is porky.speak: yield '$' yield speak() Pig: Oink Pig: Oink Pluto: Bark!! Dog: Bark!! Pig porky wilbur speak (a function) name (property returning 'Pig') name ('Porky') New bound speak method created on every access name is a data descriptor, overrides instance attributes

Slide 88

Slide 88 text

Moral ● Descriptors exist, and are complicated. ● Don't conflate "Python object" with "class". ● Python is beautiful: classes are implemented on top of a smaller core language. ● Python is ugly: the resulting design is messy and unintuitive sometimes.

Slide 89

Slide 89 text

\n