Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Python Puzzlers

Alan Pierce
October 08, 2014

Python Puzzlers

Inspired by the "Java Puzzlers" book and series of talks, this is a set of 6 puzzles that expose some pitfalls and oddities in the Python programming language. For each puzzle, you're given some Python code, and your task is to figure out what happens when the code is run. I added in a few annotations to make the slides a little more self-contained.

There are other similar talks on the internet also called "Python Puzzlers", and this talk is unrelated to all of those.

Alan Pierce

October 08, 2014
Tweet

Other Decks in Programming

Transcript

  1. 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.
  2. Credit: Java Puzzlers This talk is a tribute to Java

    Puzzlers, not a ripoff of it. :-)
  3. 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.
  4. nope = False zilch = 0 nada = [] if

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

    (nope, zilch and nada): print 'How positive!' else: print 'Don't be so negative!' What does it print?
  6. 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?
  7. 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?
  8. 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?
  9. 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?
  10. 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.)
  11. 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.)
  12. 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.)
  13. 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.)
  14. 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?
  15. 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?
  16. 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?
  17. 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?
  18. 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.
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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...
  25. 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...
  26. 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
  27. 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.
  28. 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
  29. 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...
  30. 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...
  31. 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...
  32. 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.
  33. 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]
  34. 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]
  35. 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]
  36. 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]
  37. 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]
  38. 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...
  39. 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...
  40. 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
  41. 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
  42. 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
  43. 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.
  44. 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]] !
  45. 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
  46. 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]] !
  47. 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
  48. 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]] !
  49. 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.
  50. 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]] !
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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...
  56. 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...
  57. 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
  58. 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”.
  59. 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]]
  60. 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']
  61. 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']
  62. 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']
  63. 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
  64. 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...
  65. 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...
  66. 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)
  67. 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.
  68. 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)))
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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...
  74. 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...
  75. 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.
  76. 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')
  77. 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
  78. 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.
  79. 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')
  80. 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
  81. 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
  82. 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.
  83. \n