Object-Oriented Programming (OOP)
To classify programming languages, we distinguish between different programming paradigms (cf. Overview). Programming paradigms characterize the thought concepts underlying our code. Many programming languages favor one specific programming paradigm but allow for elements of other programming paradigms as well. Python is a multi-paradigm language, i.e., our code can follow multiple programming paradigms. Among the programming paradigms supported by Python is the object-oriented programming paradigm. When this programming paradigm is implemented in practice, we refer to it as object-oriented programming (OOP).
Objects and Classes
So far, most of our code examples have been sequences of instructions, i.e., they have followed the imperative programming paradigm. In OOP, our programs describe how objects interact with each other. Every object has a unique identity as well as specific attributes ('things that characterize it') and methods ('things it can do').
OOP is inspired by the way humans perceive the world: The coffee mug on our table can be seen as an object. It has certain properties, e.g., a color, a weight, an age, and a fill status, which are its attributes. We can fill it, empty it, or clean it - so filling, emptying and cleaning are its methods. Even if our colleague's coffee mug stemmed from the same producer and was visually indistinguishable from our mug, it would still be a different mug since the two mugs have different identities.
In Python, every something is an object - regardless of whether it is a String, a number, a list, a function, a class (see below) or an entire module (i.e., a collection of definitions and statements in Python code).
In programming languages following the object-oriented programming paradigm, every object has (at least) one type. We specify a type by defining a class of objects. In our class definition, we describe the attributes (not necessarily: attribute values) and methods that should be shared by all objects of the class. A concrete object is then referred to as an instance of the class, and the process of producing a new object is called instantiation of the (type) class. Normal attributes of different instances can take different values; these attributes are also called instance attributes. In addition, there may be attributes whose values are shared across all instances of the class (i.e., the values don't belong to specific instances). These attributes are called class attributes.
To continue the previous example: Our coffee mug is an instance of the class
mug
, which has the attributescolor
,weight
,age
, andfill
. The attribute values of our mug might be white, 400g, 2 years, and empty; the attribute values of our colleague's mug could be black, 300g, 1.5 years, and half-full.
Objects are created.
In many programming languages, we create objects by calling a constructor.
In Python, the role of the constructor is filled by a special method named __init__
, which is responsible for the instantiation of the new object (the term constructor is very popular in discussions concerning other programming languages but it is hardly used in the Python community).
Objects occupy memory even if they are no longer needed.
Therefore, objects that are effectively unreachable, i.e., no longer referenced by any variable, are destroyed by Python's garbage collection so that the allocated memory can be reused.
Inheritance
It is often possible to refine the description of objects beyond the attributes and methods specified in its class.
For example, if we collect objects of the class Document
for a legal database, all of these documents will likely have a text
, which we can print()
(below, we use the name describe()
for this method since print()
is a built-in function in Python).
Examples of specific kinds of documents might be a (scientific) Paper
and a Judgment
.
All instances of these two classes will also have a printable text since both classes are specialized documents.
But judgments might have an additional attribute court
, whereas papers might have an additional attribute author
.
Therefore, from an OOP perspective, there exists a class hierarchy:
A paper is a document and inherits the attributes and methods of the class Document
.
However, since a paper has an additional attribute author
, not all documents are papers.
The class Paper
is a subclass of the class Document
, which is a superclass of the class Paper
.
Wherever our program requires a document, we can also supply a paper because a paper has all of a document's attributes and methods (but not vice versa).
Following biological terminology, the construction of class hierarchies is referred to as inheritance.
Python is among the languages that allow multiple inheritance, i.e., classes can inherit from multiple other classes.
In some programming languages (e.g., Java), however, a class can inherit directly only from one other class, and support constructions like interfaces are needed to mimick multiple inheritance.
The class at the top of Python's class hierarchy is called object
, and all other classes are (direct or indirect) subclasses of object
.
A popular tool for the graphical representation of class hierarchies is offered by the Unified Modeling Language, UML. For a brief introduction to UML's class diagrams, see the corresponding Wikipedia article.
Using Classes in Python
To instantiate an object in Python, the __init__
method is called.
All values needed to initialize the object (i.e., create its initial state) are passed as arguments to the __init__
method.
When methods are called on an object, they automatically receive the current instance or - in the case of class methods - the class itself as the first argument.
This argument is not explicitly stated in the method call but it is specified in the method definition as the first parameter.
The name of this first parameter is self
for instance methods and cls
for class methods.
The name of the first parameter could also be
darth_vader
;self
andcls
are only conventions. However, adherence to these conventions is strongly recommended.
# A simple class
# --------------
class Document(object):
# Defines the Document class as a subclass of object;
# (object) can also be omitted (and is omitted by convention)
def __init__(self, text): # Constructor with one parameter only
self.text = text # Sets the value of the instance attribute 'text'
# to the value of the argument 'text'
def describe(self): # Defines an instance method
print(self.text) # Prints the content of the instance variable text
aDocument = Document("I'm a document.")
# Calls the constructor (__init__) with the argument "I'm a document."
# Creates a new object of type Document
# and creates a reference to the object stored in the variable 'aDocument'
aDocument.describe()
# Calls the method describe() on the object referenced by the variable aDocument
# Subclasses and Inheritance
# --------------------------
class Paper(Document):
# The class Paper inherits from the Document class
def __init__(self, text, author):
super().__init__(text) # super() returns the superclass.
self.author = author # Creates and initializes the additional attribute 'author'
aPaper = Paper("I'm a paper!", "An Author")
aPaper.describe() # The method describe() is inherited
# from the Document class
Thus far, all attribute values have been associated with a concrete object.
We can also associate attribute values with an entire class, e.g., when keeping track of the total number of documents created in a system.
Attributes that are associated with an entire class are called class attributes.
Class attributes are defined outside of the __init__
method and can be accessed via the name of the class (i.e., ClassName.AttributeName
instead of ObjectName.AttributeName
).
# Static Attributes
# -----------------
class Document:
# object as the superclass can also be omitted
createdDocuments = 0 # class attribute
def __init__(self, text):
self.text = text
Document.createdDocuments += 1 # Increases class attribute by 1
# (access not via self but via Document)
start = Document.createdDocuments # How many documents exist at the start?
oneDoc = Document('Text.') # Creates a document
anotherDoc = Document('More Text.') # Creates another document
end = Document.createdDocuments # How many documents exist now?
print(f'Start: {start} documents, end: {end} documents.')
# Output: Start: 0 documents, end: 2 documents.
Working with Objects in Python
firstDoc = Document('CONTENT') # Creates a document with the content CONTENT
secondDoc = Document('CONTENT') # Creates another document with the content CONTENT
firstDoc is secondDoc # Tests for identity
firstDoc == secondDoc # Testet for equivalence - equivalence of objects
# must be defined in the class Document
# (__eq__(self,other))
id(firstDoc) # Returns the ID of the first document
id(secondDoc) # Returns the ID of the second document
# - different from the ID of the first document
type(firstDoc) # Returns the type of the first document:
# the class Document