Python’s Class Development Toolkit

Date:2013-03-16
Speaker:Raymond Hettinger

The Challenge

  • You write a class, test it, DONE.
  • What about how users use it?
  • Every new user will stretch (abuse) your code in ways you never conceived.

Our Plan

  • Learn the dev toolkist
  • See how users exercise your code
  • Have fun

Agile vs. Lean

Agile Methodology

  • Out with waterfall: design, code, test, ship
  • In with: tight iterations
  • Core idea: iterate and adapt rapidly

Lean Startup Methodology

  • Out with: raise capital, spend it, go to market, fail
  • In with: ship early, get feedback, pivot, iterate
  • Agile applied to busines

Start coding

  • Start with the documentation
  • Use new-style classes (inherit from object)
    • This is the default in Python 3
  • Variables not unique to instance should not be instance variables
  • __init__() is not a constructor!
    • Its job is to init intance variables.
  • self is a convention, use it.
  • Class definition is in itself like a module namespace
  • Pi is not a constant, it’s a variable that never changes. (hrm?)
  • YAGNI: You ain’t gonna need it!
    • aka don’t bog your code down with features you don’t even need yet
    • Stay minimal!
  • Shared data should be at class level (class variables)
  • Don’t use floats for your version numbers. Strings or tuples, plz.
  • Always use iterators (e.g. xrange) to conserve memory!
    • Stay in L1 cache where possible
  • If you expose an attribute, expect users to all kinds of interesting things with it.
    • In Python this is common and normal. Accept it.
  • Adapt init/constructor for commoon use-cases
    • CONSTRUCTOR WARS!
      • e.g. Converter functions passed to init, are bad.
      • Everyone should win.
    • Provider alternate constructors
    • classmethods make for great alternate constructors
      • dict.fromkeys(), datetime.fromtimestamp(), etc.
    • And they should always work from subclasses
  • Always plan for subclassing! (use super()!)
  • Use staticmethods to attach functions to classes
  • Use dunder prefix for class-local variables (e.g. subclasses)
  • Slots save memory, but you lose the ability to inspect, modify
    • Flyweight design pattern
    • Always do them LAST, as a memory efficiency step
    • Cache miss is as expensive as a floating point divide
    • Slots are not inherited by subclasses

The Code

"""
Circles, Inc.
"""

import math # module for code reuse

class Circle(object):
    """An advvanced circle analytics toolkit"""
    version = '0.6' # class variable

    __slots__ = ['diameter']

    def __init__(self, radius):
        self.radius = radius # instance variable

    @property
    def radius(self):
        return self.diameter / 2.0

    @radius.setter
    def radius(self, radius):
        self.diameter = radius * 2.0

    def area(self):
        "Perform quadrature on shape of unofrm radius"
        p = self.__perimeter()
        r = p / math.pi / 2.0
        return math.pi * r ** 2.0

    def perimeter(self):
        "Return the perimeter"
        return 2.0 * math.pi * self.radius
    __perimeter = perimeter

    @classmethod
    def from_bbd(cls, bbd):
        "Construct from a bounding box diagonal"
        radius = bbd / 2.0 / math.sqrt(2.0)
        return cls(radius)

    def dump(self):
        print '   radius:', self.radius
        print '     area:', self.area()
        print 'perimiter:', self.perimeter()
        print

    @staticmethod
    def angle_to_grade(self, angle):
        'Convert angle in degree to a % grade'
        return math.tan(math.radians(angle)) * 100.0

class Tire(Circle):
    "Tires are circles with a corrected perimiter"

    def perimeter(self):
        "Circumference corrected for the rubber"
        return Circle.perimeter(self) * 1.25

if __name__ == '__main__':
    print 'Version', Circle.version
    c = Circle(10)
    c.dump()

    t = Tire(10)
    t.dump()

Summary

  1. Always inherit from object
  2. Use instance vars for info unique to instances.
  3. Use class vars for shared info across instances.
  4. Regular (instance) methods need self to access instance data.
  5. Use classmethods for alternative constructors. They need cls.
  6. Use staticmethods to attach funcs to classes, They don’t need self or cls.
  7. property() lets getter/setters be invoked auomatically w/ attr access
  8. __slots__ implements Flyweight Design Pattern by suppressing instance dict.