Classes and inheritance in Python 3

From Wikipedia: In object-oriented programming, a class is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods).

Features of classes in Python:

  • Multiple inheritance permitted
  • Derived class can override base class methods
  • Base class methods can be called using super().method_name()
  • Class members are dynamic, can be modified at runtime
  • Class members are public, methods are virtual (can be overridden)
  • The first parameter in a method is a reference to the calling object, usually self
  • Operator overloading is supported
  • Aliasing of objects is supported

Classes are defined as follows:

class ClassName:
    """Docstring"""
    statement_1
    .
    .
    .
    statement_n

#create an instance of the class
var = ClassName()

Class variables are common to all instances and are often used to represent immutable data. An example is the details variable in the below example.

An instance variable is unique to every instance of the class ie object. Examples are x and y variables in the following class.

To initialize an object, the __init__(self) method is used. It’s like a constructor, thinking in terms of Java.

To specify how the contents of an object should be printed, the __str__(self) method is used. When an object is printed using print(object_variable), the __str__(self) method is called.

To format strings the string.format() will be used. The official docs.

Difference between a function and a method: a function is a named grouping of statements while a method is a function that is associated with a specific class.

To define a method, it has to be defined inside a class and is called using object.method().

Functions are of two types: pure and modifiers. Pure functions don’t modify the data it receives and is the model followed in functional programming. Modifier functions, as the name suggests, modify the data it receives. The cool thing about Python is that it allows different programming paradigms such as functional programming and object oriented programming, and you are free to use whichever programming paradigm suits you.

Operator overloading is possible in Python by overriding certain methods. Eg- override __add__() to allow using the + operator on instances of the class. To learn more refer here.

Objects can be copied using copy.copy(). A new object is created with the same contents as the original object.

All these concepts are demonstrated in the following example.

Point class example:

import copy

class Point:
    """A class to represent a point"""

    details = "Represent points"

    def __init__(self,x=0,y=0):
        self.x=x
        self.y=y
    
    def __str__(self):
        return "x={} y={}".format(self.x,self.y)

    def get_sum(self):
        """Return sum of x and y components"""
        return self.x + self.y
    
    def add_point(self,to_add):
        """Update self by add x and y component of new_point"""
        self.x += to_add.x
        self.y += to_add.y

    def __add__(self,new_point):
        self.x += new_point.x
        self.y += new_point.y
        return self

def print_point(point):
    """print contents of given point"""
    print("x={} y={}".format(point.x,point.y))

point = Point(10,20)
print(point)

print(point.details)
print(point.get_sum())

new_point = Point(2,4)
print(new_point)

point.add_point(new_point)
print(point)

print_point(new_point)

copied_point = copy.copy(point)
copied_point.x = 5
copied_point.y = 5
print(point)
print(copied_point)

print(copied_point + new_point)
#output
x=10 y=20
Represent points
30
x=2 y=4
x=12 y=24
x=2 y=4
x=12 y=24
x=5 y=5
x=7 y=9

A note about namespaces and scopes: a namespace is a mapping of names to objects. Eg- global namespace, module namespace, function namespace. A scope is a textual region where the namespace is accessible.

During execution, there are at least three nested scopes whose namespaces are directly accessible:

  • Innermost scope, an if block or a loop
  • Enclosing function scope
  • Module scope
  • Global scope

Variables can be declared as nonlocal or global. If they are not specified, he defualt is the current scope (need to check). A nonlocal statement is used to indicate a variable has to be rebound in the enclosing scope. A global statement indicates that a variable has to be rebound in the global scope. Note that this is applicable only for rebinding variables, not for modifying mutable data types. To learn more refer here.

Inheritance is supported. Syntax is as follows:

class BaseClass:
    """Docstring"""
    statement_1
    .
    .
    .
    statement_n

class DerivedClass(BaseClass):
    """Docstring"""
    statement_1
    .
    .
    .
    statement_n

Multiple inheritance is supprted using class Derived(Base1,Base2):.

It should be noted that multiple inheritance can introduce a situation where multiple base classes inherit from the same base class. To learn more about how method resolution works refer here.

A simple example of inheritance:

class Vehicle(object):
    """Represents a vehicle"""

    def __init__(self,engine_power=100):
        self.engine_power = engine_power
    
    def __str__(self):
        return "Engine power : {}HP".format(self.engine_power)
    
    def print_name(self):
        print("From vehicle")
    
class Car(Vehicle):
    """Represents a car"""

    def __init__(self,wheels=4):
        super().__init__()
        self.wheels = wheels
    
    def __str__(self):
        return "Engine power : {}HP , Wheels = {}".format(self.engine_power,self.wheels)
    
    def print_name(self):
        print("From vehicle")

car = Car()
print(car)
car.print_name()
#output
Engine power : 100HP , Wheels = 4
From vehicle

Source code for today’s plog is here.

References: