7 minute read

Basic OOP in Python

How to use global keyword

We only need to use the global keyword in a function if we want to do assignments or change the global variable

a = 1

# Uses global because there is no local 'a'
def f():
	print('Inside f() : ', a)

# Variable 'a' is redefined as a local
def g():
	a = 2
	print('Inside g() : ', a)

# Uses global keyword to modify global 'a'
def h():
	global a
	a = 3
	print('Inside h() : ', a)

# Global scope
print('global : ', a)
f()
print('global : ', a)
g()
print('global : ', a)
h()
print('global : ', a)

global :  1
Inside f() :  1
global :  1
Inside g() :  2
global :  1
Inside h() :  3
global :  3

What is name == ‘main’ in Python?

There is no public static void main(String[] args) or any main method in Python. When the command to run a Python is given to the interpreter, the code that is at level 0 indentation is executed. Before the execution, Python will define the global variables and also a few special variables such as name.

If the source file is executed as the main program, the interpreter sets the built-in variable name to have a value “main”.

# File1.py
print("File1 __name__ = %s" %__name__)

if __name__ == "__main__":
    print("File 1 is being run directly")
else:
    print("File 1 is being imported")
    
# File2.py
import File1

print("File2 __name__ = %s" %__name__)

if __name__ == "__main__":
    print("File 2 is being run directly")
else:
    print("File 2 is being imported")
Now the interpreter is given the command to run File1.py.
python File1.py
Output :
File1 __name__ = __main__
File1 is being run directly

And then File2.py is run.
python File2.py
Output :
File1 __name__ = File1
File1 is being imported
File2 __name__ = __main__
File2 is being run directly

In conclusion, it can be said that if name == “main” is the part of the program that runs when the script is run from the command line using a command like python File1.py.

Decorators in Python

Decorators allow us to wrap another function in order to extend the behavior of the wrapped function, without permanently modifying it.

# defining a decorator
def hello_decorator(func):
    
    # inner1 is a decorator in which the argument is called
    # inner function can access the outer local functions like "func"
    def inner1():
        print("BEFORE function execution")
        
        # calling the actual function inside the decorator function
        func()
        
        print("AFTER the execution")
        
    return inner1


# defining a function to be called inside wrapper
def function_to_be_used():
    print("INSIDE the function!")

    
# passing 'function_to_be_used' inside the decorator to control its behaviour
function_to_be_used = hello_decorator(function_to_be_used)

# calling the function
function_to_be_used()
BEFORE function execution
INSIDE the function!
AFTER the execution

Memoization using decorators in Python

Memoization is the top-down approach to solving a problem with dynamic program. Memoization is a technique of recording the intermediate results so that it can be used to avoid repeated calculations and speed up the programs.

# Simple recursive program to find factorial
def facto(num):
	if num == 1:
		return 1
	else:
		return num * facto(num-1)
		

print(facto(5))
print(facto(5)) # again performing same calculation
120
120
# Factorial program with memoization using decorators.

# A decorator function for function 'f' passed as parameter
memory = {}
def memoize_factorial(f):

	# This inner function has access to memory and 'f'
	def inner(num):
		if num not in memory:
			memory[num] = f(num)
			print('result saved in memory')
		else:
			print('returning result from saved memory')
		return memory[num]

	return inner


@memoize_factorial
def facto(num):
	if num == 1:
		return 1
	else:
		return num * facto(num-1)

print(facto(5))
print(facto(5)) # directly coming from saved memory
result saved in memory
result saved in memory
result saved in memory
result saved in memory
result saved in memory
120
returning result from saved memory
120

The self

Class methods must have an extra first parameter in the method definition. We do not give a value for this parameter when we call the method, Python provides it.

If we have a method that takes no arguments, then we still have to have one argument.

“__init__method” == Constructors!

__init__method is similar to constructors in Java. It is used to initialize object’s state.

Default constructor is a simple constructor which doesn’t accept any arguments.

Parameterized constructor is a constructor with parameters and takes its first argumetn as a reference to the instance being constructed known as self and the rest of the arguments are provided by the programmer.

# Class for Dog
class Dog:
    
    # Class variable
    animal = 'dog'
    
    # The init method (Constructor)
    def __init__(self, breed, color):
        # Instance variable
        self.breed = breed
        self.color = color

