====== Object-Orientation Fundamentals with Python ======
===== What you'll need to know =====
In what follows, we assume you know basic Python programming concepts including:
* How to create and use variables.
* How to define and use functions.
* How to do command-line input and output.
* How to use math operators.
* How to use the Python dot operator (e.g. ''math.sin(0)'').
* How to use control flow statements.
===== What you'll learn =====
After completing this tutorial, you will have learned:
* The fundamental concepts of object-orientation in programming.
* How to create and use simple Python objects.
===== Object-oriented concepts =====
The concept of an object in computing comes directly from the concept of an "object" in the real world. The concepts critical to understanding object-orientation in programming are:
* state
* behavior
* encapsulation and protection
* self-governance
We next explore each of these in the context of an object from the real world: a microwave oven.
==== State ====
At any given instant, a microwave oven has a particular state:
* //Is it cooking?//
* //At what power level?//
* //How much cooking time is left?//
* //What time does it think it is?//
* And so on.
All these attributes, which can be defined in terms of data, collectively define the oven's state.
==== Behavior ====
A given microwave oven also has predefined **behavior**:
* //Push the "1" button// → start cooking at maximum power for one minute.
* //Push the "+30sec" button// → start cooking for 30 seconds at maximum power or add 30 seconds to the cooking time if it's already cooking.
* //Push a magical combination of buttons// → set the internal clock.
* And so on.
These operations are ones that happen to be public-facing (i.e., operations a user can engage). There might very well also be operations that go on inside the oven that the user will never be aware of to support the oven's functioning. We call the //public-facing behavior// (i.e., the operations a user can engage) the oven's **interface**.
==== Encapsulation and protection ====
As a user, I change the state of the oven by engaging one or more of the operations in the oven's interface. In other words, I'm not expected to open up the oven and hack at its guts to make the magnetron work at maximum level for whatever time I want. As a user of the oven, I don't need to know how a magnetron or the power supplies work, and if I am a typical user I don't really care. I only need to know what changes in state to expect from the "cook 1 minute" or "add 30 seconds" operations.
Along the same lines, the way I change the clock's setting is by engaging the "set current time" operation. The operation 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 operation.
In fact, even if I wanted to get at the magnetron or directly change the clock's setting, I couldn't---at least not without a whole lot of pain and bother. The oven's insides are normally protected against public fiddling by screws and scary labels. But even if I manage to open it up, I would still need to know, say, where the clock module is, what its electronics parameters are, and a whole bunch of other hackery. No thanks. It's better to just use the designed-in public-facing behavior---its interface---than get a PhD in microwave oven design.
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"). This is a key concept in object design. Keeping someone out of stuff they should not be allowed to access is called **protection** or sometimes **information hiding**.
In many languages, properly hiding things the user has no business getting to is considered part of encapsulation.
==== Objects ====
Our microwave oven takes responsibility for managing its own state using a set of predetermined behaviors. This makes it a **self-governing entity**: an entity that takes responsibility for managing its own state through a set of predetermined behaviors.
Object-oriented languages are languages that let you create self-governing entities. Such self-governing entities are called **objects**.
To put it another way, an object is a program entity that encapsulates state (via attributes) and behavior (via operations involving those attributes) for some meaningful abstraction.
==== Class-based object-orientation ====
{{:python:about_python:farberware4241-200.jpeg?nolink|}}
I have a Farberware 4241 microwave oven in my office. It's small, but it gets the job done. There is a factory somewhere making these by the thousand million. They make all the Farberware 4241s from a master plan, a plan that defines what the Farberware 4241 //is//. In object-oriented design terminology, we would call that master plan a **class**. A class contains all the specifications needed to make a particular kind of object.
Languages that support **class-based object-oriented programming** let you define classes at a very high level. Once you've defined a class you can then create **instances**: objects created from a class. The process of creating an instance from a class is called **instantiation**.
Thus my oven is an instance of a Farberware 4241, and you might say the Farberware factory spends it's entire day instantiating 4241 ovens.
==== 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.((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 prototype-based programming.)) I will leave it you to get your Google on and learn more about prototype-based object-orientation.
Python is inherently class-based, but it's possible to do prototype-based programming with it. In what follows, we will stick exclusively class-based object-orientation.
===== Python objects =====
Let's define a class in Python and do some stuff with it.
==== Defining classes in Python ====
We are going to write a class for implementing one of these:((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).
)) \\ {{..:about_python:tottycounter.jpg?234|}}
In case you've never seen one of these bits of advanced technology before, it's a clicker-counter or tally-counter. It has two controls: a button you click to advance the counter by one and another you press to reset the count to zero. Our goal is to build one of these in software using object-orientation.
One way to start building a model for a class is to start listing the public-facing behavior (or the //interface//) you want an object of that 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 data we'll need to keep track of the state of a clicker-counter. In this case, it's pretty simple: all we really need is one integer to store the //count// value.
So, a summary of what we need so far is:
* a //click// operation
* a //reset// operation
* 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. The **operations** our object will be capable of, 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 [[http://www.c2.com/cgi/wiki?CamelCase|CamelCase]] with the first letter capitalized.
==== 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
Class definitions follow the same header/suite pattern for compound statements you've seen before with control flow statements and function definitions. The keyword ''class'' in the header declares that what follows is a class definition. The ''ClickerCounter'' identifier is the name of our class. The parenthesis that follow the name of the class are there for implementing an advanced feature that we'll not tackle here.
The suite of the class definition nests additional compound statements: function definitions that make up the instance methods.
In this class definition, we define three instance methods: ''%%__init__(self)%%'', ''click(self)'', and ''reset(self)''. These methods will belong to objects created with this class; they won't have any meaning outside of this context.
Notice that all instance methods have ''self'' as the first argument. The identifier ''self'' above is a special variable that means "this instance" or "yourself". So ''def click(self):'' indicates we are defining a method that operates on the object and ''self.count'' refers to the instance variable ''count'' associated with the object. To access instance variables and methods within the class definition, you must use the ''self'' qualifier.
A common error is to forget to use ''self'' as the first parameter in an instance method definition. Another common error is to forget to use ''self'' to qualify the names of instance variables inside instance methods. Things won't go as expected if you do either of these.
=== The __init__ method and instance variables ===
In our class definition, the roles of the ''click()'' and ''reset()'' methods are probably self-evident: ''click()'' increments the object's ''count'' member variable by one, and ''reset()'' resets it to zero.
The role of the ''%%__init__%%'' method however is not nearly as obvious. ''%%__init__%%'' is a special name that identifies a method that is automatically executed when you create an instance from the class. It's used to //init//ialize the instance. This is what's known as a **constructor**: an instance method that's executed automatically as soon as an object is instantiated.
The ''%%__init__%%'' method is the place to create and initialize your instance variables. Here we are creating only one instance variable: for storing the count state.
Weird things can happen if you forget to create and initialize all your class' instance variables in the constructor---so don't forget to do this.
==== Instantiation and use ====
Once you have a definition for a class, invoking the name of the class with a pair of parenthesis will instantiate an object from that class.
my_clicker = ClickerCounter()
If you use ''type()'' to see what ''my_clicker'' is:
type(my_clicker) #
you can see that ''my_clicker'' is an object of the ''ClickerCounter'' class, which is defined in the ''%%__main__%%'' scope.
If you print ''my_clicker'':
print(my_clicker) # <__main__.ClickerCounter object at 0xb743ceec>
you can also see that it's an object and the memory location where it's stored.
Stuff like the above is useful for debugging. Most of the time, once you instantiate objects, you just start to use them. So let's instantiate a clicker, click it three times, and print the resulting ''count'' value.
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:', a.count) # ... is it?
And here we instantiate two different counters to show they have independent state:
a = ClickerCounter() # instantiate 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:', a.count) # ... is it?
b = ClickerCounter() # instantiate 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:', b.count) # ... is it?
print('a.count:', a.count) # this counter-clicker should still be 3
One thing to note is that you **do not pass anything for the member function's ''self'' parameter when you call the method using an instance qualifier**. In other words, you call
a.click()
not
a.click(a)
# or
a.click(self)
Python's inner workings manage the fist parameter (i.e., ''self'') automatically and implicitly.
==== Parameterized constructors and methods ====
The ''%%__init__%%'' constructor is just a special method, and a method is just a function attached to an object. So, there's nothing keeping you from adding parameters to a constructor if needed. Let's modify the ''ClickerCounter'' class so it has a count limit: when the limit is reached, the counter will automatically reset. Furthermore, we will set this upper limit with a parameter in the constructor.
First we need to redefine the constructor to take a limit parameter and set an instance variable to that value:
def __init__(self, upper): # parameterized constructor
self.count = 0 # create instance variable for storing the count state
self.limit = upper # create instance variable for storing the count limit
Next we need to modify the ''click'' method so it wraps around when the limit is reached:
def click(self): # method for clicking
self.count = self.count + 1
# wrap the count if over the limit
if self.count > self.limit:
self.count = 0
This yields:
class ClickerCounter():
def __init__(self, upper): # parameterized constructor
self.count = 0 # create instance variable for storing the count
self.limit = upper # create instance variable for storing the limit
def click(self): # method for clicking
self.count = self.count + 1
# wrap the count if over the limit
if self.count > self.limit:
self.count = 0
def reset(self): # method for resetting
self.count = 0
a = ClickerCounter(10) # make a clicker-counter that goes up to 10
for i in range(5): # click it 5 times
a.click()
print(a.count) # should be 5
for i in range(6): # click it 6 more times
a.click()
print(a.count) # should wrap around to zero.
General methods can also be parameterized. The process is identical to using parameters with constructors.
==== Next steps ====
This just scratches the surface of what's possible with object-oriented programming. In particular, we haven't addressed the matter of protecting our class' members or reusing code through //inheritance//. But with even with the basics you have learned here, you can now create Python objects whose state is maintained through predefined behaviors.
Copyright © 2011-2018 Mithat Konar. All rights reserved.