Skip to content

Object programming

Object oriented programming

Object-oriented Programming, or OOP for short, is a programming paradigm, which provides a means of structuring programs based on the properties and behaviuor of individual objects.

People always prefer to use something what is known and familiar, that's why the high level concept was created - on objects, that represent real things (animal, human, car) instead of 0 and 1 (which are familiar for computer).

For instance, an object could represent a person with a name property, name, age, address, etc., with behaviours like walking, talking, breathing, and running. Or an email with properties like recipient list, subject, body, etc., and behaviours like adding attachments and sending.

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

Another common programming paradigm is procedural programming which structures a program like a recipe in that it provides a set of steps, in the form of functions and code blocks (if, loop, etc.), which flow sequentially in order to complete a task.

In object oriented programming data (objects) and operation on them are at center, instead of functions and algorithms.

Objects are represented by attributes (as variable) and methods (functions defined for object and called on object), which can read and modify an object attribute.

Class and object

So far we've got to know data types like numbers, string, list, which are designed to represent simple things: the variable with the data type int or float can represent some product price or some person height; value str allow to store the name of favou rite animal or household address; list are used to store last lottery draw results.

But what if we want to present something more complex? In such a case primitive types might be not enough.

For example we want to have a student's diary. If we would use a list, first element could be the name of student, the second his surname. In this case we would create variable, which number of elements is twice the number of students.

    journal = ['Ann', 'Scot', 'John', 'Smith', 'Mat', 'Simson']

    student_1 = journal[0] + ' ' + journal[1]
    student_2 = journal[2] + ' ' + journal[3]
    student_3 = journal[4] + ' ' + journal[5]

    print(len(journal))  # Return 6

Such a structure will not cause the issue at the beginning. However the management of such a list will not be efficient, if they get bigger (class with 40 students? why not?). What, if during work on the program, we will decide, that we need to add date of birth, grades, height or weight? We will get to no organized data.

Classes are used to create new user-defined data structures. In case of student it would be recommended to create class Student(), to track his attributes (properties) like name, surname, age, class, favourite subjects, grades, height, weight and actions (methods), that specific student can do: learning, go to break, write tests, go to school shop etc.

It’s important to note, that a class just provides structure—it’s a blueprint for how something should be defined. Class is a recipe how to create some representation, something that has all attributes defined in class. This is an object.

While the class is the blueprint, an instance is a copy of the class with actual values, literally an object belonging to a specific class. It’s not an idea anymore; it’s an actual student, from class 3a, his favourite lessons are gym and his name is John.

Summary:

Class is the blueprint, where object is defined, it describes values, methods and default state.

Object is instance of a class, It has values and methods, which are defined in class from which it comes. You can infinity the number of objects from the class in program.

Cars

Class definition

  • Class is created by using a key word class.
    # Simplest, nothing doing class
    class Student:
        pass
  • Class (with attributes and methods) create a code block, so we should use cut ins.
  • __init__(self) it's a specific method (function), used every time, when we create the object for common class; method __init__ is called an initializer.
    class Student:
        def __init__(self):
            pass
  • Parameter self is required as a first parameter in methods, it makes method relate to object, on which they are used.

    class Student:
        def __init__(self):
            self.name = "John"
            self.grade = 4
For such a defined class, every new object will have two variables: the name and the grade and for each instance they will have following values: 'John' and 4.

Below, we have example of Animal class, which will be used to create object, that represents animals. Class contains:

  • class variable : SPECIES - as it is "class", values of this variable is set in class, so it's shared with all objects (every object uses the same value, changing its value in one object causes its modification in another).
  • initializer __init__ it contains operations, that are done, when object is created - here, object variables are set, each instance has their own copy of variables and changing its value in one object causes its modification in another,
  • function definition print_details(self), as it was defined in the class we call it method.
    class Animal:
        SPECIES = ""  # class variable

        def __init__(self):
            self.name = "Johnny"  # default value for variable name from class Animal
            self.age = 2

        def print_details(self):  # method, that displays object state
            print(f"Name: {self.name}, age: {self.age}.")

Instantiating Objects

  • Object can be created after class is defined. It's similar to how we get the result from function to variable.
    snoopy = Animal()
  • Instantiating object execute method __init__ from class. In a simple way we can say, that class call is using function that's defined in class __init__(self).
  • To get variable value or execute method on object we need to use dot operated ..
    class Animal:
        ...

    my_dog = Animal()  # here we create object, __init__ initializer called
    my_dog.print_details()  # method print_details() executed on object my_dog
    print(my_dog.name) # get to variable name from object my_dog
    my_dog.age = 3  # age variable update in my_dog object

Every object has its state. When we change variable of one object, it not change others. The following example is demonstrated an object independence:

    class Animal:
        ...

    puppy = Animal()
    dog = Animal()  # create second object of Animal class
    puppy.age = 1
    puppy.name = "Rex Junior"
    dog.age = 10
    dog.name = "Rex Senior"

    print(f"My dog: {puppy.name}, {puppy.age} and older dog: {dog.name}, {dog.age}")

Initializer __init__

