Object-Oriented Python

-- or --

You Too Can Save the World!

... and maybe create some objects along the way


Steve Waterbury
Goddard Python Bootcamp 2015
June 12, 2015

What's an Object?

  • An object is created by instantiating a class
  • A class is created using Python's class statement (usually)
  • In general, any number of objects can be created from a single class ...
  • ... unless the class is given some special magic to make it a "Singleton"

Ok ... so what's a Class?

A "class" is a code structure that contains ...

  1. Data in the form of Fields, a.k.a. "attributes", and
  2. Procedures, a.k.a. "methods"

One way to think of these is:

  1. An object's attributes represent its state
  2. Its methods represent its behavior

Physicists should like this - it's a nice duality ... ;)

Have we seen any examples of Objects yet?

  • Yes! In Python, EVERYTHING is an object!
  • Wow. :)
  • Ok, so why would I need to create my OWN classes and objects?
  • Answer: you don't ... unless you do ...

Python programs can have different styles

  • Simple, short scripts can just be straight-line code -- Do this one thing! BAM!
  • A somewhat longer task might have a recurring sub-task: a function
  • If more functions appear, they might be grouped into one file (a module)
  • Classes come in when higher levels of structure are needed ...

When should I write my own classes?

  • When you have some functions that are passing one or more parameters around
  • When you have some functions that need to access the same "state" information
  • When you are developing a Graphical User Interface (GUI)
  • When you need to represent and process some highly structured data, e.g.:
    • a document or web page
    • data about a real-world "thing" (either man-made or natural)

Now for a story ...

  • In which our hero embarks on an entrepreneurial adventure ...
  • Following the "Lean Startup Methodology" ... and in which
  • You learn the Python's Class Development Toolkit,
  • And see how users will exercise your classes in unexpected ways ...

The Graduation Dilemma

  • Get a haircut and get a real job

-- or --

  • Be CEO of your own startup

... I say "Go for it!"

Company Name and Elevator Pitch

"""Circuitous LLC - 
    An advanced circle analytics company.
"""

Start coding: a Circle class

class Circle:
    """An advanced circle analytic toolkit"""


Hmmm ... is that really advanced technology?

New style classes

class Circle(object):           # new-style class
    """An advanced circle analytic toolkit"""


Yes, this is advanced technology!

Add class variable(s) for shared data

class Circle(object):           # new-style class
    """An advanced circle analytic toolkit"""

    version = "0.1"
    spam = 0.5


Class variables are the same for all instances.
(But they can be overridden by instance variables.)

Use instance variables for instance-specific data

class Circle(object):           # new-style class
    """An advanced circle analytic toolkit"""

    version = "0.1"
    spam = 0.5

    def __init__(self, radius):
        self.radius = radius


__init__ isn't a constructor. Its job is to initialize the instance variables.

Regular method

class Circle(object):           # new-style class
    """An advanced circle analytic toolkit"""

    version = "0.1"
    spam = 0.5

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        "Perform quadrature on this circle."
        return 3.14 * self.radius ** 2

Regular methods have "self" as their first argument.
Hmmm, what about the '3.14'?

Modules for code reuse

import math

class Circle(object):           # new-style class
    """An advanced circle analytic toolkit"""

    version = "0.1"
    spam = 0.5

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        "Perform quadrature on this circle."
        return math.pi * self.radius ** 2

We have "Minimum Viable Product" - ship it!

# Tutorial
from circle import Circle

print 'Circuitous version', Circle.version
c = Circle(10)
print 'A circle of radius', c.radius
print 'has an area of', c.area()
print

First customer: Academia

from random import random, seed
from circle import Circle

seed(8675309)
print 'Using Circuitous(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 %1.f' % avg
print

Next customer, a rubber sheet company, wants a perimeter method

import math

class Circle(object):           # new-style class
    """An advanced circle analytic toolkit"""

    version = "0.1"
    spam = 0.5

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        "Perform quadrature on this circle."
        return math.pi * self.radius ** 2

    def perimeter(self):
        "Find the circle's perimeter."
        return 2.0 * math.pi * self.radius

The rubber sheet company's application

from circle import Circle

cuts = [0.1, 0.7, 0.8]
circles = [Circle(r) for r in cuts]
for c in circles:
    print 'A circlet 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

Notice what happens to the radius attribute here!

Third customer: a national tire chain

from circle import Circle

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

    def perimeter(self):
        "Circumference corrected for the tire width."
        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

Next customer: a national graphics company

from circle import Circle

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 'and an area of', c.area()
print


Their comment: the API is awkward - a converter function is needed.
Perhaps change the constructor?

Need alternative constructors!

from datetime import datetime
print datetime(2013, 3, 16)
print datetime.fromtimestamp(136338616)
print datetime.fromordinal(734000)
print datetime.now()

Remember the examples of alternative constructors for pandas.DataFrame? Yep!