Python Tutorial

Objects And Classes:

This page can be downloaded as interactive jupyter notebook

An additional quiz about the contents of this tutorial, can be downloaded here (with solutions)

Object:

In python, an object is a collection of data (variables) and methods (functions) that act on those data. An object can be created by a class.

Class:

In python, a class provides a bundling data and functionality. It means, a class is a design for the object. A class has many descriptions and based on these descriptions, we can create several objects.

How to define a class in python

In python, we define a class using the keyword class. A class creates a new local namespace where all it’s attributes (data or functions) are defined.

Example: Here is an example of creating a simple class.

# Create a class

class galaxy:
    '''This is a docstring. This description is not mandatory'''
    pass

When we define a class, a new class object is created by the same name. We have access to the different attributes to instantiate new objects of that class through the class object.

class Myclass:
    "This is my first class"
    x = 100
    def Greeting(self):
        print('Hello')
        
print(Myclass.__doc__)

print(Myclass.x)

print(Myclass.Greeting)

How to create an object in Python

In python, we can use class object to create new object instances of the class. The procedure to create an object is like to create a function call.

Example: Here is example of creating an object by the class that we defined above.

# Define an object

obj = Myclass()

Here, we create a new instance named obj. We can access attributes (may be data or method) of objects by the object name prefix.

Example: Creating object.

class Myclass2:
    "This is my second class"
    x = 100
    def Greeting(self):
        print('Hello')
        
obj = Myclass2()

print(Myclass2.Greeting)

print(obj.Greeting)

# Calling function
obj.Greeting()
<function Myclass2.Greeting at 0x0000000005D928C8>
<bound method Myclass2.Greeting of <__main__.Myclass2 object at 0x0000000005D9B710>>
Hello

Notes:

  • When an object calls it’s method, the object is passed as the first argument. So, obj.Greeting() translate into Myclass2.Greeting(obj).
  • In general, calling a method with a list of arguments is equal to calling the corresponding function with an argument list that is created by inserting the method’s object before the first argument.
  • The first argument of the function in class must be the object itself. This is conventionally called self.

Example: Creating class and objects of it by having it’s attributes.

class galaxy:

    # class attribute
    typ = "planet"

    # instance attribute
    def __init__(self, name, radius):
        self.name = name
        self.radius = radius

# instantiate the galexy class
earth = galaxy("Earth", 6378)
mars = galaxy("Mars", 3389)

# access the class attributes
print("Earth is a {}".format(earth.__class__.typ))
print("Mars is also a {}".format(mars.__class__.typ))

# access the instance attributes
print("The radius of {} is {} km".format(earth.name, earth.radius))
print("The radius of {} is {} km".format(mars.name, mars.radius))
Earth is a planet
Mars is also a planet
The radius of Earth is 6378 km
The radius of Mars is 3389 km

Note: We can get the class name of a variable or an object using following syntax.

type(object).__name__

Example: Try to get the class name of the instances below.

# Creating objects and variables
A = []
B = 5.6
C = 'Hello'

# Get the class name


Object Oriented Programming

Python is an object oriented programming language. In procedure oriented programming, main stress is on functions, but in object oriented programming stress is on object. Object Oriented programming (OOP) is a popular approach to solve problems in programming using objects. Any object has two characteristics:

  • attributes
  • behavior

In previous example, Earth is an object,

  • name and radius are attributes
  • moving and rotation are behavior

Note: In python, creating reusable code and restraint repetition is the concept of OOP.

Methods

We use methods to define the behavior of an object. The functions which defined inside the body of a class are methods.

Example: Let’s run the code bellow to see the result of creating methods in python.

class galaxy:

    # instance attribute
    def __init__(self, name, radius):
        self.name = name
        self.radius = radius
        
    # instance method
    def move(self, year):
        return "{} moves {} per year".format(self.name, year)
    
    def rotate(self):
        return "{} rotates around itself".format(self.name)

# instantiate the object
earth = galaxy("Earth", 6378)

# access the class attributes
print(earth.move("'940 milion km'"))
print(earth.rotate())
Earth moves '940 milion km' per year
Earth rotates around itself

Note: Basic principles of OOP in python are in he following:

  • Inheritance: A process of using details from a new class without modifying existing class.
  • Encapsulation: Hiding the private details of a class from other objects.
  • Polymorphism: A concept of using common operation in different ways for different data input.

Inheritance

It is a way to create a new class using details of an existing class without modification. The existing class is called base class (or parent class) and the new class is called derived class (or child class). In the following sentences, you can see the Inheritance Syntax in python.

class BaseClass: Body of base class class DerivedClass(BaseClass): Body of derived class

Example: Let’s run the code below to see how inheritance works.

# Base class
class calculator:
    # instance attribute
    def __init__(self):
        print("Calculator is ready")
        
    def Add(self):
        print("Sum numbers")
        
    def Sub(self):
        print("Subtract numbers")
        
# Derived class
class Pro_cal(calculator):
    def __init__(self):
        # call super() function
        super().__init__()
        print("Professional calculator is ready")
        
    def Grad(self):
        print("Get gradient")
        
    def Sub(self):
        print("Subtraction of two numbers")
        
Compute = Pro_cal()
Compute.Grad()
Compute.Add()
Compute.Sub()
Calculator is ready
Professional calculator is ready
Get gradient
Sum numbers
Subtraction of two numbers
  • super(): This function pulls the content of __init__() method from the base class into the derived class.

In the above code, we created calculation (Base class) and Pro_cal (Derived class). We can see in Add method that the derived class inherits the functions of base class. And by the Sub method, we can see the derived class modified the behavior of base class. Also, we extend the function of base class, by creating a new method (Grad) in derived class.

Method Overriding

We can use __init__() method in both Base and Derived class. When it happens, the method in derived class overrides it in the base class.

It means, when we overriding a base method, we tend to extend the definition rather than replace it. It is the same by calling the method in base class from one of the methods in derived class. (Calling BaseClass.__init__() from __init__() method in DerivedClass.)

Note: There is possibility to use built-in functions, like super() which we explained it before. So, super().__init__() is equal to BaseClass.__init__(self).

Checking inheritances

Two built-in functions isinstance() and issubclass() are using to check inheritances.

  • isinstance(): It returns True if the object is an instance of the class or other classes derived from it.

  • issubclass(): It returns True if the first class (in inputs) is derived from the second class.

Example: Let’s run codes below to see, how works these built-in functions in python.

# Is the object earth created by class galaxy?
isinstance(earth,galaxy)
True
# Is the object obj created by class galaxy?
isinstance(obj,galaxy)
False
# Is the class calculator a BaseClass for class Pro_cal?
issubclass(Pro_cal,calculator)
True
# Is the class Pro_cal a BaseClass for class calculator?
issubclass(calculator,Pro_cal)
False

Exercise: Here is a class for polygons. Try to create a class for rectangles with function to calculate area of input rectangles as a derived class using polygon class as base class.

# Create Polygon class
class Polygon:
    # instance attribute of input polygon
    def __init__(self, No_of_sides):
        self.n = No_of_sides
        self.sides = [0 for i in range(No_of_sides)]
    # Methods to take length of sides
    def inputSides(self):
        self.sides = [float(input("Enter side "+str(i+1)+" : ")) for i in range(self.n)]
    def dispSides(self):
        for i in range(self.n):
            print("Side",i+1,"is",self.sides[i])
# Create Rectangle Class here with findArea function 



# Execute classes to check the result
# define an object
ob = Rectangle()
# Input rectangle sides
ob.inputSides()
# Calculate area of rectangle
ob.findArea()

Encapsulation

In python (using OOP), we can limit access to methods and variables. This prevent data from direct modification which is called encapsulation. We can denote private attribute using underscore as prefix, like single “_” or double “__”.

Example: Let’s see how data encapsulation in python works.

# Create a class
class ATM:
    # instance attribute
    def __init__(self):
        self.__maxcash = 500
        
    def payment(self):
        print("Max cash you can take: {}".format(self.__maxcash))
        
    def setmaxcash(self, cash):
        self.__maxcash = cash
        
# execute methods
a = ATM()
a.payment()

# change amount of cash
a.__maxcash = 1000
a.payment()

