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.

    View full-size slide

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

    View full-size slide

  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.

    View full-size slide

  4. Puzzler 0:
    Zero (Warmup)
    Difficulty:

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  7. 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?

    View full-size slide

  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
    What does it print?

    View full-size slide

  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!
    What does it print?

    View full-size slide

  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?

    View full-size slide

  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.)

    View full-size slide

  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.)

    View full-size slide

  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...
    (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.)

    View full-size slide

  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?
    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.)

    View full-size slide

  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?
    I promise that the code no
    longer raises a SyntaxError.
    What happens instead?

    View full-size slide

  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?

    View full-size slide

  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...
    I promise that the code no
    longer raises a SyntaxError.
    What happens instead?

    View full-size slide

  18. 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?

    View full-size slide

  19. 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.

    View full-size slide

  20. Puzzler 1:
    Truthsum
    Difficulty:

    View full-size slide

  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]
    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

    View full-size slide

  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
    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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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...

    View full-size slide

  27. 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...

    View full-size slide

  28. 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

    View full-size slide

  29. 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.

    View full-size slide

  30. Puzzler 2
    Interpret This!
    Difficulty:

    View full-size slide

  31. 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

    View full-size slide

  32. 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...

    View full-size slide

  33. 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...

    View full-size slide

  34. 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...

    View full-size slide

  35. 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.

    View full-size slide

  36. Puzzler 3
    The Pythagorean Puzzler
    Difficulty:

    View full-size slide

  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]

    View full-size slide

  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]

    View full-size slide

  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]

    View full-size slide

  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]

    View full-size slide

  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]

    View full-size slide

  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...

    View full-size slide

  43. 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...

    View full-size slide

  44. 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

    View full-size slide

  45. 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

    View full-size slide

  46. 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

    View full-size slide

  47. 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.

    View full-size slide

  48. Puzzler 4
    Not the Rule
    Difficulty:

    View full-size slide

  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]]
    !

    View full-size slide

  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]]
    !
    ERROR

    View full-size slide

  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]]
    !

    View full-size slide

  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]]
    !
    ERROR

    View full-size slide

  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]]
    !

    View full-size slide

  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]]
    ! That message means
    that this puzzler is
    guaranteed to raise an
    error, and you have to
    decide which error.

    View full-size slide

  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]]
    !

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  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]]
    !
    (a)Buzz
    (b)UnboundLocalError
    (c)SyntaxError
    (d)TypeError

    View full-size slide

  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)Buzz
    (b)UnboundLocalError
    (c)SyntaxError
    (d)TypeError
    And the answer is...

    View full-size slide

  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)Buzz
    (b)UnboundLocalError
    (c)SyntaxError
    (d)TypeError
    And the answer is...

    View full-size slide

  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)Buzz
    (b)UnboundLocalError
    (c)SyntaxError
    (d)TypeError
    And the answer is...
    Exception
    assigned
    to "Buzz"
    local

    View full-size slide

  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)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”.

    View full-size slide

  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]]

    View full-size slide

  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']

    View full-size slide

  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']

    View full-size slide

  67. 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']

    View full-size slide

  68. 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

    View full-size slide

  69. 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...

    View full-size slide

  70. 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...

    View full-size slide

  71. 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)

    View full-size slide

  72. 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.

    View full-size slide

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

    View full-size slide

  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)))

    View full-size slide

  75. 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

    View full-size slide

  76. 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

    View full-size slide

  77. 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

    View full-size slide

  78. 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

    View full-size slide

  79. 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...

    View full-size slide

  80. 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...

    View full-size slide

  81. 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.

    View full-size slide

  82. 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')

    View full-size slide

  83. 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

    View full-size slide

  84. 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.

    View full-size slide

  85. 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')

    View full-size slide

  86. 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

    View full-size slide

  87. 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

    View full-size slide

  88. 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.

    View full-size slide