Table of Contents
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
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.1) 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:2)
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 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 initialize 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) # <class '__main__.ClickerCounter'>
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.