Classes and Objects

Conceptually, objects are like the components of a system. Think of a program as a factory assembly line of sorts. At each step of the assembly line a system component processes some material, ultimately transforming raw material into a finished product.

An object contains data, like the raw or preprocessed materials at each step on an assembly line, and behavior, like the action each assembly line component performs.

Paradigm of Object-Oriented Programming

Object-oriented programming is a programming paradigm that provides a means of structuring programs so that properties and behaviors are bundled together into individual objects.

For instance, an object could represent a person with properties like a name, age, and address and behaviors such as walking, talking, running. Or it could represent an email with properties like a recipient list, subject, and body and behaviors like adding attachments and sending.

Put another way, object-oriented programming is an approach for modeling concrete, real-world things, like cars, as well as relations between things, like companies and employees, students and teachers, and so on. OOP models real-world entities as software objects that have some data associated with them and can perform certain functions.

Object-Oriented Programming Concepts

So far, we can discuss the major concepts within the OOP paradigm. And they are:

encapsulation:

In OOP refers to the bundling of data with methods that operate that data, or restricting of direct access to some of an object’s components.

Note

Encapsulation mechanism is often confused with hiding. It’s not actually that encapsulation does, but data hiding is available to us due to the encapsulation.

inheritance:

It’s a mechanism of basing an object or a class upon another object (prototype-based inheritance) or class (class-based inheritance), retaining similar implementation. Also defined as deriving new classes (subclasses) from existing ones such as a super class or base class and forming them into a hierarchy of classes.

polymorphism:

It’s a provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. The concept is borrowed from a principle in biology where an organism or species can have many different forms or stages.

abstraction:

The process of removing or generalizing details or attributes in the study of objects or systems to focus attention on details of greater importance, it is similar in nature to the process of generalization. The creation of abstract concept-objects by mirroring common features or attributes of various non-abstract objects or systems of study is the result of the process of abstraction.

Define a Class in Python

Primitive data-structures - like numbers, strings, lists etc. - are designed to represent simple pieces of information, such as the cost of a product, the name of a novel, or someone’s favorite colors. What if you want to represent things that are more complex?

For example, let’s say you want to track employees in an organization. You need to store some basic information about each employee. Let’s start from a very beginning and try to represent an individual employee as a bunch of variables:

first_name = "Serhii"
last_name = "Horodilov"
job_title = "Software Engineer"

This approach has number of issues. Once it’s required to store information for more that one person, you are to create another set of variables: first_name_1, first_name_2 etc. The most terrifying issue is that these portions of data have no relations to each other. Let’s try to use list for this purpose:

serhii = ["Serhii", "Horodilov", "Software Engineer"]
vlad = ["Vladyslav", "Ponomaryov", "Release Manager"]

There are number of issues with this approach as well.

First, it can make larger code files more difficult to manage. If you reference serhii[0] several lines away from where serhii list is declared, will you remember that the element with index 0 is the person’s name? Of course, you can use dict structure, but…

Second, it can introduce errors if not every person has the same number of properties.

A great way to make this type of code more manageable and more maintainable is to use classes.

All classes definitions in Python start with the keyword class, which is followed by the name of the class and a colon. Any code that is indented below the class definition is considered the part of the class’s body.

class Person:
    """Person class implementation"""

Classes vs Instances

Classes are used to create user-defined data structures. As it was mentioned above OOP is about bundling data and behaviors. Classes define data structures; each portion of data bundled within a classes is called property or field. Classes also define functions called methods, which identify the behavior and actions that an object created from the class can perform with its data.

class Person:
    """Person class implementation"""

    first_name: str = ""
    last_name: str = ""
    job_title: str = ""

A class is a blueprint for how something should be defined. It doesn’t actually contain any data. The person class above specifies that first_name and last_name properties are bundled within this class, but it don’t actually contain the person’s name.

While classes are blueprints, an instance is an object that is built from a form has been filled out with information. Just like many people can fill out the same form with their own unique information, many instances can be created from a single class.