# using set function
a.setmaxcash(1000)
a.payment()
Max cash you can take: 500
Max cash you can take: 500
Max cash you can take: 1000

In the above code, at first we defined a class ATM. Then by __init__() method, we store the maximum amount of cash that the ATM can pay. We tried to change the maximum amount of cash. But it was impossible, because Python treats private attributes like __maxcash. We used setmaxcash() function to change the value.

Polymorphism

It is an ability (in OOP) to use common interface for multiple form (data types). For example, we want to color a shape and there are many shape options (circle, triangle, rectangle). However, we can use same method to color any shape. This concept is called Polymorphism.

Example: Let’s see how do we use Polymorphism in python.

class Airplane:

    def fly(self):
        print("Airplane is for flying")
    
    def sailing(self):
        print("Airplane is not for sailing")

class Ship:

    def fly(self):
        print("ship is not for flying")
    
    def sailing(self):
        print("Ship is for sailing")

# common interface
def sailing_test(vehicle):
    vehicle.sailing()

#instantiate objects
first = Airplane()
second = Ship()

# passing the object
sailing_test(first)
sailing_test(second)
Airplane is not for sailing
Ship is for sailing

In the above code, we defined Airplane and Ship classes. Both of them have common sailing() method, but their functions are different. We created common interface sailing_test() function which can take any object to allow polymorphism. Then, we passed the objects first and second in the sailing_test() function, it ran effectively.

Constructors

In python, when a class function begins with double underscore (__) is called special function. One of particular interest is the __init__() function. This type of function is also called constructors in Object Oriented Programming. We normally use it to initialize all the variables.

Example: Let’s see how does a constructor works.

class Circle:
    # Initialize the variables
    def __init__(self,p = (0,0), r = 0):
        self.position = p
        self.radius = r
    
    # function to display data
    def showData(self):
        print("The position of the circle is:{} and the radius is:{}".format(self.position,self.radius))
    
# Define new circles
circle1 = Circle((5,5),10)
circle2 = Circle((2,6),7)

# Call function to get data
circle1.showData()

# Define a new attributes on the fly
circle1.att = 'red'

# Modify attributes
circle1.position = (3,4)

# Get all attributes
print((circle1.position,circle1.radius,circle1.att))
The position of the circle is:(5, 5) and the radius is:10
((3, 4), 10, 'red')

How to delete attributes and objects

In python, we can use del statement to delete an object and any attributes of an object.

Example: Let’s run the code below to delete object and attributes.

# Delete an attribute
del circle1.radius
circle1.showData()
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-14-215960a67d7e> in <module>()
      1 # Delete an attribute
----> 2 del circle1.radius
      3 circle1.showData()


AttributeError: radius

When you run the code above, you will see this error message AttributeError: radius. It means, the object circle1 has not attributes radius.

# Delete an object
del circle2
circle2.showData()
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-15-7e179311efa9> in <module>()
      1 # Delete an object
      2 del circle2
----> 3 circle2.showData()


NameError: name 'circle2' is not defined

When you run the code above, you will see this error message NameError: name 'circle2' is not defined. It means, the program can not find the object circle2.

Multiple Inheritance

In Python, a class can be derived from more than one base class. This is called multiple inheritance.

In multiple inheritance, the derived class inherits the features of all base classes. In the following sentences, you can see the syntax of multiple inheritance.

class Base1: pass class Base2: pass class MultiDerived(Base1,Base2): pass

Multilevel Inheritance

Also, we can inherit from a derived class. This is called multilevel inheritance. It can be of any depth in python.

New class inherits features of the base class and the derived class. In the following sentences, you can see the syntax to create multilevel class.

class Base: pass class Derived1(Base): pass class Derived2(Derived1): pass

Method Resolution in Python

In python, every class is derived from the class object. It is the most base type in python. So, all other classes (built-in or user-defined) are derived classes and all objects are instances of object class.

Example: Let’s run the code below to check this relation between class object and other classes and objects.

# Check a class
print(issubclass(tuple,object))

# Check an object
print(isinstance(6.4,object))

# Check an object
print(isinstance("Hello",object))
True
True
True

Operator Overloading

Generally, operators work for built-in classes in python. An operator behaves differently with different application types. For example, the + operator will perform arithmetic addition on two numbers, merge two lists and concatenate two strings.

This feature that allows an operator to have different meaning according to the context is called operator overloading.

Example: Let’s see, what happens when we use operators with objects of a user-defined class. The following class tries to stimulate a point in 2D coordinate system.

# Create the class
class point_2D:
    def __init__(self, x = 0, y = 0):
        self.x = x
        self.y = y
        
# Create object - define points with coordinates
p1 = point_2D(5,5)
p2 = point_2D(-10,10)
p1 + p2
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-1-4988d77ab390> in <module>()
      8 p1 = point_2D(5,5)
      9 p2 = point_2D(-10,10)
---> 10 p1 + p2


TypeError: unsupported operand type(s) for +: 'point_2D' and 'point_2D'

When you run the code above, this type of error unsupported operand type(s) for +: 'point_2D' and 'point_2D' will appears. It says, Python did not know how to add two point objects (Created by class point_2D) together. But do not worry, because we can teach this to python through operator overloading.

Special Functions

In python, class functions that begin with double score __ are called special function. For example, __init__() function that we defined it above. We need to call it, when we are creating a new object of it. There are a ton of special functions in python.

Let’s see, what will happen when we want to print an object from last example.

# Print an object (point with 2D coordinates)
print(p1)
<__main__.point_2D object at 0x0000000005EA1F28>

It did not print it well. Then we can define __str__() method in our class to control how to print the object.

# Create the class
class point_2D:
    def __init__(self, x = 0, y = 0):
        self.x = x
        self.y = y
        
    # define print method
    def __str__(self):
        return "({0},{1})".format(self.x,self.y)
    

p1 = point_2D(5,5)
p2 = point_2D(-10,10)

# Now print the object
print(p1)
str(p1)
(5,5)





'(5,5)'

So, when you execute str(p1) or print(p1), python is internally doing p1.__str__().

Overloading the + Operator

In python, we need to implement __add__() function in the class to overload the + sign. We can do whatever we like, inside this function. But, does it make sense to return a point object of the coordinate sum.

Example: Overloading the + sign.

# Create the class
class point_2D:
    def __init__(self, x = 0, y = 0):
        self.x = x
        self.y = y
        
    # define print method
    def __str__(self):
        return "({0},{1})".format(self.x,self.y)

    # Overloading operator
    def __add__(self,other):
        x = self.x + other.x
        y = self.y + other.y
        return point_2D(x,y)
    
# Create objects
p1 = point_2D(5,5)
p2 = point_2D(-10,10)

# Try to add them together
print(p1 + p2)
(-5,15)
No. Operator Expression Internally
1 Addition p1 + p2 p1.__add__(p2)
2 Subtraction p1 - p2 p1.__sub__(p2)
3 Multiplication p1 * p2 p1.__mul__(p2)
4 Power p1 ** p2 p1.__pow__(p2)
5 Division p1 / p2 p1.__truediv__(p2)
6 Floor Division p1 // p2 p1.__floordiv__(p2)
7 Remainder (modulo) p1 % p2 p1.__mod__(p2)
8 Bitwise Left Shift p1 << p2 p1.__lshift__(p2)
9 Bitwise Right Shift p1 >> p2 p1.__rshift__(p2)
10 Bitwise AND p1 & p2 p1.__and__(p2)
11 Bitwise OR p1 I p2 p1.__or__(p2)
12 Bitwise XOR p1 ^ p2 p1.__xor__(p2)
13 Bitwise NOT ~p1 p1.__invert__()
14 Less than p1 < p2 p1.__lt__(p2)
15 Less than or equal to p1 <= p2 p1.__le__(p2)
16 Equal to p1 == p2 p1.__eq__(p2)
17 Not equal to p1 != p2 p1.__ne__(p2)
18 Greater than p1 > p2 p1.__gt__(p2)
19 Greater than or equal to p1 >= p2 p1.__ge__(p2)

Author: Mohsen Soleymanighezelgechi
Last modified: 30.07.2019