Table Of Contents

Previous topic

APLpy

Next topic

Fitting data with Python

This Page

Object Oriented Programming in Python

What is Object Oriented (00) Programming?

Object Oriented Programming is a philosophy of programming which compartmentalizes data and related functions into “Objects”

Non-Object Oriented Programming (Procedural Programming) can be thought of as a long, ordered list of instructions or commands. Data is visible to the top-level program, and shepherded through the commands from beginning to end.

Procedural Example:

def mixItUp(original):
    characters = list(original)
    temp = characters[3]
    characters[3] = characters[5]
    characters[5] = temp
    temp = characters[10]
    characters[10] = characters[6]
    characters[6] = temp
    return "".join(characters)

test_string = 'this is a test'
output_string = mixItUp(test_string)
print test_string
print output_string

In Procedural code, there are often large numbers of raw data variables visible to the main program.

Object Oriented Example:

class mixableString( object ):
    def __init__(self, text):
        self.__text = list(text)

    def mixItUp(self):
        temp = self.__text[3]
        self.__text[3] = self.__text[5]
        self.__text[5] = temp
        temp = self.__text[10]
        self.__text[10] = self.__text[6]
        self.__text[6] = temp

    def get_text(self):
        return "".join(self.__text)

test_string = mixableString('this is a test')
test_string.mixItUp()
print test_string.get_text()

Here is an example script of the above code.

In Object Oriented code, most variables at the main program level are Objects. Data are stored within these objects, and are rarely accessible from the main program.

In the above example, the class mixableString( object ): defines the format of the object. Think of it as a blueprint. The __init__(self) fiction contains a description of the object. Other functions describe functions the object can perform or can access the information inside the object:: However, just as a blueprint is not a building, the definition of an object is not very useful until you “instantiate” the object. The line test_string = mixableString(‘this is a test’) creates a new instance of the mixableString object. The line test_string.mixItUp() calls the mixItUp function in that object, which performs the scrambling. The line print test_string.get_text() prints out the return value of the get_text function.

Extending this example a little further:

test_A = mixableString('this is a test')
test_B = mixableString('this is another test!')
test_A.mixItUp()
print test_A.get_text()
print test_B.get_text()

This code will produce the following:

thii st a sest
this is another test

test_A and test_B are “instances” of the class mixableString. Actions performed on one instance of a class will only affect that instance. Other instances of the same class are left unaffected.

You might have noticed that the member functions always have the variable self as the first argument, even when the calling function passes no arguments (i.e. test_string.get_text()). The self variable is so named because it is a self-referential variable. Each instance of an object has to be able to separate itself from other instances. The self variable allows python to access the private data of each individual object instance.

You also might have noticed that private data is stored in variables with the convention self.__VarName. The self.__ is the Python convention, and allows each individual object access to its private data.

What are Python Objects?

Python Objects are similar to IDL structures, but with important extra features. Just as in IDL, you can arbitrarily design the data structure within the object (structure). The object can contain whatever data you would like to put into them. In addition, you can also add functions to the objects, which allow you to perform operations on the data without “getting your hands dirty” in the main program. (Just FYI, you can also create Objects in IDL, but Objects are integral to Python)

You’ve Already Been Using OO!

Any python data variable or function to which you can apply tab completion is an object.

Lists behave like objects:

E = []
for n in range(5):
    E.append(n)

append() is a function associated with the list, and its function is to add the value ‘n’ to the end of the list ‘E’. If you type “E.” and press tab, you see that lists are objects and have the following member functions associated with them:

E.append()
E.extend()
E.insert()
E.remove()
E.sort()
E.count()
E.index()
E.pop()
E.reverse()

Here is an example from scipy:

import numpy as np
from scipy import interpolate
x = np.arange(0, 10)
y = np.exp(-x/3.0)
f = interpolate.interp1d(x, y)
xnew = np.arange(0,9, 0.1)
ynew = f(xnew)   # use interpolation function returned by `interp1d`

f is a python function/object returned by the interpolate.interp1d. When you call the function/object with a new set of x values, it will perform the interpolation and return interpolated y values.

One of the most obvious objects commonly used in Python is the matplotlib.pyplot object:

import matplotlib.pyplot as plt
fig = plt.figure(0)
ax = fig.add_subplot(1,1,1)
graph = ax.plot([1,2,3], [6,5,4])

In this example, fig, ax, and graph are all handles which refer to objects returned by the function calls. As previously demonstrated in the Making Publication Quality Plots class, you can use these handles to access and modify information in the plot.

There is a difference between using objects and an Object-Oriented approach. It’s very difficult to NOT use objects of some sort in a python script. However, the fact that you’re using objects doesn’t mean that you’re writing Object-Oriented code. A full Object-Oriented approach is quite a different approach to writing code, and is beyond the scope of this workshop. Without radically changing your approach, you can still reap the benefits of Objects and considerably clean up your code.

When to use your own Classes and Objects

Object oriented programming works well when you have a set of objects (stars, galaxies, etc...) which all have values or measurements associated with them. In a procedural program, you might think of constructing several arrays (i.e. an array for RA, an array for declination, an array for object name, an array for Stellar Mass, an array for...) While this is ok and easy for small numbers of variables, it can quickly become very confusing and spaghettify your code.

When NOT to use your own Classes and Objects

Small programming tasks with few and disparate variables. Writing your own Objects front-loads the design of a program. You can spend hours writing an object, and relatively small amounts of time writing the code which interacts with the objects, but if you’re only using it to do simple tasks, you’ll end up wasting time, both clock time and processor time. Object Orientation would not be the first choice for sleek numerical calculations.

How to Make Your Own Objects

Making your own objects is fun and easy! To create your own object, all you need to do is follow the following format:

class myObject( Object ):
    def __init__(self, init_var):
        self.__var = init_var

    def myFunction_add(self, input_var):
        self.__var += input_var

    def myFunction_getVar(self):
        return self.__var

That’s it! You define the object with the class keyword. The __init__() function is the function used to create a new instance of an object. The __init__() function is the only required function.

Here is a more practical example: Say you are studying a sample of young stars. Each star will have several attributes

class Star( Object ):
    def __init__(self, Name, RA, Dec, Jmag, Hmag, Kmag, SpT):
        self.__Name = Name           #Name of the star
        self.__RA = RA               #Right Ascension (degrees)
        self.__Dec = Dec             #Declination (degrees)
        self.__Jmag = Jmag           #J-band Magnitude
        self.__Hmag = Hmag           #H-band Magnitude
        self.__Kmag = Kmag           #K-band Magnitude
        self.__SpT = SpT             #Spectral Type
        self.__Av = 0.0              #Visual Extinction

    def get_Name(self):
        return self.__Name

    def get_RA(self, segFlag=False):
        if segFlag:
            hours = int(self.__RA/15)
            minutes = int((self.__RA-hours*15)*60/15)
            seconds = (self.__RA-hours*15.0-minutes/60.0*15.0)*3600/15
            return (hours, minutes, seconds)
        else:
            return self.__RA

    def set_Name(self, Name):
        self.__Name = Name

    def calc_Reddening(self):
        self.__Av = photmetricReddening(self.__Jmag, self.__Hmag, self.__Kmag, self.__SpT)

The Star class defines an object which has private members __Name, __RA, __Dec, __Jmag, __Hmag, __Kmag, __SpT, and __Av.

Here is an example of how an object like this might be used

star_list = []

starA = Star('TWHya', 165.46625, -34.7047, 8.2, 7.6, 7.3, 'K7V')
star_list.append(starA)
star_list.append(Star('AlphaBoo', 213.91529, 19.1824, -2.25, -2.81, -2.91, 'K1III'))
star_list.append(Star('TTauri', 65.495, 19.535, 7.24, 6.24, 5.32, 'G5V'))

Now, we can access either the sexegesimal or decimal Right Ascension:

star_list[0].get_RA(segFlag=True)
>> (11, 1, 51.900000000000546)
star_list[1].get_RA()
>> 213.91529

Other Cool Stuff

If you type print star_list, you’ll get something akin to this:

print star_list
>> [<__main__.Star at 0x104f4d0>,
    <__main__.Star at 0x104f450>,
    <__main__.Star at 0x104f490>]

Not exactly legible, but by overloading the __repr__(self), you can overload the text-representation of a Star object:

class Star( object ):
   ...
   def __repr__(self):
       return '%s: %f' % (self.__name, self.__RA)

Now, when you ask Python to print a Star object, it calls this function. So, repeating the previous exercise:

print star_list
>> [TWHya: 165.466250, AlphaBoo: 213.915290, TTauri: 65.495000]

You can also overload the “less-than” or “greater-than” operations. This is powerful because Python now knows how to compare objects of this type, and can sort them. So, in the example above, we could redefine the less-than/greater-than operations to sort a list of stars by Right Ascension (or J-band magnitude, or alphabetically by name, or whatever you like). To do this, you must include in the class definition your new definition of less-than/greater-than:

class Star( object ):
   ...
   def __lt__(self, other):
       if isinstance(other, float):
           return self.__RA < other
       else:
           return self.__RA < other.__RA

Now, we can sort the any list of Star objects by Right Ascension:

star_list.sort()
print star_list
>> [TTauri: 65.495000, TWHya: 165.466250, AlphaBoo: 213.915290]

Here is an example of the above code.

The reason the previous examples are called Overloading is because they over-ride existing functions called __repr__(self) and __lt__(self, other). Where did these functions come from? We certainly didn’t define them from scratch! To understand this, we must understand the concept of “Inheritance”. In the original definition of class Star( object ):, the object is the parent class of the Star Object. Here is an example from Wikipedia:

class Animal:
    def __init__(self, name):    # Constructor of the class
        self.name = name
    def talk(self):              # Abstract method, defined by convention only
        raise NotImplementedError("Subclass must implement abstract method")

class Cat(Animal):
    def talk(self):
        return 'Meow!'

class Dog(Animal):
    def talk(self):
        return 'Woof! Woof!'

Class Animal is the parent class for both Cat and Dog. You may notice that neither Cat nor Dog have the required __init__() function. This is because they inherit it from their parent class, Animal. This means that both objects have a self.name variable. Both Cat and Dog overload the talk(self) function with functions appropriate to their type.:

animals = [Cat('Missy'), Dog('Lassie')]
for animal in animals:
    print animal.name + ': ' + animal.talk()

This prints the following:

Missy: Meow!
Lassie: Woof! Woof!

Custom Objects are powerful ways to organize your data and keeping your code from turning into spaghetti. These are just a few of the things which are possible. The sky’s the limit, so feel free to explore!

Further Reading