Happy 1st of September!
I have decided to write a letter to you and share some thoughts and gratitude for your visits.
I recently walked into my favorite park and saw beautiful white pigeons picking some worms. They were mingling without any concern with other “usual” pigeons. They looked so different but were also quite indifferent to their differences from each other. At the same time, they were pigeons who did not care about feather color differences. They all enjoyed green grass and little worms in it.
Birds are so beautiful, all of them. And I have decided to do a simple wrap-up of simple Python classes defining birds and pigeons. I think that this post is a good recap or start for understanding Object-Oriented Programming and the available functionality in Python.
Table of Contents
- Object-Oriented Programming
- Classes in Python
- Class Methods
- Inheritance
- Polymorphism
- Encapsulation
- Conclusion
- References
Object-Oriented Programming (OOP) in Python
Object-Oriented Programming (OOP) is a programming paradigm that organises code around objects — bundles of data (attributes) and behaviour (methods) defined by classes. In Python, OOP lets you model any real-life or abstract entity as a reusable class and create many independent instances from it.
There are several programming paradigms in computer science, and writing code is the best way to actually understand them. This post focuses on Object-Oriented Programming: as the name suggests, we deal with objects, describing real-life or abstract things with classes.
For instance, birds. We can define a class for birds and also define a class for pigeons. Both classes will be similar, right? And with Python and OOP we can describe birds and pigeons very neatly and create many instances of both classes, thus creating many bird objects defined by both classes!
Defining Classes in Python with init
A class is a blueprint that defines the attributes and methods shared by every object created from it. In Python, we define a class with the class keyword. Our simple Bird class has just two instance attributes, bird_type_name and the bird’s color. The __init__ method is a special constructor method that Python calls automatically to set up each new bird object.
class Bird:
def __init__(self, bird_type_name, color):
self.bird_type_name = bird_type_name
self.color = color
After defining the “Bird” class, we can create an instance of a bird with needed attributes. We can create a few bird objects, two white and one black. Notice that we have used the list comprehensions to create a list of pigeons, which are instances of our “Bird” class. This way, we can create so many different colored pigeons!
bird_type_name = "Pigeon"
bird_colors = ["white", "black", "white"]
pigeons = [Bird(bird_type_name=bird_type_name, color=color) for color in bird_colors]
print(pigeons)
[<__main__.Bird object at 0x10b268e18>, <__main__.Bird object at 0x10b2fa440>, <__main__.Bird object at 0x10b2fa518>]
That’s Python’s default __repr__ at work — just the class name and a memory address, since we have not told Python how to describe a Bird in a friendlier way. All three bird instances live at different addresses in memory — each one is a unique object.
The bird attributes can be accessed this way.
print(f"Our first bird is a {pigeons[0].color} {pigeons[0].bird_type_name}.")
Our first bird is a white Pigeon.
Just like in real life, our created birds are different too!
print(pigeons[0]==pigeons[1])
False
Interestingly, we can change the class attributes quite easily! Imagine our first white pigeon becomes a blue pigeon!
pigeon = pigeons[0]
pigeon.color = "blue"
pigeon.color
'blue'
Class Methods in Python: Defining Object Behaviour
A class method is a function defined inside a class that operates on an object’s data through the self parameter. Class methods define the behaviour (“functions”) that our objects can do. Most pigeons can fly.
Thus, we will create the fly() method. We do not want to make too speedy pigeons.
We set the limits of pigeon fly speeds and the maximum distances to make the pigeons as realistic as possible.
I have defined the max_fly_speed and max_distance class attributes after consulting the page
“How Fast do Pigeons Fly?.”
class Bird:
max_fly_speed = 150 # km/hour
max_distance = 676 # in kilometers
# Class constructor
def __init__(self, bird_type_name, color):
self.bird_type_name = bird_type_name
self.color = color
# Class method to get bird types
def bird_type(self):
return self.bird_type_name
# Class method to fly birds
def fly(self, velocity, distance):
if velocity > self.max_fly_speed:
print(f"Sorry, our {self.bird_type_name} does not want to fly with the speed over {self.max_fly_speed} (km/h).")
return False
if distance > self.max_distance:
print(f"Sorry, our {self.bird_type_name} does not want to fly further than {self.max_distance} (km). They get tired.")
return False
print(f"The {self.color} {self.bird_type_name} flies at the speed of {velocity} (km/h) to a distance of {distance} (km). Done!")
return True
Notice self. It’s not a Python keyword — it’s a naming convention so strong that every Python developer follows it, and it’s how a method reaches back into the class methods and attributes of its own instance.
For instance, the bird_type() method returns the self.bird_type_name variable initialised
in the class constructor.
pigeon = Bird(bird_type_name="Pigeon", color="white")
pigeon.bird_type()
'Pigeon'
Let’s try to make the pigeon fly to the 800 km distance! It can’t! It should be a different bird, for instance, an albatross!
pigeon = Bird(bird_type_name="Pigeon", color="white")
pigeon.fly(velocity=100, distance=800)
Sorry, our Pigeon does not want to fly further than 676 (km). They get tired.
False
Lazy pigeon! Ok, it will exercise with flying to 100km!
pigeon.fly(velocity=55, distance=100)
The white Pigeon flies at the speed of 55 (km/h) to a distance of 100 (km). Done!
True
Inheritance in Python: Reusing a Parent Class
Inheritance is an OOP mechanism that lets a child class reuse the attributes and methods of a parent class without rewriting them. There are so many different types of birds with their features. Some birds, such as pigeons, can fly, while others, like penguins, cannot fly but swim very well. We could hard-code all these differences inside the fly() method and alter the functionality with a check of the Bird class attribute bird_type_name, but inheritance offers a more elegant way of creating classes in OOP.
Inheritance allows us to inherit the properties of a parent class. We can make our Bird
class a parent class, reusable in its children’s classes. We can also add more features to the
children’s class without modifying the parent class. For instance, let’s create
a class, Penguin, which will inherit the functionality of the Bird class, while introducing a new
method swim().
class Penguin(Bird):
bird_type_name = "Penguin"
color = "grey"
def __init__(self):
super().__init__(bird_type_name=self.bird_type_name,
color=self.color)
def swim(self):
print(f"A {self.bird_type_name} can swim very well.")
return True
Notice that we redefined the __init__ constructor method, calling the parent class’s __init__ with super() and passing along bird_type_name and color. Skip that call, and the Python interpreter complains, demanding we define the bird type and color every time we create a penguin instance.
Now, we can create a penguin object.
new_bird = Penguin()
We never define a color attribute inside the Penguin class, yet it’s there anyway — inherited from Bird. We can also change it any time a penguin ages and changes its plumage.
new_bird.color = "grey"
new_bird.color = "black and white"
We can also use the parent class method bird_type() from the child class, not realising it.
new_bird.bird_type()
'Penguin'
Polymorphism in Python: Overriding Inherited Methods
Polymorphism is an OOP concept where a child class redefines (overrides) an inherited method to change its behaviour while keeping the same method name. It is pretty annoying that our implementation of the Penguin class allows penguins to fly!
And should we call the fly() method, we get a wrong output.
That happens because we inherit the fly() method from the parent class.
new_bird.fly(velocity=55, distance=100)
The black and white Penguin flies at the speed of 55 (km/h) to a distance of 100 (km). Done!
True
Fortunately, the polymorphism concept is about modifying the inherited method in the child class. When re-implementing the child method, we “override” it with new functionality. In this final implementation of the Penguin class, we change the fly() method, returning False and printing the message that penguins do not fly. Notice that we can also redefine fly() without arguments “velocity” and “distance.”
class Penguin(Bird):
bird_type_name = "Penguin"
color = "grey"
def __init__(self):
super().__init__(bird_type_name=self.bird_type_name,
color=self.color)
def fly(self, velocity=0, distance=0):
print(f"A {self.bird_type_name} cannot fly.")
return False
def swim(self):
print(f"A {self.bird_type_name} can swim very well.")
return True
new_bird = Penguin()
new_bird.fly()
A Penguin cannot fly.
False
Encapsulation in Python: Protected and Private Variables
Encapsulation is an OOP principle that restricts direct access to an object’s internal data, exposing it only through controlled methods such as getters and setters. Providing access to all class methods and variables is not always a good idea. Sometimes we deal with sensitive information that should be protected. Since Python is a bit different from Java and does not have access modifiers, we can use a different approach to controlling access to the class data and methods.
For instance, we want to make Bird class variables max_distance and max_fly_speed as private. There are several ways of implementing it.
The first option is to precede the variable names with an underscore — the official convention for marking a name as a non-public part of the API.
class Bird:
_max_fly_speed = 150 # km/hour
_max_distance = 676 # in kilometers
# Class constructor
def __init__(self, bird_type_name, color):
self.bird_type_name = bird_type_name
self.color = color
# Class method to get bird types
def bird_type(self):
return self.bird_type_name
# Class method to fly birds
def fly(self, velocity, distance):
if velocity > self._max_fly_speed:
print(f"Sorry, our {self.bird_type_name} does not want to fly with the speed over {self._max_fly_speed} (km/h).")
return False
if distance > self._max_distance:
print(f"Sorry, our {self.bird_type_name} does not want to fly further than {self._max_distance} (km). They get tired.")
return False
print(f"The {self.color} {self.bird_type_name} flies at the speed of {velocity} (km/h) to a distance of {distance} (km). Done!")
return True
We can still easily read _max_distance from outside the class — the underscore is only a convention, a signal to other developers that this is not part of the public API, not something Python enforces.
bird = Bird('Pigeon', color="grey")
bird._max_distance
676
Another approach is to prefix the name with two underscores, which triggers name mangling: Python rewrites __max_distance internally to _Bird__max_distance, so the plain name no longer resolves from outside the class. The rest of the code works as intended, and this variable is usually called “private” — though, as you’ll see below, it’s renamed rather than truly hidden.
class Bird:
__max_fly_speed = 150 # km/hour
__max_distance = 676 # in kilometers
# Class constructor
def __init__(self, bird_type_name, color):
self.bird_type_name = bird_type_name
self.color = color
# Class method to get bird types
def bird_type(self):
return self.bird_type_name
# Class method to fly birds
def fly(self, velocity, distance):
if velocity > self.__max_fly_speed:
print(f"Sorry, our {self.bird_type_name} does not want to fly with the speed over {self.__max_fly_speed} (km/h).")
return False
if distance > self.__max_distance:
print(f"Sorry, our {self.bird_type_name} does not want to fly further than {self.__max_distance} (km). They get tired.")
return False
print(f"The {self.color} {self.bird_type_name} flies at the speed of {velocity} (km/h) to a distance of {distance} (km). Done!")
return True
bird = Bird('Pigeon', color="grey")
bird.__max_distance
AttributeError: 'Bird' object has no attribute '__max_distance'
Further, we can create getter and setter methods to define these hidden variables.
class Bird:
__max_fly_speed = 150 # km/hour
__max_distance = 676 # in kilometers
# Class constructor
def __init__(self, bird_type_name, color):
self.bird_type_name = bird_type_name
self.color = color
# __max_fly_speed setter
def set_max_fly_speed(self, max_fly_speed):
self.__max_fly_speed = max_fly_speed
# __max_fly_speed getter
def get_max_fly_speed(self):
return self.__max_fly_speed
# Class method to get bird types
def bird_type(self):
return self.bird_type_name
# Class method to fly birds
def fly(self, velocity, distance):
if velocity > self.__max_fly_speed:
print(f"Sorry, our {self.bird_type_name} does not want to fly with the speed over {self.__max_fly_speed} (km/h).")
return False
if distance > self.__max_distance:
print(f"Sorry, our {self.bird_type_name} does not want to fly further than {self.__max_distance} (km). They get tired.")
return False
print(f"The {self.color} {self.bird_type_name} flies at the speed of {velocity} (km/h) to a distance of {distance} (km). Done!")
return True
bird = Bird('Pigeon', color="grey")
bird.get_max_fly_speed()
150
Now we can change the private variable, pigeons’ maximum fly speed, and make a flying champion!
bird.set_max_fly_speed(200)
bird.get_max_fly_speed()
200
Wait, but where is my so-promised Pigeon class? Similarly, with the Penguin class, we inherit the functionality from the Bird class. I have created my pigeons’ default color to be white as a sign of Peace, but you can make your own choice!
class Pigeon(Bird):
bird_type_name = "Pigeon"
color = "white"
def __init__(self, color="white"):
self.color = color
super().__init__(bird_type_name=self.bird_type_name,
color=self.color)
white_pigeon = Pigeon()
white_pigeon.fly(velocity=55, distance=100)
The white Pigeon flies at the speed of 55 (km/h) to a distance of 100 (km). Done!
True
Conclusion: Python OOP Concepts Summary
Python classes are the foundation of object-oriented programming, and the four pillars demonstrated here work together: inheritance reuses a parent class, polymorphism overrides inherited methods, and encapsulation protects internal data through protected (_name) and private (__name) variables. A class is a blueprint that produces independent object instances, each with its own state.
Birds are free to fly, love their families, and do not care about the feather color of other birds. Many birds mate for life, raise their babies, and enjoy the sunshine on this beautiful Earth. I wish people would be more like birds, free, loving, tolerant of each other, and happy!
Openness and willingness to understand each other are essential in the World tormented by the ongoing history of suffering, which never ends due to greed and stupidity. I always focus my life on family, science, and coding. What is right and what is wrong? I think that we are all having the feeling of a better World. And, it will be my greatest joy to see a change for humanity, for the best.
We all deserve to be happy in this beautiful World, full of kind, intelligent, and gorgeous people. We are all part of Nature and the Universe, which moves so quickly, with all the stars born and dying as time moves. Nothing matters except humanity, freedom, love, Nature, and birds!
That’s all on birds and OOP in Python! I hope that this post was helpful for the familiarity of classes and what we can do with them in Python. In short, we made the “Bird” class that forms a “blueprint” describing particular bird objects. We made a few pigeons as instances of the “Bird” class and made them fly within considerable distances and speed limits. We also created a Penguin, inherited from the Bird class. We observed how class inheritance, polymorphism, and encapsulation work!
p.s. I want to thank my Twitter friend and an excellent ML coder Daniel Torac for his constructive comments on this article, particularly on protected and private variables considering the encapsulation in Python. Thank you very much for reading and for much inspiration!
References
- How Fast do Pigeons Fly?
- Python Classes — official Python tutorial
- Python Classes — A First Look at Classes (class definitions,
__init__) - Python Classes — Method Objects (the
selfparameter) - Python Classes — Random Remarks (
selfis a convention, not a keyword) - Python Classes — Inheritance
- Python Classes — Private Variables and name mangling
object.__init__— Python data modelobject.__repr__— Python data model (default object representation)super()— Python built-in functions
Did you like this post? Please let me know if you have any comments or suggestions.
Python posts that might be interesting for youEnjoyed this? Get more like it.
Weekly notes on AI tools, Python, and what I'm actually building — plus two free gifts: the 15-page Fantastic AI: The 2026 Toolkit and a Git Commands & Contribution Workflow Cheatsheet.