Rodger = Dog("Pug", "brown")
print('Rodger details:')  
print('Rodger is a', Rodger.animal)
print('Breed: ', Rodger.breed)
print('Color: ', Rodger.color)
Rodger details:
Rodger is a dog
Breed:  Pug
Color:  brown

Destructors

Destructors are called when an object gets destroyed.

Inheritance in Python

Inheritance provides reusability of a code.

# parent class
class Person( object ):
	def __init__(self, name, idnumber):
		self.name = name
		self.idnumber = idnumber
	def display(self):
		print(self.name)
		print(self.idnumber)

# child class
class Employee( Person ):
	def __init__(self, name, idnumber, salary, post):
		self.salary = salary
		self.post = post

		# invoking the __init__ of the parent class
		Person.__init__(self, name, idnumber)


# creation of an object variable or an instance
a = Employee('Rahul', 886012, 200000, "Intern")

# calling a function of the class Person using its instance
a.display()
Rahul
886012

Unlike Java, Python shows multiple inheritance.

class Base1(object):
    def __init__(self):
        self.str1 = "Geek1"
        print("Base1")
        
class Base2(object):
    def __init__(self):
        self.str2 = "Geek2"
        print("Base2")

class Derived(Base1, Base2):
    def __init__(self):
        
        Base1.__init__(self)
        Base2.__init__(self)
        print("Derived")
        
    def printStrs(self):
        print(self.str1, self.str2)
        
ob = Derived()
ob.printStrs()
Base1
Base2
Derived
Geek1 Geek2

We can have a child and grandchild relationship –> Multilevel inheritance.

When more than one derived classes are created from a single base –> Hierarchical inheritance.

When we do not want the instance variables of the parent class to be inherited by the child class, we can make some of the instance variables private. We can do that by adding double under scores before its name.

class C(object):
    def __init__(self):
        self.c = 21
        
        # d is private instance variable
        self.__d = 42

class D(C):
    def __init__(self):
        self.e = 84
        C.__init__(self)

object1 = D()
print(object1.d) ## will throw an AttributeError
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

<ipython-input-38-40374b247fd2> in <module>
     12 
     13 object1 = D()
---> 14 print(object1.d)


AttributeError: 'D' object has no attribute 'd'

Encapsulation in Python

Protected members are those members of the class that cannot be accessed outside the class but can be accessed from within the class and its subclasses. To accomplish this, users can prefix the name of the member by a single underscore “_”.

As stated above, private members are instances that cannot be accessed except inside a class. Users accomplish this by prefixing the name of the member by a double underscore “__”.

Polymorphism in Python

Polymorphism means the same function name (but different signatures) being used for different types.

While method overloading is not supported in Python, method overriding is supported.

Key Difference between Method Overloading and Overriding

Overloading Overriding
compile-time polymorphism run-time polymorphism
helps to increase readability used to grant specific implementation of the method which is already provided by its superclass
occurs within class performed in 2 classes with inheritance relationship
same name, different signatures, not necessarily same return type same name, signature, return type
not supported in Python always used in Python OOP

“Static” in Python

While Java requires specification of static and non-static members, Python does not require a static keyword.

Class method and Static method

@classmethod is a built-in function decorator that is an expression that gets evalutated after users’ function is defined.

A class method is a method that is bound to the class and not the object of the class.

A class method has the access to the state of the class as it takes a class parameter that points to the class and not the object instance.

It can modify a class state that would apply across all the instances of the class. For example, it can modify a class variable that will be applicable to all the instances.

A static method does not receive an implicit first argument, but it is bound to the class and not the object of the class. The difference between class method and static method is that static method cannot access or modify the class state.

Users generally use the class method to create factory methods, while static methods to create utility functions.

from datetime import date

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    # class method to create a Person object by birth year
    @classmethod
    def fromBirthYear(cls, name, year):
        return cls(name, date.today().year - year)
        
    # static method to check if a Person is adult
    @staticmethod
    def isAdult(age):
        return age > 18

person1 = Person("Mike", 21)
person2 = Person.fromBirthYear("Jeongrok", 1998)

print(person1.age)
print(person2.age)

print(Person.isAdult(22))
21
24
True