Instead of set default values for variables in object, we can set them in the initializer, when object is created.

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.name = name
            self.age = age

    my_cat = Animal("Snoopy", 5)
    my_parrot = Animal("Ara")  # age set to 2 by default
    my_turtle = Animal()  # Default name and age value

All class arguments used in create object function, are forwarded to the initializer. The instance of this object will set variables based on __init__ method.

Basic magic methods

The methods that magic in Python are the ones whose names begin and end with a double floor, i.e. "__". In general, the __init__ constructor mentioned above is one such method. The purpose of the existence of magic methods is to make our classes able to smoothly enter the Python ecosystem, i.e. be comparable, sortable or, for example, be able to cope if we pass them to functions such as len or str.

__str__ and __repr__

If we execute the following code fragment:

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.name = name
            self.age = age


    print(Animal())

then we will get a result similar to

<__main__.Animal object at 0x7f885ead6690>

However, if we add the method __str__ to our class:

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.name = name
            self.age = age

        def __str__(self):
            return f"<{self.name} ({self.age})>"


    print(Animal())
Then we will get:
<Rex (2)>

The handling of __repr__ functions is similar - the main difference between them is the purpose. __str__ is used when creating the human representation of an object and __repr__ is used when creating an object representation for the computer.

Compare

Set of comparison operators and their corresponding magic methods:

<   and corresponding method:__lt__  
<=  and corresponding method:__le__  
==  and corresponding method:__eq__  
!=  and corresponding method:__ne__  
>=  and corresponding method:__ge__  
>   and corresponding method:__gt__  

And example:

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.name = name
            self.age = age

        def __gt__(self, other):
            return self.age > other.age


    print(Animal("Alex", 3) > Animal("Milk", 2))

in which, by defining the __gt__ method, we can compare animals by age. The result of this code is True.

Length

So method __len__. Defining it allows us to use our classes as arguments to the len function:

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.name = name
            self.age = age

        def __len__(self):
            return self.age


    print(len(Animal("Alex", 3)))

In this example, we have defined that we specify the length of our object as the value of the age field. The result of executing the code is 3.

Mathematical operators and casting to other types

We can make it possible to perform mathematical operations on instances of our classes. Both add between each other and between different types. Methods such as:

__add__ operator: +
__sub__ operator: -
__mul__ operator: *
__floordiv__ operator: //
__truediv__ operator: /
__mod__ operator: %
__pow__ operator: **
__lshift__ operator: <<
__rshift__ operator: >>
__and__ operator: &
__xor__ operator: ^
__or__ operator: |

We can also teach our classes to be projected to eg an int. The following methods are used for this:

int()   __int__
long()  __long__
float() __float__
oct()   __oct__
hex()   __hex__

An example could look like this:

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.name = name
            self.age = age

        def __add__(self, other):
            return Animal(self.name, self.age + int(other))

        def __str__(self):
            return f"<{self.name} ({self.age})>"

        def __int__(self):
            return self.age


    print(Animal("Alex", 1) + 1)

    print(Animal("Alex", 3) + Animal("Milk", 2))

where we defined that if we add something to the Animal class, the result will be a new class but its "age" field will be increased accordingly. The result of this code is:

<Alex (2)>
<Alex (5)>

There are at least a dozen more magical methods, but they all have the same goal - to make our classes take full advantage of the possibilities of the Python language. This is a really powerful tool.

Private and public variables

In Animal class we could get to every variable and update it - also everyone could do this. It's not always recommended (what, if someone set age to -10 by mistake?).

For more control on objects, Python allow to define variables as private and public, which limit access to them.

Usage of _ in variable name suggests other programmers, that variable can be updated only by method of this object. It's only suggestion - change of variable value still can be updated without methods of this class. Such a variable is called protected.

    class Animal:
        def __init__(self, name="Rex", age=2):
            self._name = name
            self._age = age

    my_dog = Animal()
    print(my_dog._name)  # it works, will print value of name variable, from my_dog object
  • To set real value and variable privacy, you need to add __ to object variable name. In this case variable value set will not be possible without class methods usage - otherwise, Python will return error AttributeError (it's treated as such attribute not exist, it's hidden). Such variables are called private.
    class Animal:
        def __init__(self, name="Rex", age=2):
            self.__name = name
            self.__age = age

    my_dog = Animal()
    print(my_dog.__name)  # Python return error!!!

How to set privet variable value:

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.__name = name
            self.__age = age

        def set_age(self, age):
            if age > 0:
                self.__age = age
            else:
                print("Age must be grater than 0.")

        def get_age(self):
            return self.__age

    my_dog = Animal()
    my_dog.set_age(3)
    print(my_dog.get_age())  # Will show updated __age variable value from my_dog object

Properties

In above example we prevent user to cheat us and set negative number for age, we use separated method to show attribute and other to set it.

