8.3k

# 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. March 16, 2013

## Transcript

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

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

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

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

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

•  Get  haircut  and  get  a  real  job
•  Be  CEO  of  our  own  startup

I  say,  “Go  for  it!

7. Company  name  and  elevator  pitch
''' Circuitous, LLC –
'''

8. Start  coding:    Circle  class
class Circle:
Hmm,  is  that  really  advanced  technology?

9. New  style  classes
class Circle(object): # new-style class

10. Ini;alize  instance  variables
class Circle(object):
Init  isn’t  a  constructor.    It’s  job  is  to  ini;alize  the  instance  variables.

11. Regular  method
class Circle(object):
def area(self):
return 3.14 * self.radius ** 2.0
Regular  methods  have  “self”  as  ﬁrst  argument.

12. Modules  for  code  reuse
import math # module for code reuse
class Circle(object):
def area(self):
return math.pi * self.radius ** 2.0

13. Class  variables  for  shared  data
import math
class Circle(object):
version = '0.1' # class variable
def area(self):
return math.pi * self.radius ** 2.0

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

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

16. Next  customer  wants  a  perimeter
method
class Circle(object):
version = '0.2'
def area(self):
return math.pi * self.radius ** 2.0
def perimeter(self):
return 2.0 * math.pi * self.radius

17. Second  customer:  Rubber  sheet
company
cuts = [0.1, 0.7, 0.8]
circles = [Circle(r) for r in cuts]
for c in circles:
print 'has a perimeter of', c.perimeter()
print 'and a cold area of', c.area()
print 'and a warm area of', c.area()
print

18. 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 'has an inner area of', t.area()
print 'and an odometer corrected perimeter of',
print t.perimeter()
print

19. Next  customer:    Na;onal  graphics
company
bbd = 25.1
print 'A circle with a bbd of 25.1'
print 'an an area of', c.area()
print
The  API  is  awkward.    A  converter  func;on  is  always  needed.
Perhaps  change  the  constructor  signature?

20. 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'])

21. Class  methods  create  alterna;ve
constructors
class Circle(object):
version = '0.3'
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)

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

23. 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 'has an inner area of', t.area()
print 'and an odometer corrected perimeter of',
print t.perimeter()
print
Hmm,  this  code  doesn’t  work.

24. Alterna;ve  constructors  need  to
an;cipate  subclassing
class Circle(object):
version = '0.3'
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)

25. New  customer  request:    add  a  func;on
'Convert angle in degree to a percentage grade'
Will  this  also  work  for  the  Sphere  class  and  the  Hyperbolic  class?
Can  people  even  ﬁnd  this  code?

26. Move  func;on  to  a  regular  method
class Circle(object):
version = '0.4b'
'Convert angle in degree to a percentage grade'
Really?    You  have  to  create  an  instance  just  to  call  func;on?
Well,  ﬁndability  has  been  improved  and  it  won’t  be  called  in  the  wrong  context.

27. Move  func;on  to  a  sta;c  method
class Circle(object):
version = '0.4'
@staticmethod # attach functions to classes
'Convert angle in degree to a percentage grade'

28. Client  code:  Trucking  company
print 'A inclinometer reading of 5 degrees'
print
Nice,  clean  call.
No  instance  is  required.
The  correct  context  is  present.
The  method  is  ﬁndable.

29. Government  request:    ISO-­‐11110
class Circle(object):
version = '0.5b'
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

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

31. Class  local  reference:  keep  a  spare  copy
class Circle(object):
version = '0.5b'
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

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

33. Class  local  reference  using  the  double
underscore
class Circle(object):
version = '0.5'
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

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

35. Government  request:    ISO-­‐22220
class Circle(object):
version = '0.6'
return self.diameter / 2.0
Oh  no,  this  is  going  to  be  terrible!
I  wish  that  all  aUribute  access  would  magically  transform  to  these  method  calls.

36. Convert  aUribute  access  to  method
access:  property
class Circle(object):
version = '0.6'
return self.diameter / 2.0

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

38. Flyweight  design  paUern:    Slots
class Circle(object):
# flyweight design pattern suppresses
# the instance dictionary
__slots__ = ['diameter']
version = '0.7'
return self.diameter / 2.0

39. 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  speciﬁed.
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.

40. Q  &  A
{"ft_lang_label":"__label__en","ft_lang_prob":0.50519574,"math_prob":0.7745628,"size":11524,"snap":"2023-14-2023-23","text_gpt3_token_len":3149,"char_repetition_ratio":0.17604166,"word_repetition_ratio":0.3621908,"special_character_ratio":0.26822284,"punctuation_ratio":0.18656716,"nsfw_num_words":0,"has_unicode_error":false,"math_prob_llama3":0.9785638,"pos_list":[0,1,2],"im_url_duplicate_count":[null,null,null],"WARC_HEADER":"{\"WARC-Type\":\"response\",\"WARC-Date\":\"2023-05-31T15:57:08Z\",\"WARC-Record-ID\":\"<urn:uuid:70ddd45f-f5e2-4011-9e38-462faa8af2fe>\",\"Content-Length\":\"125109\",\"Content-Type\":\"application/http; msgtype=response\",\"WARC-Warcinfo-ID\":\"<urn:uuid:cef60996-a612-4530-b319-871812ce03b2>\",\"WARC-Concurrent-To\":\"<urn:uuid:06f2321d-4c98-486b-b6e2-d686f12d8563>\",\"WARC-IP-Address\":\"172.67.23.78\",\"WARC-Target-URI\":\"https://speakerdeck.com/pyconslides/pythons-class-development-toolkit-by-raymond-hettinger\",\"WARC-Payload-Digest\":\"sha1:6IUJ2DEACNDLX3KH5ZJHYHWIZFZXZWPD\",\"WARC-Block-Digest\":\"sha1:ORC4WAH3JGR4TCTHYHIXYLQGQY4LTVB2\",\"WARC-Identified-Payload-Type\":\"text/html\",\"warc_filename\":\"/cc_download/warc_2023/CC-MAIN-2023-23/CC-MAIN-2023-23_segments_1685224646937.1_warc_CC-MAIN-20230531150014-20230531180014-00117.warc.gz\"}"}