Slide 1

Slide 1 text

Python's  Class  Development   Toolkit   Raymond  He+nger   @raymondh  

Slide 2

Slide 2 text

whoami   •  Python  Core  Developer   •   Buil;ns:    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  

Slide 3

Slide 3 text

Our  plan   •  Learn  Python’s  development  toolkit   •  See  the  various  ways  users  will  exercise  your   classes  in  unexpected  ways   •  Have  a  liUle  fun  teasing  the  followers  of  the   Lean  Startup  Methodology  

Slide 4

Slide 4 text

Agile  Methodology   •  Out  with  waterfall:      Design  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  

Slide 5

Slide 5 text

Lean  Startup  Methodology   •  Out  with:    Raise  capital,  spend  it,  go  to   market,  then  fail   •  In  with:    Ship  early,  get  customer  feedback,   pivot,  and  iterate   •  Lean  Startup  ==  Agile  applied  to  businesses  

Slide 6

Slide 6 text

Gradua;on  dilemma   •  Get  haircut  and  get  a  real  job   •  Be  CEO  of  our  own  startup       I  say,  “Go  for  it!  

Slide 7

Slide 7 text

Company  name  and  elevator  pitch   ''' Circuitous, LLC – An Advanced Circle Analytics Company '''

Slide 8

Slide 8 text

Start  coding:    Circle  class   class Circle: 'An advanced circle analytic toolkit' Hmm,  is  that  really  advanced  technology?  

Slide 9

Slide 9 text

New  style  classes   class Circle(object): # new-style class 'An advanced circle analytic toolkit' Yes,  this  is  advanced  technology!  

Slide 10

Slide 10 text

Ini;alize  instance  variables   class Circle(object): 'An advanced circle analytic toolkit' def __init__(self, radius): self.radius = radius # instance variable Init  isn’t  a  constructor.    It’s  job  is  to  ini;alize  the  instance  variables.  

Slide 11

Slide 11 text

Regular  method   class Circle(object): 'An advanced circle analytic toolkit' 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?  

Slide 12

Slide 12 text

Modules  for  code  reuse   import math # module for code reuse class Circle(object): 'An advanced circle analytic toolkit' def __init__(self, radius): self.radius = radius def area(self): 'Perform quadrature on a shape of uniform radius' return math.pi * self.radius ** 2.0

Slide 13

Slide 13 text

Class  variables  for  shared  data   import math class Circle(object): 'An advanced circle analytic toolkit' version = '0.1' # class variable def __init__(self, radius): self.radius = radius def area(self): 'Perform quadrature on a shape of uniform radius' return math.pi * self.radius ** 2.0

Slide 14

Slide 14 text

Minimum  viable  product:    Ship  it!   # Tutorial print 'Circuituous version', Circle.version c = Circle(10) print 'A circle of radius', c.radius print 'has an area of', c.area() print

Slide 15

Slide 15 text

First  customer:    Academia   from random import random, seed 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

Slide 16

Slide 16 text

Next  customer  wants  a  perimeter   method   class Circle(object): 'An advanced circle analytic toolkit' version = '0.2' def __init__(self, radius): self.radius = radius def area(self): return math.pi * self.radius ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius

Slide 17

Slide 17 text

