Python's Class Development Toolkit by Raymond Hettinger
Short but thorough tutorial on the Python's built-in toolset for creating classes. We look at commonly encountered challenges and how to solve them using Python.
set(), frozenset(), sorted(), reversed(), enumerate(), any(), all() and the python3 version of zip() • Standard library: collec;ons, itertools, lru_cache • Language features: key-‐func;ons, and generator expressions • Op;miza;ons: peephole op;mizer, length-‐hint, fast sum, etc. • Python Instructor – Adconion, Cisco, HP, EBay, Paypal, … • Python evangelist and former PSF Board Member id -‐u
Code Test Ship • In with: Tight itera;ons • Let liUle bits of design, coding, and tes;ng inform later bits of design, coding and tes;ng • The core idea is iterate and adapt quickly
def __init__(self, radius): self.radius = radius def area(self): 'Perform quadrature on a shape of uniform radius' return 3.14 * self.radius ** 2.0 Regular methods have “self” as first argument. Hmm, what about the 3.14?
seed(8675309) print 'Using Circuituous(tm) version', Circle.version n = 10 circles = [Circle(random()) for i in xrange(n)] print 'The average area of', n, 'random circles' avg = sum([c.area() for c in circles]) / n print 'is %.1f' % avg print
0.7, 0.8] circles = [Circle(r) for r in cuts] for c in circles: print 'A circlet with with a radius of', c.radius print 'has a perimeter of', c.perimeter() print 'and a cold area of', c.area() c.radius *= 1.1 print 'and a warm area of', c.area() print Hmm, how do we feel about exposing the radius aUribute?
are circles with a corrected perimeter' def perimeter(self): 'Circumference corrected for the rubber' return Circle.perimeter(self) * 1.25 t = Tire(22) print 'A tire of radius', t.radius print 'has an inner area of', t.area() print 'and an odometer corrected perimeter of', print t.perimeter() print
25.1 c = Circle(bbd_to_radius(bbd)) print 'A circle with a bbd of 25.1' print 'has a radius of', c.radius print 'an an area of', c.area() print The API is awkward. A converter func;on is always needed. Perhaps change the constructor signature?
are circles with a corrected perimeter' def perimeter(self): 'Circumference corrected for the rubber' return Circle.perimeter(self) * 1.25 t = Tire.from_bbd(45) print 'A tire of radius', t.radius print 'has an inner area of', t.area() print 'and an odometer corrected perimeter of', print t.perimeter() print Hmm, this code doesn’t work.
'Convert angle in degree to a percentage grade' return math.tan(math.radians(angle)) * 100.0 Will this also work for the Sphere class and the Hyperbolic class? Can people even find this code?
advanced circle analytic toolkit' version = '0.4b' def __init__(self, radius): self.radius = radius def angle_to_grade(self, angle): 'Convert angle in degree to a percentage grade' return math.tan(math.radians(angle)) * 100.0 Really? You have to create an instance just to call func;on? Well, findability has been improved and it won’t be called in the wrong context.
5 degrees' print 'is a %0.1f%% grade.' % Circle.angle_to_grade(5) print Nice, clean call. No instance is required. The correct context is present. The method is findable.
'Tires are circles with an odometer corrected perimeter‘ def perimeter(self): 'Circumference corrected for the rubber' return Circle.perimeter(self) * 1.25 _perimeter = perimeter
“liUle change” • You’re not allowed to store the radius • You must store the diameter instead! How hard could this be? Just write some geUer and seUer methods.
analytic toolkit' version = '0.6' def __init__(self, radius): self.radius = radius def get_radius(self): 'Radius of a circle' return self.diameter / 2.0 def set_radius(self, radius): self.diameter = radius * 2.0 Oh no, this is going to be terrible! I wish that all aUribute access would magically transform to these method calls.
print 'Using Circuituous(tm) version', Circle.version circles = [Circle(random()) for i in xrange(n)] print 'The average area of', n, 'random circles' avg = sum([c.area() for c in circles]) / n print 'is %.1f' % avg print I sense a major memory problem. Circle instances are over 300 bytes each!
object(). 2. Instance variables for informa;on unique to an instance. 3. Class variables for data shared among all instances. 4. Regular methods need “self” to operate on instance data. 5. Thread local calls use the double underscore. Gives subclasses the freedom to override methods without breaking other methods. 6. Class methods implement alterna;ve constructors. They need “cls” so they can create subclass instances as well. 7. Sta;c methods aUach func;ons to classes. They don’t need either “self” or “cls”. Sta;c methods improve discoverability and require context to be specified. 8. A property() lets geUer and seUer methods be invoked automa;cally by aUribute access. This allows Python classes to freely expose their instance variables. 9. The “__slots__” variable implements the Flyweight Design PaUern by suppressing instance dic;onaries.