serhii = Person()
serhii.first_name = "Serhii"
serhii.last_name = "Horodilov"
serhii.job_title = "Software Engineer"

vlad = Person()
vlad.first_name = "Vladyslav"
vlad.last_name = "Ponomaryov"
vlad.job_title = "Release Manager"

Methods

A function bundled within a class is called method. There are several ways to define a class method. For now it’s needed to know, that each method will get a special argument at the first position. This argument is a reference to an actual object. By convention, this argument is called self.

class Person:
    """Person class implementation"""

    first_name: str = ""
    last_name: str = ""
    job_title: str = ""

    def get_fullname(self) -> str:
        """Return a person's fullname"""

        return " ".join([self.first_name, self.last_name])

Initializing Instance with Data

There are several methods surrounded with double underscores (__method__) that are called dunder methods or magic methods. We’ll take a closer look at these methods in the future. For now, it’s ok to just one of these special methods: __init__. It initializes an instance with some specific data.

class Person:
    """Person class implementation"""

    programming_language: str = "Python"

    def __init__(
        self, first_name: str, last_name: str, job_title: str
    ) -> None:
        """Initialize a person instance"""

        self.first_name = first_name
        self.last_name = last_name
        self.job_title = job_title

    def get_fullname(self) -> str:
        """Return a person's fullname"""

        return " ".join([self.first_name, self.last_name])


serhii = Person("Serhii", "Horodilov", "Software Engineer")
vlad = Person("Vladyslav", "Ponomaryov", "Release Manager")

serhii.first_name  # Serhii
serhii.last_name   # Horodilov

Note, this call definition has a property called programming_language defined outside of the __init__ method. This property is shared across all the class instances.

Some More Details on self

self is nothing except the convention. Instance methods will receive a pointer to the instance itself as the first argument. In two words: it is the actual object to call the method with. For example, the student class defines attributes (student’s name and scores) and methods available for each student instance: complete the challenge or skip classes. While the actual student instance contains data and methods related to the exact one student. The self is a referer to this exact object.

Data Hiding

Many programming languages has access modifiers implemented. The Python has also.

../_images/wy_ban.jpg

“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python [docc]. It’s implemented as a convention-level.

A name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member). These should not be used outside the class itself and can be changed without notice.

Since there is a valid use case for class-private members (namely to avoid name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called name mangling. Any identifier with at least two leading underscore (e.g. __spam) is textually replaced with _classname__spam, where _classname is the current class name with leading underscore stripped.

class Employee:
    """Employee superclass example"""

    first_name: str = ""
    last_name: str = ""

    _rate: int = 0
    __tax: float = 0.18

    def _get_amount(self, hours: int) -> int:
        return self._rate * hours

    def _get_tax(self, amount: int) -> int:
        return int(round(self.__tax * amount, 0))

    def get_balance(self, hours: int) -> int:
        amount = self._get_amount(hours)
        tax = self._get_tax(amount)

        return amount - tax

    def set_rate(self, rate: int) -> None:
        self._rate = rate


class Employee10PercentTax(Employee):
    _Employee__tax = 0.10

Few Words about Inheritance

Note

Just in two words. This topic is discovered in the future articles.

You can derive your classes from a super class. Derived classes are called sub classes and the class used to inherit from is called super class. Other terms are child class and parent class, but they are not common (this is author’s personal opinion).

Just put a super class in parenthesis two inherit from it:

class Dog():
    """Abstract dog implementation"""


class JackRussellTerrier(Dog):
    """Jack russell terrier species implementation"""

Few Words about Polymorphism

You’ve already use this. The most simple explanation is addition operator. For different types of data it would produce different types of output:

["a", "b", "c"] + ["d"]  # the result is ["a", "b", "c", "d"]
"a" + "b" + "cd"         # the result is "abcd"

For example you may have various classes inherited from a base class, that provides a common interface, but each derived class may implement the method in its own way.

class Animal:
    """Abstract animal"""

    def voice(self):
        pass


class Cat(Animal):
    def voice(self):
        print("MEOW")


class Dog(Animal):
    def voice(self):
        print("ARF)