In what follows, we assume you know basic Python programming concepts including:
After completing this tutorial, you will have learned:
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:
We next explore each of these in the context of an object from the real world: a microwave oven.
At any given instant, a microwave oven has a particular state:
All these attributes, which can be defined in terms of data, collectively define the oven's state.
A given microwave oven also has predefined behavior:
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.
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.
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.
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.
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.
Let's define a class in Python and do some stuff with it.
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:
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:
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.
Here's a Python definition for a
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:
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
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.
In our class definition, the roles of the
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.
__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.
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
type(my_clicker) # <class '__main__.ClickerCounter'>
you can see that
my_clicker is an object of the
ClickerCounter class, which is defined in the
If you print
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
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(a) # or a.click(self)
Python's inner workings manage the fist parameter (i.e.,
self) automatically and implicitly.
__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
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.
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.