Review

  • Classes in Python are a way to make your own data types.
  • Classes have variables (data)
  • Classes have functions (methods)
  • The special function __init__ initializes a class.

Here's an example of a class:

class BlogEntry :
 
  def __init__(self, title, text) : 
    self.title = title
    self.text = text 
 
  def pretty_print(self) :
    print(self.title, '\n\t', self.text)

The blog entry class has:

  • A member variable self.title (really just title)
  • A member variable text
  • A member function pretty_print
  • An initializer function __init__

Test Your Understanding

  • Classes and object-oriented programming is often difficult for new programmers.
  • Don't despair!

Test your understanding of classes. What does this code print?

class MyClass : 
 
    def __init__(self, value1, value2) :
        print ('Initializing class with', value1, value2)
        self.name = value1 
        self.color = value2 
 
    def print_name(self) :
        print ('Name is:', self.name)
 
    def get_color(self) :
        return self.color 
 
 
print ('Hello classy world.')
mike = MyClass('Mike', 'Blue')
mike.print_name()
print ('Color:', mike.get_color())
 
dan = MyClass('Dan', 'Red')
print (f"{dan.name}'s favorite color is {dan.color}")

Class Design In Practice

  • The goal of object oriented design is to make the programmer's life easier!
  • One of the important ways classes do that is by fostering reuse of code.
  • Inheritance is a tool for allowing classes to share functions.
  • When classes are in an inherited relationship
    • One class is said to be the parent or the base class
    • One class is said to be the child or the derived class

A derived class lets you add functions to the base class. Here's a simple example:

class Base :
 
    def base_function(self) :
        print ('Hello')
 
 
class Derived(Base) : 
 
    def derived_function(self) :
        print ('World')
 
 
d = Derived() 
d.base_function()
d.derived_function()

Notice that the instance of Derived has function that it inherited from the base class Base.

Mix-ins

  • Class inheritance isn't the only way to get the function of one class into another.
  • The mix-in is a conceptually simpler way.
  • The mix-in takes a bit more typing but gives you greater control.

Here's the example above done as a mix-in:

class Base :
 
    def base_function(self) :
        print ('Hello')
 
 
class MixIn : 
 
    def __init__(self) : 
        self.base_instance = Base() 
 
    def base_function(self) : 
        self.base_instance.base_function() 
 
    def derived_function(self) :
        print ('World')
 
 
m = MixIn() 
m.base_function()
m.derived_function()
  • When you have a mix-in you control what functions of the base class are exposed.
  • But the mix-in class must have it's own copy of any mixed functions.

Inheritance and Functions

* It's often the case that a designer wishes to expand on a function in base class. 
* When a derived class has the same function as the base class the derived class wins. 

Here's an example:

class Base :
 
    def base_function(self) :
        print ('Hello')
 
 
class Derived(Base) : 
 
    def base_function(self) : 
        print ('Override')
 
    def derived_function(self) :
        print ('World')
 
 
d = Derived() 
d.base_function()
d.derived_function()

The function in Derived overrides the function in Base.

  • The overriding behavior is most useful when applied to the __init__ function.
  • So what happens if you want to use both?
  • The super() function gives you access to the base (or super) class.
class Base :
 
    def __init__(self) : 
        print ('Initializing Base')
        self.base_var = 'Hello'
 
    def base_function(self) :
        print (self.base_var)
 
 
class Derived(Base) : 
 
    def __init__(self) : 
        print ('Initializing Derived')
        self.der_var = 'World'
        super().__init__()
 
    def derived_function(self) :
        print (self.der_var)
 
 
d = Derived() 
d.base_function()
d.derived_function()
  • Derived classes often have an is-a relationship with the base class.
    • The derived class “is a” base class class.

Here's an example of a inheritance relationship:

class Animal : 
 
    def __init__(self, num_legs) :
        self.legs = num_legs
 
    def get_legs(self) :
        return self.legs 

Animal is the parent class. The child class will have an is-a relationship with an animal. What “is an” animal. A duck!

class Duck(Animal) :
 
    def __init__(self) : 
        super().__init__(2)
 
    def get_sound(self) : 
        return "quack"

The Duck class can be used this way:

>>> a = Duck()
>>> print ('A duck has', a.get_legs(), 'and says', a.get_sound())
A duck has 2 and says quack

There are a few important things to notice here:

  • The Animal class has the get_legs() method
  • The Duck class has the get_sound() method
    • The Duck class can use the parent's get_legs() method

There's one other important line of code to notice…

  • The Animal class has an __init__ function that takes on argument.
  • When Duck is created it has to give the parent class that argument.
    • You can reference the parent class (or superclass) using the super() function.
  • Child classes can override methods in the parent class.
  • When that happens the child class' method is called.

Here's an example where Duck overrides the get_legs() method:

class Duck(Animal) :
 
    def __init__(self) : 
        super().__init__(2)
 
    def get_sound(self) : 
        return "quack"
 
    def get_legs(self) :
        print ('Override! Wings are legs too!')
        return 4

Now calling the overridden method goes to Duck

Class Variables

  • Variables in classes that are used with the self variable are called instance variables.
  • Instance variables are unique for every instance of the class.
    • See self.legs in the example above.
  • Class variables are shared among every instance of the class.
  • Class variables are handy when you want to keep constants around.

Here's an example of the difference between class and instance variables:

class Foo : 
  class_var = 'Class Variable'
  def __init__(self) : 
    self.instance_var = 'Instance Variable'

You can use class_var with the name of the class, but not instance_var you have to use that with an instance of the class:

>>> f = Foo()
>>> f.class_var 
'Class Variable'
>>> f.instance_var 
'Instance Variable'
>>> Foo.class_var
'Class Variable'
>>> Foo.instance_var 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute 'instance_var'

Watch Out!

Class variables are shared!

class Dog:
 
    tricks = []             # mistaken use of a class variable
 
    def __init__(self, name):
        self.name = name
 
    def add_trick(self, trick):
        self.tricks.append(trick)
 
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

Circling Back to the Web

Weeks a go we talked about HTML. That was an advanced topic then, but now everyone needs to know.

Take a look at the lecture notes here:

This week everyone will build a starter Flask application. You'll need to have Flask installed:

Advanced: Google App Engine

Setting up Google AppEngine in Cloud9

$ cd ~ 
$ wget https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-200.0.0-linux-x86_64.tar.gz
$ tar -xvf google-cloud-sdk-200.0.0-linux-x86_64.tar.gz 
$ CLOUDSDK_PYTHON=python2.7 ./google-cloud-sdk/install.sh 
$ CLOUDSDK_PYTHON=python2.7 gcloud init 
$ pip install --upgrade --user google-cloud 
$ echo 'export CLOUDSDK_PYTHON=python2.7' >> ~/.bashrc
# Now exit this shell and start a new one!

You need a Google account to do the next steps. AppEngine costs money but there's an extensive free-tier that allows you to run low traffic apps for free.

Using dev-appserver.py in Cloud9

The Google instructions need to be updated for Cloud9. Run the dev_appserver.py command with the following option:

$ dev_appserver.py --enable_host_checking false app.yaml