Introduction To Python: Part 5

In this lecture, we will establish the basics of Object Oriented programming, working with classes in Python.

Object Oriented programming has been the dominant model for the past couple decades. You will certainly need to use it during your career. First, because it’s a good way to solve a lot of problems. Second, because you will need to use other libraries that are Object Oriented. Even a fair bit of the Python standard library is Object Oriented.

Definitions

There are a few terms that are common in object oriented programming. We’ll need to understand these terms in order to work well together.

class
A category of objects: particular data and behavior: A “circle” (same as a type in python)
instance
A particular object of a class: a specific circle
object
The general case of a instance – really any value (in Python anyway)
attribute
Something that belongs to an object (or class): generally thought of as a variable, or single object, as opposed to a ...
method
A function that belongs to a class

The distinction between these last two is a bit fuzzy. Since functions are first-class objects in Python, a method really is an attribute. But the distinction is useful since it allows us to separate actions an object can perform from data an object contains.

Python Classes

In Python, we build a class using the class statement (py3). Evaluating the statement creates a new type object. So classes are types, interesting!

The class object is created when the statement is evaluated, much like def. The name of the class is bound as a symbol to the class object in the namespace where it is defined. For compatibility, you should always subclass from object as shown below. We’ll discuss why at a later time.

In [4]: class C(object):
   ...:     pass
   ...:
In [5]: type(C)
Out[5]: type

Here’s a sample class, just about the simplest class you can write. In this case, our class has an x and y class attribute. These attributes are available on the class object itself, or on instances of the object via the dot operator.

>>> class Point(object):
...     x = 1
...     y = 2
>>> Point
<class __main__.Point at 0x2bf928>
>>> Point.x
1
>>> p = Point()
>>> p
<__main__.Point instance at 0x2de918>
>>> p.x
1

The basic structure of a real class might look more like this. In this case, the x and y attributes are instance attributes. They are attached to self.

class Point(object):
    # everything defined in here is in the class namespace

    def __init__(self, x, y):
        # everything attached to self is in the instance namespace
        self.x = x
        self.y = y

## create an instance of the class
p = Point(3,4)

## access the attributes
print("p.x is:", p.x)
print("p.y is:", p.y)

The Initializer

The __init__ special method is called when a new instance of a class is created. You can use it to do any set-up for your instance that you need.

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

It is passed the arguments you provide when you call the class object:

Point(x, y)

But there’s a third parameter in the declaration of that __init__ method. What is that self thing?

self

For every method of a class, the instance of the class on which the method is invoked is passed as the first parameter. Calling the instance self in the parameters list is only a convention. But this convention is one that you do want to use. Anything else would be immensely confusing to any experience Python programmer.

Classes & Methods

Most classes will need an __init__ special method. Other methods may also be required. Methods of the class will take some parameters and use them to manipulate the attributes of self. The method may or may not return some useful value, it’s not required that they do so.

class Circle(object):
    color = "red"

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

    def grow(self, factor=2):
        self.diameter = self.diameter * factor

Be careful when calling methods on an instance. You may be greeted with the following TypeError:

In [205]: C = Circle(5)
In [206]: C.grow(2,3)

TypeError: grow() takes at most 2 arguments (3 given)

You’ll think to yourself “Huh? But I only gave two arguments!”.

The trick is that self is implicitly passed by Python when you invoke a method on an instance. This is automatic. Methods of a class are called bound when invoked on an instance. They are unbound if they are invoked on the class object itself. When unbound, an instance must be explicitly passed as the first argument.

In [2]: c1 = Circle(5)

In [3]: c1.grow
Out[3]: <bound method Circle.grow of <__main__.Circle object at 0x103ceae80>>

In [4]: c1.grow(3)

In [5]: c1.diameter
Out[5]: 15

In [6]: Circle.grow
Out[6]: <function __main__.Circle.grow>

In [7]: Circle.grow(c1, 2)

In [8]: c1.diameter
Out[8]: 30

The requirement to explicitly specify self, and then have it implicitly passed seems confusing at first. But like most of Python’s quirks, there is a strong rationale behind it. The BDFL has made the decision that self is here to stay, and even written extensively about why.

Namespaces and Classes

For the following examples, consider this class definition of a point with color and size.

class Point(object):
    size = 4
    color= "red"
    def __init__(self, x, y):
        self.x = x
        self.y = y

Anything assigned to a self.<xyz> attribute is kept in the instance name space. Remember that self is the instance. The x and y attributes of our example Point class are instance attributes, found in the instance namespace.

Anything that is assigned in the class scope is a class attribute. These attributes are kept in the class namespace. Every instance of the class shares the same class namespace.

Note

methods defined by def are class attributes as well.

The class has one namespace, the instance has another. When you ask an instance for an attribute that is defined in the class namespace, it is also found. This means that you can use self to get at attributes that belong to the class namespace.

class Point(object):
    size = 4
    color= "red"
    ...
    def get_color():
        return self.color

p3 = Point(5, 6)
p3.get_color()
'red'

Under the covers, Python looks for the name first in the instance namespace, and then in the class namespace. If it isn’t found in either place, then an AttributeError is raised.

Wrap Up

In this lesson, we’ve learned the basics of programming classes in Python. We learned a few terms that we will use to describe the objects we work with. We learned about the initializer method you can use to set up new instances. We learned about the different namespaces available in Python classes. And we learned a bit about the self parameter and how it works.

This should help you to get started using Python classes to build your data structures.