Python in easy steps (2014)
7
Programming objects
This chapter demonstrates how to use Python for Object Oriented Programming.
Encapsulating data
Creating instance objects
Addressing class attributes
Examining built-in attributes
Collecting garbage
Inheriting features
Overriding base methods
Harnessing polymorphism
Summary
Encapsulating data
A “class” is a specified prototype describing a set of properties that characterize an object. Each class has a data structure that can contain both functions and variables to characterize the object.
The properties of a class are referred to as its data “members”. Class function members are known as its “methods”, and class variable members (declared within a class structure but outside any method definitions) are known as its “attributes”.
Class members can be referenced throughout a program using dot notation, suffixing the member name after the class name, with syntax of class-name.method-name() or class-name.attribute-name.
A class declaration begins with the class keyword, followed by a programmer-specified name (adhering to the usual Python naming conventions but beginning in uppercase) then a : colon. Next come indented statements optionally specifying a class document string, class variable attribute declarations, and class method definitions - so the class block syntax looks like this:
class ClassName :
‘‘ class-documentation-string ‘‘‘
class-variable-declarations
class-method-definitions
The class declaration, which specifies its attributes and methods, is a blueprint from which working copies (“instances”) can be made.
All variables declared within method definitions are known as “instance” variables and are only available locally within the method in which they are declared - they cannot be directly referenced outside the class structure.
Typically, instance variables contain data passed by the caller when an instance copy of the class is created. As this data is only available locally for internal use it is effectively hidden from the rest of the program. This technique of data “encapsulation” ensures that data is securely stored within the class structure and is the first principle of Object Oriented Programming (OOP).
It is conventional to begin class names with an uppercase character and object names with lowercase.
All properties of a class are referenced internally by the dot notation prefix self - so an attribute named “sound” is self.sound. Additionally, all method definitions in a class must have self as their first argument - so a method named “talk” is talk( self ).
When a class instance is created, a special __init__( self ) method is automatically called. Subsequent arguments can be added in its parentheses if values are to be passed to initialize its attributes.
The class documentation string can be accessed via the special__doc__ docstring attribute with Classname.__doc__ .
A complete Python class declaration could look like this example:
class Critter :
‘‘‘ A base class for all critter properties. ‘‘‘
count = 0
def __init__( self , chat ) :
self.sound = chat
Critter.count += 1
def talk( self ) :
return self.sound
It is useful to examine the class components of this example:
•The variable count is a class variable whose integer value gets shared among all instances of this class - this value can be referenced as Critter.count from inside or outside the class
•The first method __init__() is the initialization method that is automatically called when an instance of the class is created
•The __init__() method in this case initializes an instance sound, with a value passed from the chat argument, and increments the value of the count class variable whenever an instance of this class is created
•The second method talk() is declared like a regular function except the first argument is self which is automatically incorporated - no value needs to be passed from the caller
•The talk() method in this case simply returns the value encapsulated in the sound instance variable
While a program class cannot perfectly emulate a real-word object, the aim is to encapsulate all relevant attributes and actions.
Creating instance objects
An “instance” of a class object is simply a copy of the prototype created by calling that class name’s constructor and specifying the required number of arguments within its parentheses. The call’s arguments must match those specified by the __init__() method definition - other than a value for the internal self argument.
The class instance object returned by the constructor is assigned to a variable using the syntax instance-name = ClassName( args ).
Dot notation can be used to reference the methods and class variable attributes of an instance object by suffixing their name as instance-name.method-name() or instance-name.attribute-name.
A constructor creates an instance of a class and is simply the class name followed by parentheses containing any required argument values.
Typically, a base class can be defined as a Python module file so it can be imported into other scripts where instance objects can be easily created from the “master” class prototype.
Start a new Python script by declaring a new class with a descriptive document string
class Bird :
‘‘’A base class to define bird properties.’’’
Bird.py
Next, add an indented statement to declare and initialize a class variable attribute with an integer zero value
count = 0
Now, define the intializer class method to initialize an instance variable and to increment the class variable
def __init__( self , chat ) :
self.sound = chat
Bird.count += 1
Finally, add a class method to return the value of the instance variable when called - then save this class file
def talk( self ) :
return self.sound
You must not pass an argument value for the self argument as this is automatically incorporated by Python.
Start another Python script by making features of the class file available, then display its document string
from Bird import *
print( ‘\nClass Instances Of:\n’ , Bird.__doc__ )
instance.py
Next, add a statement to create an instance of the class and pass a string argument value to its instance variable
polly = Bird( ‘Squawk, squawk!’ )
Bird instance - polly
Now, display this instance variable value and call the class method to display the common class variable value
print( ‘\nNumber Of Birds:’ , polly.count )
print( ‘Polly Says:’ , polly.talk() )
Create a second instance of the class, passing a different string argument value to its instance variable
harry = Bird( ‘Tweet, tweet!’ )
Bird instance - harry
Finally, display this instance variable value and call the class method to display the common class variable value
print( ‘\nNumber Of Birds:’ , harry.count )
print( ‘Harry Says:’ , harry.talk() )
Save the file in your scripts directory then open a Command Prompt window there and run this program - to see two instances of the Bird class get created
The class variable count can also be referenced with Bird.count but the encapsulated instance variable sound can only be accessed by calling an instance’s talk() method.
Addressing class attributes
An attribute of a class instance can be added, modified, or removed at any time using dot notation to address the attribute. Making a statement that assigns a value to an attribute will update the value contained within an existing attribute or create a new attribute of the specified name containing the assigned value:
instance-name.attribute-name = value
del instance-name.attribute-name
Alternatively, you can use the following Python built-in functions to add, modify, or remove an instance variable:
•getattr( instance-name , ‘attribute-name’ ) - return the attribute value of the class instance
•hasattr( instance-name , ‘attribute-name’ ) - return True if the attribute value exists in the instance, otherwise return False
•setattr( instance-name , ‘attribute-name’ , value ) - update the existing attribute value or create a new attribute in the instance
•delattr( instance-name , ‘attribute-name’ ) - remove the attribute from the instance
The attribute name specified to these built-in functions must be enclosed within quotes.
The name of attributes automatically supplied by Python always begin with an underscore character to notionally indicate “privacy” - so these should not be modified, or removed. You can add your own attributes named in this way to indicate privacy if you wish but in reality these can be modified like any other attribute.
Start a new Python script by by making features of the Bird class available that was created here
from Bird import *
address.py
Next, create an instance of the class then add a new attribute with an assigned value using dot notation
chick = Bird( ‘Cheep, cheep!’ )
chick.age = ‘1 week’
Now, display the values in both instance variable attributes
print( ‘\nChick Says:’ , chick.talk() )
print( ‘Chick Age:’ , chick.age )
Then, modify the new attribute using dot notation and display its new value
chick.age = ‘2 weeks’
print( ‘Chick Now:’ , chick.age )
Bird instance - chick
Next, modify the new attribute once more, this time using a built-in function
setattr( chick , ‘age’ , ‘3 weeks’ )
Now, display a list of all non-private instance attributes and their respective values using a built-in function
print( ‘\nChick Attributes...’ )
for attrib in dir( chick ) :
if attrib[0] != ‘_’ :
print( attrib , ‘:’ , getattr( chick , attrib ) )
Finally, remove the new attribute and confirm its removal using a built-in functions
delattr( chick , ‘age’ )
print( ‘\nChick age Attribute?’ , hasattr( chick , ‘age’ ) )
Save the file in your scripts directory then open a Command Prompt window there and run this program - to see the instance attributes get addressed
This loop skips any attribute whose name begins with an underscore so “private” attributes will not get displayed in the list.
Examining built-in attributes
Each Python class is automatically created with a number of built-in private attributes whose values can be referenced using dot notation. For example, with class-name.__doc__ to see the document string attribute value of a specified class name.
The built-in dir() function can be used to display a list of all the built-in attributes in a class specified within its parentheses by testing whether each attribute name begins with an underscore.
The built-in __dict__ attribute contains a “namespace” dictionary of class component keys and their associated values. The dictionary of a base class includes its default __init__() method, and all class methods and attributes. The dictionary of a class instance includes its instance attributes.
Start a new Python script by making features of the Bird class available that was created here
from Bird import *
builtin.py
Next, add a statement to create an instance of the class
zola = Bird( ‘Beep, beep!’ )
Now, add a loop to display all built-in instance attributes
print( ‘\nBuilt-in Instance Attributes...’ )
for attrib in dir( zola ) :
if attrib[0] == ‘_’ :
print( attrib )
Then, add a loop to display all items in the class dictionary
print( ‘\nClass Dictionary...’ )
for item in Bird.__dict__ :
print( item , ‘:’ , Bird.__dict__[ item ] )
Finally, add a loop to display all items in the instance dictionary
print( ‘\nInstance Dictionary...’ )
for item in zola.__dict__ :
print( item , ‘:’ , zola.__dict__[ item ] )
The function values stored in the dictionary are the machine addresses where the functions are stored.
Save the file in your scripts directory then open a Command Prompt window there and run this program - to examine the built-in attributes
Bird - zola.
The class dictionary output displays all class attributes, whereas the instance dictionary output displays only instance attributes - the class attributes are shared by the instance.
A class instance is first created in this program so the __init__() method has been called to increment the count value before the dictionary gets listed.
The __weakref__ attribute is simply used internally for automatic garbage collection of “weak references” in the program for efficiency.
Collecting garbage
When a class instance object is created it is allocated a unique memory address that can be seen using the built-in id() function. Python automatically performs “garbage collection” to free up memory space by periodically deleting un-needed objects such as class instances - so their memory address becomes vacant.
Whenever an object gets assigned a new name or gets placed in a container, such as a list, its “reference count” increases. Conversely, whenever these are removed or go out of scope its count decreases. The object becomes eligible for collection when this count is zero.
Destroying an instance of a class may, optionally, call upon a “destructor” to execute a __del__() method - explicitly reclaiming occupied memory space and executing any specified statements.
Start a new Python script by declaring a class with an initializer method creating two instance variables and a method to display one of those variable values
class Songbird :
def __init__( self , name , song ) :
self.name = name
self.song = song
print( self.name , ‘Is Born...’ )
Songbird.py
Next, add a method to simply display both variable values
def sing( self ) :
print( self.name , ‘Sings:’ , self.song )
Now, add a destructor method for confirmation when instances of the class are destroyed - then save this file
def __del__( self ) :
print( self.name , ‘Flew Away!\n’ )
Start another Python script by making features of the class file available
from Songbird import *
garbage.py
Next, create an instance of the class then display its instance attribute values and its identity address
bird_1 = Songbird( ‘Koko’ , ‘Tweet, tweet!\n’ )
print( bird_1.name , ‘ID:’ , id( bird_1 ) )
bird_1.sing()
Now, delete this instance - calling its destructor method
del bird_1
Songbird - Koko
Create two more instances of the class then display their instance attribute values and their identity addresses
bird_2 = Songbird( ‘Louie’ , ‘Chirp, chirp!\n’ )
print( bird_2.name , ‘ID:’ , id( bird_2 ) )
bird_2.sing()
bird_3 = Songbird( ‘Misty’ , ‘Squawk, squawk!\n’ )
print( bird_3.name , ‘ID:’ , id( bird_3 ) )
bird_3.sing()
Songbird - Louie
Finally, delete these instances - calling their destructors
del bird_2
del bird_3
Songbird - Misty
Save the file in your scripts directory then open a Command Prompt window there and run this program - to see memory space handled by garbage collection
The second instance created here is allocated the memory address vacated when the first instance was deleted.
Inheriting features
A Python class can be created as a brand new class, like those in previous examples, or can be “derived” from an existing class. Importantly, a derived class inherits members of the parent (base) class from which it is derived - in addition to its own members.
The ability to inherit members from a base class allows derived classes to be created that share certain common properties, which have been defined in the base class. For example, a “Polygon” base class may define width and height properties that are common to all polygons. Classes of “Rectangle” and Triangle” could be derived from the Polygon class - inheriting width and height properties, in addition to their own members defining their unique features.
The virtue of inheritance is extremely powerful and is the second principle of Object Oriented Programming (OOP).
A derived class declaration adds ( ) parentheses after its class name specifying the name of its parent base class.
Create a new Python script that declares a base class with two class variables and a method to set their values
class Polygon :
width = 0
height = 0
def set_values( self , width , height ) :
Polygon.width = width
Polygon.height = height
Polygon.py
Next, create a script that declares a derived class with a method to return manipulated class variable values
from Polygon import *
class Rectangle( Polygon ) :
def area( self ) :
return self.width * self.height
Rectangle.py
Now, create another script that declares a derived class with a method to return manipulated class variable values
from Polygon import *
class Triangle( Polygon ) :
def area( self ) :
return ( self.width * self.height ) / 2
Triangle.py
Save the three class files then start a new Python script by making features of both derived classes available
from Rectangle import *
from Triangle import *
inherit.py
Next, create an instance of each derived class
rect = Rectangle()
trey = Triangle()
Now, call the class method inherited from the base class, passing arguments to assign to the class variables
rect.set_values( 4 , 5 )
trey.set_values( 4 , 5 )
Finally, display the result of manipulating the class variables inherited from the base class
print( ‘Rectangle Area:’ , rect.area() )
print( ‘Triangle Area:’ , trey.area() )
Save the file in your scripts directory then open a Command Prompt window there and run this program - to see output get displayed using inherited features
A class declaration can derive from more than one class by listing multiple base classes in the parentheses after its name in the declaration.
Don’t confuse class instances and derived classes - an instance is a copy of a class, whereas a derived class is a new class that inherits properties of the base class from which it is derived.
Overriding base methods
A method can be declared in a derived class to override a matching method in the base class - if both method declarations have the same name and the same number of listed arguments. This effectively hides the base class method as it becomes inaccessible unless it is called explicitly, using the base class name for identification.
Where a method in a base class supplies a default argument value this can be used in an explicit call to the base method or alternative values can be supplied by overriding methods.
Create a new Python script that declares a base class with an initializer method to set an instance variable and a second method to display that variable value
class Person :
‘‘’A base class to define Person properties.’’’
def __init__( self , name ) :
self.name = name
def speak( self , msg = ‘(Calling The Base Class)’ ) :
print( self.name , msg )
Person.py
Next, create a script that declares a derived class with a method that overrides the second base class method
from Person import *
‘‘’A derived class to define Man properties.’’’
class Man( Person ) :
def speak( self , msg ) :
print( self.name , ‘:\n\tHello!’ , msg )
Man.py
Now, create another script that also declares a derived class with a method that once again overrides the same method in the base class
from Person import *
‘‘’A derived class to define Hombre properties.’’’
class Hombre( Person ) :
def speak( self , msg ) :
print( self.name , ‘:\n\tHola!’ , msg )
Hombre.py
Save the three class files then start a new Python script by making features of both derived classes available
from Man import *
from Hombre import *
override.py
Next, create an instance of each derived class, initializing the “name” instance variable attribute
guy_1 = Man( ‘Richard’ )
guy_2 = Hombre( ‘Ricardo’ )
Now, call the overriding methods of each derived class, assigning different values to the “msg” argument
guy_1.speak( ‘It\’s a beautiful evening.\n’ )
guy_2.speak( ‘Es una tarde hermosa.\n’ )
Man -Richard
Hombre - Ricardo
Finally, explicitly call the base class method, passing a reference to each derived class - but none for the “msg” variable so its default value will be used
Person.speak( guy_1 )
Person.speak( guy_2 )
Save the file in your scripts directory then open a Command Prompt window there and run this program - to see output from overriding and base class methods
The method declaration in the derived class must exactly match that in the base class to override it.
Harnessing polymorphism
The three cornerstones of Object Oriented Programming (OOP) are encapsulation, inheritance, and polymorphism. Examples earlier in this chapter have demonstrated how data can be encapsulated within a Python class, and how derived classes inherit the properties of their base class. This example introduces the final cornerstone principle of polymorphism.
The term “polymorphism” (from Greek, meaning “many forms” ) describes the ability to assign a different meaning, or purpose, to an entity according to its context.
In Python, the + character entity can be described as polymorphic because it represents either the arithmetical addition operator, in the context of numerical operands, or the string concatenation operator in the context of character operands.
Perhaps more importantly, Python class methods can also be polymorphic because the Python language uses “duck typing” - meaning... if it walks like a duck, swims like a duck, and quacks like a duck, then that bird is reckoned to be a duck.
In a duck-typed language you can create a function to take an object of any type and call that object’s methods. If the object does indeed have the called methods (is reckoned to be a duck) they are executed, otherwise the function signals a run-time error.
Like-named methods of multiple classes can be created and instances of those classes will execute the associated version.
Create a new Python script that declares a class with methods to display strings unique to the class
class Duck :
def talk( self ) :
print( ‘\nDuck Says: Quack!’ )
def coat( self ) :
print( ‘Duck Wears: Feathers’ )
Duck.py
Next, create a Python script that declares a class with like-named methods but to display strings unique to this class
class Mouse :
def talk( self ) :
print( ‘\nMouse Says: Squeak!’ )
def coat( self ) :
print( ‘Mouse Wears: Fur’ )
Mouse.py
Save the two class files then start a new Python script by making features of both classes available
from Duck import *
from Mouse import *
polymorph.py
Next, define a function that accepts any single object as its argument and attempts to call methods of that object
def describe( object ) :
object.talk()
object.coat()
Now, create an instance object of each class
donald = Duck()
mickey = Mouse()
Duck - donald
Finally, add statements to call the function and pass each instance object to it as an argument
describe( donald )
describe( mickey )
Mouse - mickey
Save the file in your scripts directory and open a Command Prompt window there then run this program - to see the methods of associated versions get called
A class can have only one method with a given name - method overloading is not supported in Python.
Object Oriented Programming with Python allows data encapsulation, inheritance, and polymorphism. Base class methods can be overridden by like-named methods in derived classes. Python does not, however, support the technique of “overloading” found in other languages - in which methods of the same name can be created with different argument lists in a single class.
Summary
•A class is a data structure prototype describing object properties with its methods and attribute members
•Each class declaration begins with the class keyword and is followed by an indented code block that may contain a class document string, class variables, and class methods
•Class variables have global scope but instance variables (declared within method definitions) have only local scope
•Instance variables encapsulate data securely in a class structure and are initialized when a class instance is created
•Properties of a class are referenced by dot notation and are addressed internally using the self prefix
•A class instance is a copy of the prototype that automatically calls its __init__() method when the instance is first created
•An attribute of a class can be added, modified, or removed using dot notation or manipulated using the built-in functions getattr(), hasattr(), setattr(), and delattr()
•The name of attributes automatically supplied by Python begin with an underscore character to notionally indicate privacy
•The built-in __dict__ attribute contains a namespace dictionary of class component keys and values
•Python automatically performs garbage collection but the del keyword can remove objects and call the class destructor
•A derived class inherits the method and attribute members of the parent base class from which it is derived
•A method of a derived class can override a matching method of the same name in its parent base class
•Python is a duck-typed language that supports polymorphism for like-named methods of multiple classes