Instead of methods like set_age or get_age, we can use so called property (specific attribute), that help encapsulate variables in more python way. It's more python way for better and safer attribute management without additional methods definition.

  • property it might have methods getter, setter and deleter.
  • The name of method, that use a property mechanism for operations get, set and delete, needs to be the same!
    class Animal:
        def __init__(self, name, age):
            self.__name = name
            self.__age = age

        @property  # getter - get variable value
        def age(self):
            return self.=__age

        @age.setter  # setter - set variable new value
        def age(self, age):
            if age > 0:
                self.=__age = age
            else:
                print("Age must be greater than 0.")
        @age.deleter  # deleter - remove variable
        def age(self):
            del self.=__age

    my_dog = Animal()
    my_dog.age = 3  # Set age - using setter
    print(my_dog.age)  # read age - using getter
    del my_dog.age  # Remove variable - using deleter
  • property is defined by name, that used to access hidden attribute.
  • Next create method with chosen name from previous step and add command @property, encapsulate method causing we have a specific attribute.
  • Command @property in method definition allows to set getter, method that will be used every time, when someone will read attribute value.
  • Command @<method_name_from_property.setter> allows to set action in case of attribute update - here we can see several checks - is value have valid type or is from expected range.
  • Method with @<method_name_from_property.deleter> is that will call when attribute deleted (i.e. command del obiekt.attribute_name).
  • Command @property uses decorate mechanism in Python. More in next part of this course.

Value and reference

When we assign an object to a variable, we create the relation called reference. If many variables are related to the same object, we can modify it using any of them. We can object state using one variable, and it will be propagated in other variables, that set to this object. This way we can use objects as function arguments, other objects methods or move between files and still we will use one same instance, with it values and same methods.

Reference

    class Animal:
        def __init__(self, name="Rex", age=2):
            self.__name = name
            self.__age = age

    dog_a = Animal()
    dog_b = dog_a
    print(my_dog_a.name)  # Return "Rex"
    print(dog_b.name)  # Return "Rex"

    dog_a.name = "Pongo"
    print(my_dog_a.name)  # Return "Pongo"
    print(dog_b.name)  # Return "Pongo"

Object-oriented programming paradigms

There are four basis rules in object-oriented programming, that define root of this way of programming. There are:

  • Abstraction
  • Encapsulation
  • Inheritance
  • Polymorphism

Abstraction

Abstraction means concept or idea, where it's not related with specific instance. It's difficult to imagine an animal or a fruit - always we will think about some specific, existed object: a dog, a cat, a mouse or a banana. In this case we don't want to get specific animal or fruit, but only the meaning, that abstract concept of an animal or a fruit is not imaginable.

In the same way in object-oriented programming - in this concept the class is often defined, instead of implementation itself. This way we create base, that features can be extended.

Encapsulation

Encapsulation is a mechanism to hide data implementation by limit only to public methods. Instance variables are defined as private and access methods are public and they are used to update private variable values. This is called encapsulation.

Python is specific language, because it offers huge freedom - without explicit property mechanism use or private variables feature use, default variable values - variables and methods are available from each module or other class object etc.

Inheritance

Inheritance allows child class to use methods and attributes from base (parent) class. Child class automatically gets access to the same variables and methods, that's in parent class. We don't need to define those variables and methods again, this way we safe time. Also, when we want to create class, that is similar to other, we don't need to re-write or copy the same code - we just need to inherit of older class.

Inheritance expresses relation is. For example dog is an animal, banana is a fruit. Based on this relation, class Banana looks like perfect candidate to get class Fruit functionality.

Inheritance is defined by parent class name in brackets, right after child class name.

    # Parent class
    class Vehicle:
        pass

    # Child class
    class Car(Vehicle):
        pass

Here is more real inheritance example:

    class Human:
        def __init__(self, name, height, weigth):
            self.name = name
            self.height = height
            self.weigth = weigth

    class Programist(Human):
        def __init__(self, name, height, weigth, languages):
            super().__init__(name, height, weigth)
            self.languages = languages

    bob = Programist("Bob", 180, 100, ["Python", "Java"])
    print(bob.name)  # Access to inherited Human class name attribute

Programist is a human, so such a relation is make sense and objects from Programist class can inherit Human class attributes.

In specific cases we might need to implement class, that inherits from more than one parent class.

    class Bat:
        pass

    class Man:
        pass

    class Batman(Bat, Man):
        pass

In this case Batman class will have access to all attributes and methods from Man and Bat classes.

Polymorphism

Polymorphism is "various", means one name/object/method and many ways of usage. We get by overwrite methods. Child class inherits methods for its parent, can re-define them and use in itself way. Polymorphism is strongly related to inheritance. We can crate code, that will work in parent class (over type) and it will work in every child class (under type), because both child and parent class, will have access to the same methods.

    class A:
        def __init__(self):
            pass
        def print_value(self):
            print("some value")

    class B(A):
        def __init__(self):
            pass
        def print_value(self):
            print("other value")

    values = [A(), B()]

    for value in values:
        value.print_value()  # First will display 'some value', and next 'other value'

List values contains two objects: first A type, second B type (in this case objects are not assigned to variables). Thanks to inheritance and that both objects have access to print_value method, for loop don't need to check value variable data type. Only important thing is that both elements provide print_value method.