Elena' s AI Blog

Python classes and pigeons

01 Sep 2022 / 22 minutes to read

Elena Daehnhardt


Three white doves, Jasper AI-generated art, January 2023


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

In CS, there are few programming paradigms widely known and discussed. I am not keen to go into discussions, and Writing code is the best way to understand programming paradigms and their meaning. In this post, I am going to focus on Object-Oriented Programming. As we see from the name, we are dealing with objects. And we can describe any real-life or abstract object with the help of 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!

Classes in Python

In Python, we define a class with the respective word. A class is initialised as follows. Our simple bird class has just two class attributes such as bird_type_name and the birds’ color. The init method is a special method to construct (or create) birds.

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 instance at 0x10b268e18>, <__main__.Bird instance at 0x10b2fa440>, <__main__.Bird instance at 0x10b2fa518>]

All three bird instances are located in different places in the computer memory and are unique objects.

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.

As 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

Class methods are “functions” that our objects can do. Most of the 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 the “self” keyword. It helps in accessing the class methods and defined class-wide attributes. For instance, the bird_type() method returns 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
    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
    The white Pigeon flies at the speed of 55 (km/h) to a distance of 100 (km). Done!
True

Inheritance

The 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. Indeed, we can hard-encode all these differences inside of the fly() method, and with the check of the Bird class attribute ‘bird_type_name’, we can alter the functionality. Luckily, there is 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 our “init” constructor method. Bu calling the parent class’ method “init” with super(). and the defined attributes “bird_type_name” and “color.” Otherwise, the Python interpreter will complain and demand that we define the bird type and color when creating a penguin instance of the class. Now, we can create a penguin object.

new_bird = Penguin()

The “color” attribute is not defined in the class “Penguin,” yet, it is available since it is inherited from the class “Bird.” We can also change it anytime a penguin age 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

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()
    The grey 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

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 preceed the variable names with an underscore, as pythonists say “creating a protected variable”.

    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

Unfortunately, we can still easily access the content of variables (_max_distance). This is a mere convention for coders that this variable is protected and should not be used outside the class.

    bird = Bird('Pigeon', color="grey")
    bird._max_distance
    676

Another way is to use two underscores, which really works to hide the variable. And the rest of the code works as intended. This variable can be referred as “private”.

    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 Pinguin 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()

Conclusion

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?

Did you like this post? Please let me know if you have any comments or suggestions.

Python posts that might be interesting for you



desktop bg dark

About Elena

Elena, a PhD in Computer Science, simplifies AI concepts and helps you use machine learning.

Citation
Elena Daehnhardt. (2022) 'Python classes and pigeons', daehnhardt.com, 01 September 2022. Available at: https://daehnhardt.com/blog/2022/09/01/coding-python-classes-oop-polymorphism-encapsulation-inheritance/
All Posts