How to use Python for object-oriented programming (OOP)
Object-oriented programming (OOP) is a programming method that reduces development times and makes it easier to read and maintain your code. Python can use structured and functional paradigms as well, but its OOP functionality is incredibly strong and intuitive. The language includes many built-in data types, like lists and strings.
In OOP, complex problems can be solved by building a hierarchy of objects that interact with one another. In Python, data and corresponding behaviors, or methods, are tightly coupled. Data within an object is protected and can be accessed by the self parameter from inside the object. Methods belong to an object, rather than being defined as global functions seen in other methodologies.
One advantage of using OOP in Python is that it’s incredibly flexible. Let’s delve into that.
Why use object-oriented programming in Python?
OOP is an imperative programming language, in which the control flow is fully described within the script itself. Declarative programming, by contrast, describes the desired results of computation in terms of the language’s core functions.
Python’s flavor of OOP creates and defines objects that combine data and methods. So, an object has attributes that define its state, such as “height”, “age” and “location”. That same object, representing a person, has methods that define its behavior, like “singing” or “dancing”. Because these are not global attributes, you can talk about the height, age, and location of a building object and use actions like “construct” or “demolish” with the expectation that the object will perform its defined behavior without any confusion.
Objects are building blocks. They’re used to define and solve larger problems by breaking them down into smaller pieces. For each kind of object, you write a class to define its attributes. Object-oriented programs can be built out of standard modules that you’ve used before, or ones designed by someone else. You never have to duplicate your work. That makes programming faster and more portable. It’s also more teachable, for the same reasons.
Find out more about OOPs, programming paradigms and learn the basics of Python with our Python tutorial.
How to instantiate an object in Python
If you’ve never heard the term, “instantiate” means that you create a new object from a class. The new object, also called class instance, will contain all the data attributes and method defined by the class. So, classes serve as blueprints for the creation, or instantiation, of objects.
Let’s assume we’re writing code for a kitchen environment. We can model our objects so that they represent objects in the real world, like glasses, plates, bottles, and cups.
We define the behavior and state of every object. For example, containers could be empty or full. They could be open or closed. In order for the objects to interact with one another as intended, we need to give them scope and definition. We can talk about the maximum volume of every container. We can say whether it will accept wet or dry contents. We can define the temperature ranges that it can store safely.
By doing this, the objects will be able to interact. A cup can be poured into a glass, for example. If the contents are too hot, the glass might shatter even though the cup was fine with those contents.
So how does modifying the state of an object work in OOP in Python? Let’s look at an example:
Creating abstraction with OOP in Python
A coding abstraction hides unnecessary information. These are important mental shortcuts that allow a programmer to focus more on solving the task at hand. For example, instead of asking “is the volume of the bottle’s contents equal to the bottle’s total capacity?”, an abstraction would simply ask “is the bottle full?” The more abstract version is more concise, so it is preferable.
Abstract concepts can become new ideas in Python. Using the Python additions operator, the plus sign can add numbers, merge the contents of multiple lists, combine a literal string with the contents of a variable, and more:
By defining abstractions like the additions operator for the container, we can write code that reads like human language:
OOP terminology can be confusing, with different sources using different terms for the same concept. We’ve collected the most-easily misunderstood terms for your convenience:
OOP term | Explanation |
---|---|
Object | A “smart” or “alive” data structure that combines internal state and functionality. |
Class | A blueprint for the creation of objects. Classes roughly correspond to types in non-OOP languages. |
Class instance | Another term for “object”. Note that, although the term is sometimes incorrectly used, there is no such thing as an “object instance”. |
Attribute | A component of an object, also called “member”. Data attributes are also called “fields” and correspond to variables that store state. Function attributes are called “methods”. |
Instance attribute | An attribute that belongs to an object. Instances attributes are unique to each object. |
Class attribute | An attribute that belongs to a class. Class attributes are shared between all instances of the class. |
Class object | Classes are objects themselves. When referring to a class in code, we may use the term “class object”. |
How does OOP in Python work?
Attributes are the object’s components, such as data and functions. Instead of restricting access to attributes via keywords, Python considers attributes starting with an underscore to be private. For example, _internal_attr
or _internal_method()
.
Methods can be used to set or retrieve object details. They use self as their first argument, which points to a specific instance of a class. Inside methods, self acts as a placeholder for an instantiated object.
Since every objects encapsulates its own state, accessing internal data via the reference self._internal
is fine. External access violates encapsulation and should be avoided:
Defining a class in Python
Simple values like strings and numbers are used in Python to represent single pieces of information. To define something more complex, like a data structure that is made up of multiple attributes such as height, weight, and age, you create a class.
Once the class is defined, it allows you to instantiate objects. In Python, a class is called as a function. When called as a function, the class acts as a constructor that delivers a class instance. Internally, the constructor calls the initialization function __init__()
that sets up the object’s state.
Let’s say we’re modeling the concept of a container as a class, with the name “Container”. The following methods might be used to define important interactions:
Method | Explanation |
---|---|
__init__
|
Initialize new container with initial values. |
__repr__
|
Outputs condition of container. |
volume
|
Outputs volume of container. |
volume_filled
|
Outputs filling state of the container. |
volume_available
|
Outputs remaining volume in container. |
is_empty
|
Tests if container is empty. |
is_full
|
Tests if container is full. |
empty
|
Empties container and returns contents. |
_add
|
Internal method to add a substance without performing checks. |
add
|
Public method to add specified amount of substance if space is available. |
fill
|
Fills the remaining volume of the container with a substance. |
pour_into
|
Transfers contents of the container to another container. |
__add__
|
Implements addition operator for container; falls back to pour_into method.
|
With that in mind, here’s the code that we can use to define the container class:
If we instantiate a glass and fill it with water, the result will be a glass at full capacity:
And when we take the water back, the glass is empty again:
For a slightly more complex example, we’ll mix orange juice and wine in a pitcher, which is our version of a mimosa! First, we create the containers, then we fill them with wine and orange juice respectively:
We can use the addition-assignment operator += to pour the contents of both containers into the pitcher:
This works because our container class uses the __add__()
method. The assignment of pitcher += bottle is transformed into pitcher = pitcher + bottle. Then pitcher + bottle is translated by Python into pitcher.__add__(bottle)
.
Static attributes
A static method is bound to a class, not an object instantiated from the class. Static methods can’t access or modify an object’s state but can be called without referencing an object.
Static methods are mostly utility methods that only operate on their arguments. In contrast, a class method receives a class as its first parameter. They’re mostly used to perform an operation on a complete class, without the need or desire to access any individual object. For example, conversions (metric to imperial,etc.) can be implemented as static methods.
Attributes are called “static” because they exist before an object is instantiated. Static attributes can be either data or methods.
Python doesn’t have a static keyword to explicitly distinguish between object attributes and class attributes. Instead, the @staticmethod decorator is used.
Let’s look at an example of a static method for our Container classes. This one will be a conversion of milliliters to fluid ounces:
Access to static attributes is performed with an attribute reference in dot notation using the pattern Classname.attr.
Interfaces
An “interface” is the collection of all public methods of an object. It defines and records the behavior of an object, serving as a kind of API.
Unlike C++, Python doesn’t have separate layers for interfaces (AKA header files) and implementations. There is no explicit “interface” keyword.
Python determines which methods are bound to an object and the class that it was instantiated from at runtime. Therefore, the language doesn’t require explicit interfaces. Instead, it uses “duck typing”:
“If it walks like a duck and it quacks like a duck, then it must be a duck” – Source: https://docs.python.org/3/glossary.html#term-duck-typing
What’s duck typing? It means your Python program can flexibly use objects of different classes in the same context. You can rely on the language checking for the presence of a given method and using it as long as the context and syntax are correct.
Inheritance
Inheritance is a useful feature in object-oriented programming that allows you to build a hierarchy of classes. So, a child class already defines all of the attributes of the parent. Multiple inheritance can be used flexibly in Python as well.
Let’s extend our container class to allow for sealed containers. We’ll define a new class called SealableContainer, which will inherit attributes from Container. We can also define a new Sealable class. It will allow us to apply and remove the seal. Since the Sealable class just provides another class with new methods, it is called a “mixin”:
Our SealableContainer inherits from the Container class and the Sealable mixin. We override the __init__()
method and define two new parameters. This will let us set the contents and seal state of our SealableContainer at instantiation.
Similar to using the __init__()
method, we override other methods to differentiate our SealableContainer from the unsealed Container. We override __repr__()
so that the open or closed state is also being output. We also override the empty()
and _add()
methods, with the effect that closed containers must be opened before emptying or filling them. We use super()
to access the existing functionality of the parent class.
Congratulations, you’ve just learned how to use Python for object-oriented programming. Let’s celebrate by mixing a Cuba Libre! We’ll need a glass, a small bottle of cola, and a shot glass filled with 20cl of rum:
Now we can add ice to the glass before pouring the rum in. Because the cola bottle starts off in the sealed state, we need to open it first. Then we can pour the contents into the glass: