User Tools

Site Tools


python:about_python:about_python_iii

About Python III

Python is object oriented

Object oriented concepts

Object oriented languages are languages that let you create self-governing entities. A self-governing entity is one that takes responsibility for managing its own state through a set of predetermined behaviors. Such self-governing entities are called objects.

The concept of an object in computing comes directly from the concept of an “object” in the real world. An example of an object from the real world is a mobile telephone.

State

At any given instant, a mobile telephone has a particular state. Is it on? What is the battery charge level? What phone numbers are stored in its address book? Does it have a connection to the service provider? And so on. All these attributes collectively define the phone's state.

Behavior

A mobile phone also comes with a set of behaviors. When I push a certain set of buttons in the right order, it uses its network connection to connect to another phone. When I push a certain button long enough, it turns off. When I push another button, the sound level increases. And so on. These operations collectively define the phone's behaviors. We call the public-facing set of behaviors (i.e., the ones a user can engage) the phone's interface.

Protection and encapsulation

As a user, I can change the state of the telephone only by engaging one or more of its behaviors in the phone's interface. In other words, I can't open up the phone and apply some mystery physics to change the charge level of the battery. To change the state of the charge level, I have to engage the phone's charging behavior (plugging in the power adapter) or wait for the battery to discharge. I (as a user) don't know how these things happen, and if I am a typical user I don't really care. I only know what changes in state to expect from the “plug adapter in”, “let phone idle”, and “place a call” behaviors.

As another example, the only way I can add a contact to the phone's address book is by engaging the “enter new contact” behavior. The behavior takes care of all the details of getting the data to the right place, etc. As a user, I don't need to know exactly how it does it. I only need to know what to expect from the behavior. In fact, even if I wanted to directly add new data to the address book (i.e., bypassing the behavior and fiddling with the phone's memory myself), I couldn't–at least not without a whole lot of pain and bother. The phone's insides are normally protected against public fiddling by screws. And even if I manage to open it up, I would still need to know where the memory is, what its electrical parameters are, and a whole bunch of other hackery. No thanks. It's better to just use the public-facing set of behaviors–its interface–than get a PhD in phone design.

Keeping the user out of bits that the user should not be allowed to access is called protection. In general computing, the “not caring about how it works–I only need to know what it does” is part of encapsulation (literally, “to place in a capsule”). With objects, encapsulation also includes making sure that the public cannot directly reach members that it has no business directly reaching (i.e., protection).

A mobile phone is an object

Since our mobile phone takes responsibility for managing its own state using a set of predetermined behaviors, it is a self-governing entity. And so it is an object.

Class-based object orientation

I have three friends who have mobile phones. Jimmy has a Nokia N8. Angie and Sally each use an Apple iPhone 4. There is a factory (probably in China) that is making Nokia N8s by the hundreds. There is also a factory (definitely in China) making iPhones by the thousand millions.

Both factories make their phones based on master plans. The master plan for Apple's iPhone 4 defines what an iPhone 4 is. From one plan, the iPhone factory can make many, many phones. In computing terminology, we would call that master plan a class. A class contains all the specifications needed to make a particular kind of object. Each iPhone 4 begins its life identical to every other freshly-made iPhone 4 because they were made using the same master plan. So, the iPhone that Angie pulled out the box that her dad gave her on Christmas was identical to the one that Sally pulled out of her box on her birthday. But as Angie and Sally begin using their phones, the state of each phone changes. The behaviors designed into the phones don't (and won't) change, but the state does.

In computing, we call objects that have been created from classes instances. Thus Jimmy's phone is an instance of a Nokia N8, Angie's phone is an instance of an iPhone 4, and Sally's is another instance of an iPhone 4.

Prototype-based object orientation

There is another kind of object orientation called prototype-based object orientation or prototype-based programming. This is the kind of object-orientation used in JavaScript and some other languages.1) I will leave it you to get your Google on and learn more about prototype-based object orientation.

Python objects

Python is inherently class-based, but it's not hard to do prototypal-style programming as well. We will stick to class-based object orientation here.

We will next define a class in Python and instantiate and use some objects.

Defining classes in Python

We are going to write a class for implementing one of these:2)

A good way to start building a model for an object or class is to start listing the public-facing behaviors (or the interface) you want the class to have. A pretty comprehensive list of the things you might do with a clicker-counter is:

  • click: makes the count increase by one.
  • reset: sets the count to zero.

Next we can think about what kinds of things we'll need to keep track of the state of a clicker-counter. In this case, it's pretty simple: we really only need an integer to store the count value.

So, a summary of what we need so far is:

  • a click behavior
  • a reset behavior
  • an integer to store the count

In Python, object attributes (which together make up the state) are defined in special variables called instance variables. A class definition can include as many instance variables as it needs to store the state. In our case, we are getting off easy: the clicker-counter only needs one. Object operations (which make up behavior) are defined using instance methods. In Python a method is nothing more than a function defined inside a class. We call anything belonging to a class (e.g., instance variables and methods) a member of the class.

Class names in Python traditionally use CamelCase with the first letter capitalized.

Constructors in Python

When objects are created (i.e., instantiated), you almost always need to do some initialization of the object. This is so common that most object-oriented languages implement the concept of a constructors: a method or method-like entity that is automatically invoked whenever you instantiate an object.

Constructors in Python are implemented just like regular methods, but they use the special name __init__ (that's *two* underscores, “init”, and two more underscores). All the member variables that the object will use should be created and initialized inside an __init__ method. Not only is it good programming practice, but also if you try to access a member variable that has not yet been created, the Python interpreter will bark at you and your script will probably fail.

Our first Python class

Here's a Python definition for a ClickerCounter class:

class ClickerCounter():
    def __init__(self):  # constructor
        self.count = 0   # create instance variable for storing the count state
 
    def click(self):     # method for clicking
        self.count = self.count + 1
 
    def reset(self):     # method for resetting
        self.count = 0

Note: What look like blank lines above are actually lines with four spaces in them. If you don't “indent” empty lines, then Python will not include the following line in the same block! If you copy/paste this code, you may have to add the spaces yourself.

The keyword self above is a special variable that means “this instance” or “yourself”. So self.count refers to the instance variable count associated with the object and def click(self): means we are defining a method that operates on the object. Notice that all instance methods have self as the first argument.

Once you have a definition for a class, you can instantiate a objects:

my_clicker = ClickerCounter()

You can see what my_clicker is with type():

>>> type(my_clicker)
<class '__main__.ClickerCounter'>

This tells us that my_clicker is an object of the ClickerCounter class, which is defined in the __main__ scope.

We can also see it's value:

>>> print my_clicker
<__main__.ClickerCounter object at 0xb743ceec>

Note that the value of my_clicker is not 0 or 1 or 2 or whatever value my_clicker.count has. The value of my_clicker is actually the memory address where the my_clicker object is stored. 0 or 1 or 2 or whatever is the value of the my_clicker.count instance variable.

Stuff like the above is useful for debugging. Most of the time, once you instantiate objects, you start to use them:

a = ClickerCounter()    # make a clicker-counter
a.click()               # count should now be 1
a.click()               # count should now be 2
a.click()               # count should now be 3
print a.count           # ... is it?
 
b = ClickerCounter()    # make another clicker
b.click()               # count should now be 1
b.click()               # count should now be 2
b.reset()               # count should now be 0
print b.count           # ... is it?
print a.count           # this counter-clicker should still be 3

Protecting members

The ClickerCounter class we created above works fairly well, but it has a problem:

a = ClickerCounter()    # make a clicker-counter
a.click()
a.reset()
a.count = -666          # WTF?
print a.count
a.count = "I am in your object, eating your chickens."          # WTF?
print a.count

The problem is that the count instance variable is exposed to the public. This is analogous to making a real-world clicker-counter that lets the user open up the device and set the display to any value s/he chooses. Not the best idea. Usually.

One solution to this problem is to adopt a coding practice3): “Do not ever access an instance variable directly.”

a.count = 3             # don't do this
print a.count           # don't do this either!

So, if our coding practice says we are not allowed to access instance variables directly, how can you possibly use them? The answer is accessors and mutators.4)

Accessors

An accessor is a method that lets you read from (but not write to) instance variables. Typically, they are named get_{name-of-variable}(self) or get{name-of-variable}(self).

class ClickerCounter():
    def __init__(self):  # constructor
        self.count = 0
 
    def get_count(self):  # accessor for count
        return self.count
 
    def click(self):      # click the counter
        self.count = self.count + 1
 
    def reset(self):      # reset the count
        self.count = 0
Mutators

A mutator is a method that lets you write to an instance variable. Typically, they are named set_{variable-name}(self,{new-value}) or set{variable-name}(self,{new-value}). A mutator should check that the new-value makes sense. For example, we might define a set_count mutator as follows:

class ClickerCounter():
    def __init__(self):  # constructor
        self.count = 0
 
    def set_count(self, count):  # mutator for count
        if isinstance(count, int) and count >= 0:
            self.count = count
        else:
            print count, "is not an integer!"
 
    def get_count(self):  # accessor for count
        return self.count
 
    def click(self):      # click the counter
        self.count = self.count + 1
 
    def reset(self):      # reset the count
        self.count = 0

Now we can:

a = ClickerCounter()      # make a clicker-counter
a.click()
a.click()
a.click()
print a.get_count()       # should be 3
a.reset()
print a.get_count()       # should be 0
a.set_count(33)
print a.get_count()       # should be 33
a.set_count("Evil...")    # what happens?
 
print a.count             # this works, but don't do it!
a.count = 99              # this works, but don't do it!

As long as we don't access instance variables directly but use accessors and mutators instead, we will have a proper, self-governing ClickerCounter.

Note that without the set_count mutator, we still have a nice, usable class. If your class does not need a particular mutator or accessor, don't feel that you must add it.

Properties

While the “Don't ever access instance variables directly” coding practice is an effecive way of encapsulating an object's state (with or without name-mangling), a much better approach is to use Python properties. We might look at that later.

Copyright © 2011 Mithat Konar. All rights reserved.

1)
C++, Java, C#, and PHP support only class-based object orientation; JavaScript and Ruby are inherently prototype-based; Objective-C is class-based but lets you do prototypal-based programming.
2)
Picture from: “Totty Clicker - Gadgets at Play.com (UK).” Play.com (UK): DVDs, Music CDs, MP3s, Video Games, Books, Electronics & Gadgets - Free Delivery. http://www.play.com/Gadgets/Gadgets/4-/11566684/Totty-Clicker/Product.html?ptsl=1&ob=Price&fb=0# (accessed January 25, 2011).
3)
A coding practice is a rule that you as a programmer or programming team agree on regarding how you should write your code. Coding practices are not syntax rules imposed by the language. For example, a common Python coding practice is to place a single underscore in front of instance members that should not be accessed directly.
4)
Python also provides a certain amount of additional protection via name-mangling. But name mangling might not work entirely as you expect unless you carefully study how name-mangling works as well as Python's scoping rules.
python/about_python/about_python_iii.txt · Last modified: 2017/12/06 01:57 by mithat