Second  customer:  Rubber  sheet   company   cuts = [0.1, 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?  

Slide 18

Slide 18 text

Third  customer:    Na;onal  ;re  chain   class Tire(Circle): 'Tires 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

Slide 19

Slide 19 text

Next  customer:    Na;onal  graphics   company   bbd = 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?  

Slide 20

Slide 20 text

Need  an  alterna;ve  constructor   print datetime(2013, 3, 16) print datetime.fromtimestamp(1363383616) print datetime.fromordinal(734000) print datetime.now() print dict.fromkeys(['raymond', 'rachel', 'matthew'])

Slide 21

Slide 21 text

Class  methods  create  alterna;ve   constructors   class Circle(object): 'An advanced circle analytic toolkit' version = '0.3' def __init__(self, radius): self.radius = radius def area(self): return math.pi * self.radius ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius @classmethod # alternative constructor def from_bbd(cls, bbd): 'Construct a circle from a bounding box diagonal' radius = bbd / 2.0 / math.sqrt(2.0) return Circle(radius)

Slide 22

Slide 22 text

Client  code:    Na;onal  graphics   company   c = Circle.from_bbd(25.1) print 'A circle with a bbd of 25.1' print 'has a radius of', c.radius print 'an an area of', c.area() print

Slide 23

Slide 23 text

It  should  also  work  for  subclasses   class Tire(Circle): 'Tires 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.  

Slide 24

Slide 24 text

Alterna;ve  constructors  need  to   an;cipate  subclassing   class Circle(object): 'An advanced circle analytic toolkit' version = '0.3' def __init__(self, radius): self.radius = radius def area(self): return math.pi * self.radius ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius @classmethod # alternative constructor def from_bbd(cls, bbd): 'Construct a circle from a bounding box diagonal' radius = bbd / 2.0 / math.sqrt(2.0) return cls(radius)

Slide 25

Slide 25 text

New  customer  request:    add  a  func;on   def angle_to_grade(angle): '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?  

Slide 26

Slide 26 text

Move  func;on  to  a  regular  method   class Circle(object): 'An 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.  

Slide 27

Slide 27 text

Move  func;on  to  a  sta;c  method   class Circle(object): 'An advanced circle analytic toolkit' version = '0.4' def __init__(self, radius): self.radius = radius @staticmethod # attach functions to classes def angle_to_grade(angle): 'Convert angle in degree to a percentage grade' return math.tan(math.radians(angle)) * 100.0

Slide 28

Slide 28 text

Client  code:  Trucking  company   print 'A inclinometer reading of 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.  

Slide 29

Slide 29 text

Government  request:    ISO-­‐11110   class Circle(object): 'An advanced circle analytic toolkit' version = '0.5b' def __init__(self, radius): self.radius = radius def area(self): p = self.perimeter() r = p / math.pi / 2.0 return math.pi * r ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius

Slide 30

Slide 30 text

Problem  with  the  ;re  company   class Tire(Circle): 'Tires are circles with an odometer corrected perimeter' def perimeter(self): 'Circumference corrected for the rubber' return Circle.perimeter(self) * 1.25

Slide 31

Slide 31 text

Class  local  reference:  keep  a  spare  copy   class Circle(object): 'An advanced circle analytic toolkit' version = '0.5b' def __init__(self, radius): self.radius = radius def area(self): p = self._perimeter() r = p / math.pi / 2.0 return math.pi * r ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius _perimeter = perimeter

Slide 32

Slide 32 text

Tire  company  adopts  the  same   strategy   class Tire(Circle): 'Tires are circles with an odometer corrected perimeter‘ def perimeter(self): 'Circumference corrected for the rubber' return Circle.perimeter(self) * 1.25 _perimeter = perimeter

Slide 33

Slide 33 text

Class  local  reference  using  the  double   underscore   class Circle(object): 'An advanced circle analytic toolkit' version = '0.5' def __init__(self, radius): self.radius = radius def area(self): p = self.__perimeter() r = p / math.pi / 2.0 return math.pi * r ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius

Slide 34

Slide 34 text

Government  request:    ISO-­‐22220   •  We  insist  on  one  “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.  

Slide 35

Slide 35 text

Government  request:    ISO-­‐22220   class Circle(object): 'An advanced circle 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.  

Slide 36

Slide 36 text

Convert  aUribute  access  to  method   access:  property   class Circle(object): 'An advanced circle analytic toolkit' version = '0.6' def __init__(self, radius): self.radius = radius @property # convert dotted access to method calls def radius(self): 'Radius of a circle' return self.diameter / 2.0 @radius.setter def radius(self, radius): self.diameter = radius * 2.0

Slide 37

Slide 37 text

User  request:    Many  circles   n = 10000000 seed(8675309) 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!  

Slide 38

Slide 38 text

Flyweight  design  paUern:    Slots   class Circle(object): 'An advanced circle analytic toolkit' # flyweight design pattern suppresses # the instance dictionary __slots__ = ['diameter'] version = '0.7' def __init__(self, radius): self.radius = radius @property # convert dotted access to method calls def radius(self): return self.diameter / 2.0 @radius.setter def radius(self, radius): self.diameter = radius * 2.0

Slide 39

Slide 39 text

Summary:    Toolset  for  New-­‐Style  Classes   1.  Inherit  from  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.  

Slide 40

Slide 40 text

Q  &  A