PYTHON OBJECT ORIENTED PROGRAMMING (not an alien thing)

Scientific computing with python seminar

by Mauricio J. Del Razo S. 2014

1. FIRST STEPS

Objects in python are like objects in the world (or should I say the universe).

Every object belongs to a certain class of objects with certain properties. For instance, our favorite extraterrestial beings can be objects:


In [1]:
class alien:
    # A property of all aliens
    appearance = 'ugly'

Each of our aliens is an object that belongs to the class alien (Yes, I'm saying Aliens are objects)


In [2]:
ET = alien()
Alf = alien()
Yoda = alien()

Every one of our buddies will have the same appearance


In [3]:
print(ET.appearance, Alf.appearance, Yoda.appearance)


('ugly', 'ugly', 'ugly')

All aliens might have the same rights, but they are not the same


In [4]:
ET.color = 'red'
ET.head = 'giant'
ET.planet = 'Mars?'

Alf.color = 'yellow'
Alf.head = 'funny with an elephant trompe'
Alf.planet = 'Melmac'

Yoda.color = 'green'
Yoda.head = 'wrinkled with long hairy ears'
Yoda.planet = 'Jupitron'

In [5]:
print('ET:',ET.appearance,ET.color, ET.planet, ET.head)
print('Alf:',Alf.appearance,Alf.color, Alf.planet, Alf.head)
print('Yoda:',Yoda.appearance,Yoda.color, Yoda.planet, Yoda.head)


('ET:', 'ugly', 'red', 'Mars?', 'giant')
('Alf:', 'ugly', 'yellow', 'Melmac', 'funny with an elephant trompe')
('Yoda:', 'ugly', 'green', 'Jupitron', 'wrinkled with long hairy ears')

BUT WATCH OUT w/CLASS VARIABLES AND INSTANCE VARIABLES!!!

Class variables are usually shared among all the objects of that class; they are more "global", like appearance.

Instance variables are those unique to each object, like color, planet and head.

If we are not careful, we might end up messing up:


In [6]:
alien.appearance = 'handsome stallion'
print(ET.appearance,Alf.appearance,Yoda.appearance)


('handsome stallion', 'handsome stallion', 'handsome stallion')

2. INTRODUCTION TO OBJECT ORIENTED PROGRAMMING

We call an object an instance of a certain class .

Each class (alien) has its class variables (appearance) that everyone in their class share with the same value.

Each instance of a class (an object like ET,Alf or Yoda) has unique values for their instance variables (color, planet and head)

It is better to define the variables when creating the class to avoid a mess. Whenever we define a new object using a class, the function "def init" is called. We use the variable "self" to make sure we are refering to the instance variables specific to each object.

Lets be more careful and add some complexity to our previous example:


In [7]:
# Second version of the alien class
class alienV2(object):
    # Class variable
    number = 0
    
    # Initialization function
    def __init__(self, name, color, planet, head):
        # Instance variables
        self.name = name
        self.color = color
        self.planet = planet
        self.head = head
        alienV2.number += 1
    
    # A class function
    def speak(self):
        print('My name is ' + self.name + ' from the planet ' + self.planet + '; my head is '
              + self.head + ', and my skin gets more and more ' + self.color + 
               ' when I tan in the sun. There are other ' + str(alienV2.number -1) + ' aliens like me hiding in this code.')
        print(' ')

We can also define all the instance variables at the moment we create the object:


In [8]:
alien_1 = alienV2('ET','red','Mars','big')
alien_2 = alienV2('Alf','yellow','Melmac','funny with an elephant trompe')
alien_3 = alienV2('Yoda','green','Jupitron','wrinkled with long hairy ears')

We can also define actions or functions within the class. For instance, we can make our aliens introduce themselves:


In [9]:
alien_3.speak()


My name is Yoda from the planet Jupitron; my head is wrinkled with long hairy ears, and my skin gets more and more green when I tan in the sun. There are other 2 aliens like me hiding in this code.
 

We can also define an array of objects:


In [10]:
alienV2.number = 0
alien_list = [] 
alien_list.append(alienV2('ET','red','Mars','big'))
alien_list.append(alienV2('Alf','yellow','Melmac','funny with like an elephant trompe'))
alien_list.append(alienV2('Yoda','green','Jupitron','wrinkled with long hairy ears'))

In [11]:
for i in range(len(alien_list)):
    alien_list[i].speak()


My name is ET from the planet Mars; my head is big, and my skin gets more and more red when I tan in the sun. There are other 2 aliens like me hiding in this code.
 
My name is Alf from the planet Melmac; my head is funny with like an elephant trompe, and my skin gets more and more yellow when I tan in the sun. There are other 2 aliens like me hiding in this code.
 
My name is Yoda from the planet Jupitron; my head is wrinkled with long hairy ears, and my skin gets more and more green when I tan in the sun. There are other 2 aliens like me hiding in this code.
 

Excercise:

  1. Create your own class with several instance variables and one class variable
  2. Add a function/action to your class
  3. Create an array of objects that are instances of your new class

You can use the following template:


In [12]:
class your_class(object):
    # Class variables here
    
    def __init__(self,instance_var):
        # Instance variables here (and in the init function)
        self.instance_var = instance_var
    
    # Define a functon or action for your object
    def action(self):
        # Do something, like:
        self.instance_var = 0

3. INHERITANCE

If we want to extend the functionality of a class, we have a three possibilities: modify the class, rewrite the class or use class inheritance. If we modify the class, we will have to modify all the code that calls this class; rewriting the class seems to be a waste of time, since most of the functionality might already be there. The best choice is to use clas inheritance, which follows this structure:

#define child class class child_class(parent_class): # initialization is not neccesary def __init__(self): print "Calling child constructor" def childMethod(self): print 'Calling child method'

In [13]:
# Define a child class that has alienV2 as parent class
%pylab inline
from IPython.display import Image

class alien_interactive(alienV2):
    
    # Initialize child class function 
    def __init__(self, imgsrc, name, color, planet, head):
        super(alien_interactive, self).__init__(name, color, planet, head)
        self.imgsrc = imgsrc 
   
    def show_yourself(self):
        img = Image(filename=self.imgsrc)
        display(img)
        self.speak()


Populating the interactive namespace from numpy and matplotlib

In [14]:
alien_list = []
alienV2.number = 0 # Note it's not alien_interactive.number
alien_list.append(alien_interactive('images/ET.jpg','ET','red','Mars','big'))
alien_list.append(alien_interactive('images/Alf.jpg','Alf','yellow','Melmac','funny with like an elephant trompe'))
alien_list.append(alien_interactive('images/yoda.jpg','Yoda','green','Jupitron','wrinkled with long hairy ears'))

In [15]:
alien_list[1].show_yourself()


My name is Alf from the planet Melmac; my head is funny with like an elephant trompe, and my skin gets more and more yellow when I tan in the sun. There are other 2 aliens like me hiding in this code.
 

In [16]:
from IPython.html.widgets import interact

def show_interactive(alien_number):
    alien_list[alien_number].show_yourself()

interact(show_interactive, alien_number=(0,2));


My name is Alf from the planet Melmac; my head is funny with like an elephant trompe, and my skin gets more and more yellow when I tan in the sun. There are other 2 aliens like me hiding in this code.
 

Excercise:

  1. Creata a child class from the class you created before
  2. Add new instance variables and a new function
  3. Create an array of objects with: "[ your_class_child(instance_variables) for i in range(3)] "

You can use the following template


In [17]:
class your_class_child(your_class):
    
    # Initialize child class function 
    def __init__(self, child_instance_variable, parent_instance_variable):
        super(your_class_child, self).__init__(parent_instance_variable)
        # Define child instance variables
        self.child_instance_variable = child_instance_variable
    
    # Add an action unique to the child class
    def child_action(self):
        # Do something, like
        self.child_instance_variable = 0

4. WHAT ARE *args AND **kwargs?

This two commands are very useful when it comes to object oriented programming; however, they don't have to be neccesarily used in the context of object oriented programming. We can also used them when calling a function or other situations. Here we will cover a couple of examples in object oriented programming.

*args:

The argument *args let you pass an arbitrary number of arguments to your function. This is particularly useful, when you want to inherit from a parent class without knowing too much about it. For instance, lets make another child class of alienV2 with the same functionality as before, bu this time we will use *args.


In [18]:
# Define a child class that has alienV2 as parent class
class alien_interactiveV2(alienV2):
    
    # Initialize child class function 
    def __init__(self, imgsrc,*args):
        super(alien_interactiveV2, self).__init__(*args)
        self.imgsrc = imgsrc 
        
    def show_yourself(self):
        try:
            img = Image(filename=self.imgsrc)
        except:
            img = Image(url=self.imgsrc)
        display(img)
        self.speak()

In [19]:
alien_list.append(alien_interactiveV2('http://tinyurl.com/mnwj7wj','Ridley Scott','white','LV223','voluptuous'))
interact(show_interactive, alien_number=(0,3));


My name is Alf from the planet Melmac; my head is funny with like an elephant trompe, and my skin gets more and more yellow when I tan in the sun. There are other 3 aliens like me hiding in this code.
 

**kwargs:

The argument **kwargs let you pass a dictionary to your function. This is particularly useful, when you want to write into specific instance variables of your parent class without knowing too much about it, nor the order you have to pass them. For instance, lets make another child class of alienV2 with the same functionality as before, bu this time we will use **kwargs and *args.


In [20]:
# Define a child class that has alienV2 as parent class
class alien_interactiveV3(alienV2):
    
    # Initialize child class function 
    def __init__(self, *args,**kwargs):
        super(alien_interactiveV3, self).__init__(**kwargs)
        self.imgsrc = args[0] 
        
    def show_yourself(self):
        try:
            img = Image(filename=self.imgsrc)
        except:
            img = Image(url=self.imgsrc)
        display(img)
        self.speak()

In [21]:
characteristics = {'head':"like Homer Simpson's with tentacles",
                   'name':'Zoidberg', 
                   'color':'pink', 
                   'planet':' Decapod 10'}
alien_list.append(alien_interactiveV3('http://tinyurl.com/ngdd6ul',**characteristics))
interact(show_interactive, alien_number=(0,4));


My name is Zoidberg from the planet  Decapod 10; my head is like Homer Simpson's with tentacles, and my skin gets more and more pink when I tan in the sun. There are other 4 aliens like me hiding in this code.
 

Excercise:

  1. Create a new child class from the class you created before
  2. Use *args and **kwargs to simplify the code
  3. Add an object to your array of objects using this new class

5. FINAL NOTES

Everything in python is an object


In [22]:
txt = 'my name is Zoidberg'

In [23]:
txt


Out[23]:
'my name is Zoidberg'

In [24]:
txt.upper()


Out[24]:
'MY NAME IS ZOIDBERG'

Loades modules like numpy, scipy, matplotlib, etc... are objets...


In [25]:
import numpy as np

In [26]:
np.__name__


Out[26]:
'numpy'

In [27]:
np.__class__


Out[27]:
module

Write np. and then press tab...


In [28]:
np.


  File "<ipython-input-28-a58a88215da2>", line 1
    np.
       ^
SyntaxError: invalid syntax

Yesss! Classes can get very complicated, and that's why inheritance is very useful!!!

For some more advance applications, also check "polymorphism".

Although, the examples presented in this notebook seem dumb; they provide all the core understanding of object oriented programming required to do scientific computing.

Some ideas of interesting applications in scientific computing:

  • Simple and efficient use of complicated data structures: np.array, graphs or networks (neighbors and connectivity like instance variables ).
  • Defining operations and/or algorithms in arbitrary data structures (sparse matrix vs non-sparse).
  • Stochastic simulations of jump processes.
  • Many, many, many others...

6. ASSIGNMENT: AN INTEGRATOR PARENT

1 Create a function func for the function $f(x) = sin(x)$.

2 A template of the parent class integrator is provided. This class will have all the common functionality to integrate a function f in the interval [a,b]. However, the method of integration is not yet providednor the integration function. Read carefully the template, note the method of integration is not defined:


In [ ]:
class Integrator:
    def __init__(self, a, b, n):
        self.a, self.b, self.n = a, b, n
        self.points, self.weights = self.method()
        
    def method(self):
        raise NotImplementedError('no rule in class %s' %self.__class__.__name__)

3 Add an additional function called integrate(self,func) inside the class Integrator, which calculates the integral from the weigths $w_i$: self.weights at different points x_i:self.points. Note this should be arrays. The integral is calculated as

$$ \int_a^b f(x) dx = \sum_{i=0}^{n-1} w_i f(x_i) $$

4 Create a child class of Integrator called Trapezoidal, where you define a function called method(self). This function should return two arrays: x and w corresponding to the points and their current weights.This should be the values correponding to the trapezoidal rule for integration. If you are not familiar with it, you can look for it online

5 Create a child class of Integrator called Midpoint. It should do the same as Trapezoidal but employing the midpoint method for numerical integration.

6 Create a separate function of x that plots $ \int_0^x sin(x) dx = 1 - cos(x), $ using any of the two methods you developed.


In [ ]: