Table of Contents
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.