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
- CONSTRUCTOR WARS!
- 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¶
- Always inherit from object
- Use instance vars for info unique to instances.
- Use class vars for shared info across instances.
- Regular (instance) methods need
self
to access instance data. - Use classmethods for alternative constructors. They need
cls
. - Use staticmethods to attach funcs to classes, They don’t need
self
orcls
. property()
lets getter/setters be invoked auomatically w/ attr access__slots__
implements Flyweight Design Pattern by suppressing instance dict.