Advanced Python Material
Advanced Python Material
Programming in Python
Object-oriented programming (OOP) is a programming paradigm
based on the concept of "objects". The object contains both data and
code: Data in the form of properties (often known as attributes), and
code, in the form of methods (actions object can perform).
Attribute
Behavior
An object consists of :
State: It is represented by the attributes of an object. It also reflects the
properties of an object.
Behavior: It is represented by the methods of an object. It also reflects the
response of an object to other objects.
Identity: It gives a unique name to an object and enables one object to
interact with other objects.
class ClassName:
# Statement
obj = ClassName()
print(obj.atrr)
Class creates a user-defined data structure, which holds its own data members
and member functions, which can be accessed and used by creating an
instance of that class. A class is like a blueprint for an object.
Some points on Python class:
Classes are created by keyword class.
Attributes are the variables that belong to a class.
Attributes are always public and can be accessed using the dot (.) operator.
Eg.: Myclass.Myattribute
Example:
# Python3 program to
# demonstrate instantiating
# a class
class Dog:
# A simple class
# attribute
attr1 = "mammal"
attr2 = "dog"
# A sample method
def fun(self):
# Driver code
# Object instantiation
Rodger = Dog()
print(Rodger.attr1)
Rodger.fun()
Output:
mammal
I'm a mammal
I'm a dog
.
__init__ method
The __init__ method is similar to constructors in C++ and Java. Constructors
are used to initializing the object’s state. Like methods, a constructor also
contains a collection of statements(i.e. instructions) that are executed at the
time of Object creation. It runs as soon as an object of a class is instantiated.
The method is useful to do any initialization you want to do with your object.
class Person:
self.name = name
# Sample Method
def say_hi(self):
p = Person('Nikhil')
p.say_hi()
Output:
Hello, my name is Nikhil
# variables.
class Dog:
# Class Variable
animal = 'dog'
# Instance Variable
self.breed = breed
self.color = color
print('Rodger details:')
print('\nBuzo details:')
# name also
print(Dog.animal)
Output:
Rodger details:
Rodger is a dog
Breed: Pug
Color: brown
Buzo details:
Buzo is a dog
Breed: Bulldog
Color: black
class Dog:
# Class Variable
animal = 'dog'
# Instance Variable
self.breed = breed
self.color = color
def getColor(self):
return self.color
# Driver Code
Rodger = Dog("pug")
Rodger.setColor("brown")
print(Rodger.getColor())
Output:
brown
constructors
Constructors are generally used for instantiating an object. The task of
constructors is to initialize(assign values) to the data members of the class
when an object of the class is created. In Python the __init__() method is called
the constructor and is always called when an object is created.
Syntax of constructor declaration :
def __init__(self):
# body of the constructor
Types of constructors :
default constructor: The default constructor is a simple constructor which
doesn’t accept any arguments. Its definition has only one argument which is
a reference to the instance being constructed.
parameterized constructor: constructor with parameters is known as
parameterized constructor. The parameterized constructor takes its first
argument as a reference to the instance being constructed known as self
and the rest of the arguments are provided by the programmer.
# default constructor
def __init__(self):
self.geek = "GeekforGeeks"
def print_Geek(self):
print(self.geek)
obj.print_Geek()
Output :
GeekforGeeks
Example of the parameterized constructor :
class Addition:
first = 0
second = 0
answer = 0
# parameterized constructor
self.first = f
self.second = s
def display(self):
def calculate(self):
self.answer = self.first + self.second
# perform Addition
obj.calculate()
# display result
obj.display()
Output :
First number = 1000
Second number = 2000
Addition of two numbers = 3000
Data hiding is a concept which underlines the hiding of data or information from
the user. It is one of the key aspects of Object-Oriented programming
strategies. It includes object details such as data members, internal work. Data
hiding excludes full data entry to class members and defends object integrity by
preventing unintended changes. Data hiding also minimizes system complexity
for increase robustness by limiting interdependencies between software
requirements. Data hiding is also known as information hiding. In class, if we
declare the data members as private so that no other class can access the data
members, then it is a process of hiding data.
Data Hiding in Python:
The Python document introduces Data Hiding as isolating the user from a part
of program implementation. Some objects in the module are kept internal,
unseen, and unreachable to the user. Modules in the program are easy enough
to understand how to use the application, but the client cannot know how the
application functions. Thus, data hiding imparts security, along with discarding
dependency. Data hiding in Python is the technique to defend access to specific
users in the application. Python is applied in every technical area and has a
user-friendly syntax and vast libraries. Data hiding in Python is performed using
the __ double underscore before done prefix. This makes the class members
non-public and isolated from the other classes.
Example
class Solution:
__privateCounter = 0
def sum(self):
self.__privateCounter += 1
print(self.__privateCounter)
count = Solution()
count.sum()
count.sum()
print(count.__privateCount)
Output:
Traceback (most recent call last):
File "/home/db01b918da68a3747044d44675be8872.py", line 11, in
<module>
print(count.__privateCount)
AttributeError: 'Solution' object has no attribute
'__privateCount'
To rectify the error, we can access the private member through the class
name :
class Solution:
__privateCounter = 0
def sum(self):
self.__privateCounter += 1
print(self.__privateCounter)
count = Solution()
count.sum()
count.sum()
print(count._Solution__privateCounter)
Output:
1
2
2
Advantages of Data Hiding:
1. It helps to prevent damage or misuse of volatile data by hiding it from the
public.
2. The class objects are disconnected from the irrelevant data.
3. It isolates objects as the basic concept of OOP.
4. It increases the security against hackers that are unable to access important
data.
Disadvantages of Data Hiding:
1. It enables programmers to write lengthy code to hide important data from
common clients.
2. The linkage between the visible and invisible data makes the objects work
faster, but data hiding prevents this linkage.
Example:
class Student:
# constructor
def __init__(self, name, age):
# Instance variable
self.name = name
self.age = age
First, create instance variables name and age in the Student class.
Next, create an instance method display() to print student name
and age.
Next, create object of a Student class to call the instance method.
et’s see how to call an instance method show() to access the student
object details such as name and age.
class Student:
# constructor
def __init__(self, name, age):
# Instance variable
self.name = name
self.age = age
# instance method access instance variable
def show(self):
print('Name:', self.name, 'Age:', self.age)
Output:
First Student
Second Student
Note:
Inside any instance method, we can use self to access any data or
method that reside in our class. We are unable to access it without
a self parameter.
An instance method can freely access attributes and even modify the
value of attributes of an object by using the self parameter.
class Student:
def __init__(self, roll_no, name, age):
# Instance variable
self.roll_no = roll_no
self.name = name
self.age = age
# create object
print('class VIII')
stud = Student(20, "Emma", 14)
# call instance method
stud.show()
Output:
class VIII
class IX
Example:
class Student:
def __init__(self, roll_no, name, age):
# Instance variable
self.roll_no = roll_no
self.name = name
self.age = age
# create object
stud = Student(20, "Emma", 14)
# call instance method
stud.add_marks(75)
# display object
print('Roll Number:', stud.roll_no, 'Name:', stud.name, 'Age:', stud.age,
'Marks:', stud.marks)
Output:
Example:
import types
class Student:
# constructor
def __init__(self, name, age):
self.name = name
self.age = age
# instance method
def show(self):
print('Name:', self.name, 'Age:', self.age)
# create object
s1 = Student("Jessa", 15)
Example:
class Student:
# constructor
def __init__(self, name, age):
self.name = name
self.age = age
# instance method
def show(self):
print('Name:', self.name, 'Age:', self.age)
# instance method
def percentage(self, sub1, sub2):
print('Percentage:', (sub1 + sub2) / 2)
Run
Output:
Percentage: 64.5
del emma.percentage
AttributeError: percentage
The delattr() is used to delete the named attribute from the object with
the prior permission of the object. Use the following syntax to delete
the instance method.
delattr(object, name)
Example:
class Student:
# Class variable
school_name = 'ABC School '
Output
class Student:
# Class variable
school_name = 'ABC School '
# constructor
def __init__(self, name):
self.name = name
# access class variable inside constructor using self
print(self.school_name)
# access using class name
print(Student.school_name)
# create Object
s1 = Student('Emma')
Output
ABC School
ABC School
class Student:
# Class variable
school_name = 'ABC School '
# constructor
def __init__(self, name, roll_no):
self.name = name
self.roll_no = roll_no
# Instance method
def show(self):
print('Inside instance method')
# access using self
print(self.name, self.roll_no, self.school_name)
# access using class name
print(Student.school_name)
# create Object
s1 = Student('Emma', 10)
s1.show()
print('Outside class')
# access class variable outside class
# access using object reference
print(s1.school_name)
Output
ABC School
Outside class
ABC School
ABC School
Example
class Student:
# Class variable
school_name = 'ABC School '
# constructor
def __init__(self, name, roll_no):
self.name = name
self.roll_no = roll_no
# Instance method
def show(self):
print(self.name, self.roll_no, Student.school_name)
# create Object
s1 = Student('Emma', 10)
print('Before')
s1.show()
Output:
Before
After
Note:
class Student:
# Class variable
school_name = 'ABC School '
# constructor
def __init__(self, name, roll_no):
self.name = name
self.roll_no = roll_no
# create Objects
s1 = Student('Emma', 10)
s2 = Student('Jessa', 20)
print('Before')
print(s1.name, s1.roll_no, s1.school_name)
print(s2.name, s2.roll_no, s2.school_name)
Output:
Before
After
A new instance variable is created for the s1 object, and this variable
shadows the class variables. So always use the class name to modify
the class variable.
Class Variable vs Instance variables
The following table shows the difference between the instance variable
and the class variable.
Instance variables are declared inside the constructor i.e., Class variables are declared inside the class definition but
the __init__() method. outside any of the instance methods and constructors.
It is gets created when an instance of the class is created. It is created when the program begins to execute.
Changes made to these variables through one object will Changes made in the class variable will reflect in all
Let’s see the example to create a class variable and instance variable.
class Car:
# Class variable
manufacturer = 'BMW'
# create Object
car = Car('x1', 2500)
print(car.model, car.price, Car.manufacturer)
Output:
x1 2500 BMW
When we use inheritance, all variables and methods of the base class
are available to the child class. In such cases, We can also change the
value of the parent class’s class variable in the child class.
We can use the parent class or child class name to change the value of
a parent class’s class variable in the child class.
Example
class Course:
# class variable
course = "Python"
class Student(Course):
def show_student(self):
# Accessing class variable of parent class
print('Before')
print("Student name:", self.name, "Course Name:", Student.course)
# changing class variable value of base class
print('Now')
Student.course = "Machine Learning"
print("Student name:", self.name, "Course Name:", Student.course)
# creating object of Student class
stud = Student("Emma")
stud.show_student()
Run
Output
Before
Now
What if both child class and parent class has the same class
variable name. In this case, the child class will not inherit the class
variable of a base class. So it is recommended to create a separate
class variable for child class instead of inheriting the base class
variable.
Example:
class Course:
# class variable
course = "Python"
class Student(Course):
# class variable
course = "SQL"
def show_student(self):
# Accessing class variable
print('Before')
print("Student name:", self.name, "Course Name:", Student.course)
# changing class variable's value
print('Now')
Student.course = "Machine Learning"
print("Student name:", self.name, "Course Name:", Student.course)
# creating object of Student class
stud = Student("Emma")
stud.show_student()
Run
Output:
Before
Now
For example,
Example
class Player:
# class variables
club = 'Chelsea'
sport = 'Football'
def show(self):
print("Player :", 'Name:', self.name, 'Club:', self.club, 'Sports:',
self.sport)
p1 = Player('John')
p2 = Player('Emma')
p2.sport = 'Tennis'
p2.show()
Run
Output
In the above example, the instance variable name is unique for each
player. The class variable team and sport can be accessed and modified
by any object.
In our case, for object p1 new instance variable club gets created, and
for object p2 new instance variable sport gets created.
So when you try to access the class variable using the p1 or p2 object, it
will not return the actual class variable value.
inheritance in Python
The process of inheriting the properties of the parent class into a child class is called
inheritance. The existing class is called a base class or parent class and the new class is called a
subclass or child class or derived class.
In this Python lesson, you will learn inheritance, method overloading, method overriding, types
of inheritance, and MRO (Method Resolution Order).
In inheritance, the child class acquires all the data members, properties, and functions from the
parent class. Also, a child class can also provide its specific implementation to the methods of
the parent class.
For example, In the real world, Car is a sub-class of a Vehicle class. We can create a Car by
inheriting the properties of a Vehicle such as Wheels, Colors, Fuel tank, engine, and add extra
properties in Car as required.
Syntax
class BaseClass:
Body of base class
class DerivedClass(BaseClass):
Body of derived class
Types Of Inheritance
In Python, based upon the number of child and parent classes involved, there are five types of
inheritance. The type of inheritance are listed below:
1. Single inheritance
2. Multiple Inheritance
3. Multilevel inheritance
4. Hierarchical Inheritance
5. Hybrid Inheritance
Example
Let’s create one parent class called ClassOne and one child class called ClassTwo to implement
single inheritance.
# Base class
class Vehicle:
def Vehicle_info(self):
print('Inside Vehicle class')
# Child class
class Car(Vehicle):
def car_info(self):
print('Inside Car class')
Output
Multiple Inheritance
In multiple inheritance, one child class can inherit from multiple parent classes. So here is one
child class and multiple parent classes.
Example
# Parent class 1
class Person:
def person_info(self, name, age):
print('Inside Person class')
print('Name:', name, 'Age:', age)
# Parent class 2
class Company:
def company_info(self, company_name, location):
print('Inside Company class')
print('Name:', company_name, 'location:', location)
# Child class
class Employee(Person, Company):
def Employee_info(self, salary, skill):
print('Inside Employee class')
print('Salary:', salary, 'Skill:', skill)
# access data
emp.person_info('Jessa', 28)
emp.company_info('Google', 'Atlanta')
emp.Employee_info(12000, 'Machine Learning')
Run
Output
In the above example, we created two parent classes Person and Company respectively. Then we
create one child called Employee which inherit from Person and Company classes.
Multilevel inheritance
In multilevel inheritance, a class inherits from a child class or derived class. Suppose three
classes A, B, C. A is the superclass, B is the child class of A, C is the child class of B. In other
words, we can say a chain of classes is called multilevel inheritance.Python Multilevel
Inheritance
Example
# Base class
class Vehicle:
def Vehicle_info(self):
print('Inside Vehicle class')
# Child class
class Car(Vehicle):
def car_info(self):
print('Inside Car class')
# Child class
class SportsCar(Car):
def sports_car_info(self):
print('Inside SportsCar class')
Output
Example
Let’s create ‘Vehicle’ as a parent class and two child class ‘Car’ and ‘Truck’ as a parent class.
class Vehicle:
def info(self):
print("This is Vehicle")
class Car(Vehicle):
def car_info(self, name):
print("Car name is:", name)
class Truck(Vehicle):
def truck_info(self, name):
print("Truck name is:", name)
obj1 = Car()
obj1.info()
obj1.car_info('BMW')
obj2 = Truck()
obj2.info()
obj2.truck_info('Ford')
Output
This is Vehicle
This is Vehicle
Hybrid Inheritance
When inheritance is consists of multiple types or a combination of different inheritance is called
hybrid inheritance.Python hybrid inheritance
Example
class Vehicle:
def vehicle_info(self):
print("Inside Vehicle class")
class Car(Vehicle):
def car_info(self):
print("Inside Car class")
class Truck(Vehicle):
def truck_info(self):
print("Inside Truck class")
# create object
s_car = SportsCar()
s_car.vehicle_info()
s_car.car_info()
s_car.sports_car_info()
Run
Note: In the above example, hierarchical and multiple inheritance exists. Here we created,
parent class Vehicle and two child classes named Car and Truck this is hierarchical inheritance.
Another is SportsCar inherit from two parent classes named Car and Vehicle. This is multiple
inheritance.
In child class, we can refer to parent class by using the super() function. The super function
returns a temporary object of the parent class that allows us to call a parent class method inside a
child class method.
Example
class Company:
def company_name(self):
return 'Google'
class Employee(Company):
def info(self):
# Calling the superclass method using super()function
c_name = super().company_name()
print("Jessa works at", c_name)
Run
Output:
In the above example, we create a parent class Company and child class Employee.
In Employee class, we call the parent class method by using a super() function.
issubclass()
In Python, we can verify whether a particular class is a subclass of another class. For this
purpose, we can use Python built-in function issubclass(). This function returns True if the
given class is the subclass of the specified class. Otherwise, it returns False.
Syntax
issubclass(class, classinfo)
Where,
class Company:
def fun1(self):
print("Inside parent class")
class Employee(Company):
def fun2(self):
print("Inside child class.")
class Player:
def fun3(self):
print("Inside Player class.")
# Result True
print(issubclass(Employee, Company))
# Result False
print(issubclass(Employee, list))
# Result False
print(issubclass(Player, Company))
# Result True
print(issubclass(Employee, (list, Company)))
# Result True
print(issubclass(Company, (list, Company)))
Run
Method Overriding
In inheritance, all members available in the parent class are by default available in the child
class. If the child class does not satisfy with parent class implementation, then the child class is
allowed to redefine that method by extending additional functions in the child class. This concept
is called method overriding.
When a child class method has the same name, same parameters, and same return type as a
method in its superclass, then the method in the child is said to override the method in the parent
class.
Example
class Vehicle:
def max_speed(self):
print("max speed is 100 Km/Hour")
class Car(Vehicle):
# overridden the implementation of Vehicle class
def max_speed(self):
print("max speed is 200 Km/Hour")
Run
Output:
In the above example, we create two classes named Vehicle (Parent class) and Car (Child
class). The class Car extends from the class Vehicle so, all properties of the parent class are
available in the child class. In addition to that, the child class redefined the
method max_speed().
This order is also called the Linearization of a class, and a set of rules is called MRO (Method
Resolution Order). The MRO plays an essential role in multiple inheritances as a single
method may found in multiple parent classes.
1. First, it searches in the current parent class if not available, then searches in the parents class
specified while inheriting (that is left to right.)
2. We can get the MRO of a class. For this purpose, we can use either the mro attribute or
the mro() method.
Example
class A:
def process(self):
class B(A):
def process(self):
def process(self):
C1 = C()
C1.process()
print(C.mro())
# In class C
In the above example, we create three classes named A, B and C. Class B is inherited from A,
class C inherits from B and A. When we create an object of the C class and calling
the process() method, Python looks for the process() method in the current class in
the C class itself.
Then search for parent classes, namely B and A, because C class inherit from B and A. that is, C(B,
A) and always search in left to right manner.
class A:
def process(self):
class B(A):
def process(self):
class C(A,B):
def process(self):
C1 = C()
C1.process()
class A:
def process(self):
class B():
def process(self):
def process1(self):
C1 = C()
C1.process()
use this code, the author did a small mistake by inheriting class A in B, just
remove that A. It should look like classB()
Example:
# calculate count
print(len(students))
print(len(school))
Output
10
class Vehicle:
def show(self):
print('Details:', self.name, self.color, self.price)
def max_speed(self):
print('Vehicle max speed is 150')
def change_gear(self):
print('Vehicle change 6 gear')
def change_gear(self):
print('Car change 7 gear')
# Car Object
car = Car('Car x1', 'Red', 20000)
car.show()
# calls methods from Car class
car.max_speed()
car.change_gear()
# Vehicle Object
vehicle = Vehicle('Truck x1', 'white', 75000)
vehicle.show()
# calls method from a Vehicle class
vehicle.max_speed()
vehicle.change_gear()
Output:
On the other hand, the show() method isn’t overridden in the Car class,
so it is used from the Vehicle class.
Example
class Shopping:
def __init__(self, basket, buyer):
self.basket = list(basket)
self.buyer = buyer
def __len__(self):
print('Redefine length')
count = len(self.basket)
# count total items in a different way
# pair of shoes and shir+pant
return count * 2
Output
Redefine length
Python allows different classes to have methods with the same name.
Let’s design a different class in the same way by adding the same
methods in two or more classes.
Next, create an object of each class
Next, add all objects in a tuple.
In the end, iterate the tuple using a for loop and call methods of a
object without checking its class.
Example
class Ferrari:
def fuel_type(self):
print("Petrol")
def max_speed(self):
print("Max speed 350")
class BMW:
def fuel_type(self):
print("Diesel")
def max_speed(self):
print("Max speed is 240")
ferrari = Ferrari()
bmw = BMW()
Output
Petrol
Diesel
Example
class Ferrari:
def fuel_type(self):
print("Petrol")
def max_speed(self):
print("Max speed 350")
class BMW:
def fuel_type(self):
print("Diesel")
def max_speed(self):
print("Max speed is 240")
# normal function
def car_details(obj):
obj.fuel_type()
obj.max_speed()
ferrari = Ferrari()
bmw = BMW()
car_details(ferrari)
car_details(bmw)
Run
Output
Petrol
Diesel
Let us see how a built-in method process objects having different data
types.
Example:
print('Reverse string')
for i in reversed('PYnative'):
print(i, end=' ')
print('\nReverse list')
for i in reversed(['Emma', 'Jessa', 'Kelly']):
print(i, end=' ')
Run
Output:
Reverse string
e v i t a n Y P
Reverse list
Method Overloading
The process of calling the same method with different parameters is
known as method overloading. Python does not support method
overloading. Python considers only the latest defined method even if
you overload the method. Python will raise a TypeError if you overload
the method.
Example
Run
To overcome the above problem, we can use different ways to achieve
the method overloading. In Python, to overload the class method, we
need to write the method’s logic so that different code executes inside
the function depending on the parameter passes.
For example, the built-in function range() takes three parameters and
produce different result depending upon the number of parameters
passed to it.
Example:
Run
Output:
0, 1, 2, 3, 4,
5, 6, 7, 8, 9,
2, 4, 6, 8, 10,
class Shape:
# function with two default parameters
def area(self, a, b=0):
if b > 0:
print('Area of Rectangle is:', a * b)
else:
print('Area of Square is:', a ** 2)
square = Shape()
square.area(5)
rectangle = Shape()
rectangle.area(5, 3)
Output:
Example:
# add 2 numbers
print(100 + 200)
Output:
300
JessRoy
[10, 20, 30, 'jessa', 'emma', 'kelly']
Example:
class Book:
def __init__(self, pages):
self.pages = pages
Output
Example:
class Book:
def __init__(self, pages):
self.pages = pages
b1 = Book(400)
b2 = Book(300)
print("Total number of pages: ", b1 + b2)
Output
Example:
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
class TimeSheet:
def __init__(self, name, days):
self.name = name
self.days = days
Output
Wroked for 50 days
Magic Methods
In Python, there are different magic methods available to perform
overloading operations. The below table shows the magic methods
names to overload the mathematical operator, assignment operator,
and relational operators in Python.
Python has numerous built-in exceptions that force your program to output an
error when something in the program goes wrong.
However, sometimes you may need to create your own custom exceptions
that serve your purpose.
class ValueTooSmallError(Error):
"""Raised when the input value is too small"""
pass
class ValueTooLargeError(Error):
"""Raised when the input value is too large"""
pass
Enter a number: 12
This value is too large, try again!
Enter a number: 0
This value is too small, try again!
Enter a number: 8
This value is too small, try again!
Enter a number: 10
Congratulations! You guessed it correctly.
To learn about customizing the Exception classes, you need to have the basic
knowledge of Object-Oriented programming.
Attributes:
salary -- input salary which caused the error
message -- explanation of the error
"""
Output
Here, we have overridden the constructor of the Exception class to accept our
own custom arguments salary and message . Then, the constructor of the
parent Exception class is called manually with the self.message argument
using super() .
def __str__(self):
return f'{self.salary} -> {self.message}'
Output
Iterators
Python iterator objects are required to support two methods while following the iterator
protocol.
__iter__ returns the iterator object itself. This is used in for and in statements.
__next__ method returns the next value from the iterator. If there is no more items to
return then it should raise StopIteration exception.
class Counter(object):
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
'Returns itself as an iterator object'
return self
def __next__(self):
'Returns the next value till current is lower than high'
if self.current > self.high:
raise StopIteration
else:
self.current += 1
return self.current - 1
>>> c = Counter(5,10)
>>> for i in c:
... print(i, end=' ')
...
5 6 7 8 9 10
Remember that an iterator object can be used only once. It means after it
raises StopIteration once, it will keep raising the same exception.
>>> c = Counter(5,6)
>>> next(c)
5
>>> next(c)
6
>>> next(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in next
StopIteration
>>> next(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in next
StopIteration
Using the iterator in for loop example we saw, the following example tries to show the
code behind the scenes.
Generators
In this section we learn about Python generators. They were introduced in Python 2.3. It
is an easier way to create iterators using a keyword yield from a function.
In the above example we create a simple generator using the yield statements. We can
use it in a for loop just like we use any other iterators.
In the next example we will create the same Counter class using a generator function
and use it in a for loop.
Inside the while loop when it reaches to the yield statement, the value of low is returned
and the generator state is suspended. During the second next call the generator
resumed where it freeze-ed before and then the value of low is increased by one. It
continues with the while loop and comes to the yield statement again.
When you call an generator function it returns a *generator* object. If you call *dir* on
this object you will find that it contains __iter__ and *__next__* methods among the
other methods.
>>> c = counter_generator(5,10)
>>> dir(c)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__',
'__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__',
'close', 'gi_code', 'gi_frame', 'gi_running', 'send', 'throw']
We mostly use generators for laze evaluations. This way generators become a good
approach to work with lots of data. If you don’t want to load all the data in the memory,
you can use a generator which will pass you each piece of data at a time.
One of the biggest example of such example is os.path.walk() function which uses a
callback function and current os.walk generator. Using the generator implementation
saves memory.
We can have generators which produces infinite values. The following is a one such
example.
>>> g = my_generator()
>>> for c in g:
... print(c)
...
Inside my generator
a
b
c
>>> for c in g:
... print(c)
...
One way to create a reusable generator is Object based generators which do not hold
any state. Any class with a __iter__ method which yields data can be used as an object
generator. In the following example we will recreate our counter generator.
Generator expressions
In this section we will learn about generator expressions which is a high performance,
memory efficient generalization of list comprehensions and generators.
For example we will try to sum the squares of all numbers from 1 to 9.
The example actually first creates a list of the square values in memory and then it
iterates over it and finally after sum it frees the memory. You can understand the
memory usage in case of a big list.
The syntax of generator expression says that always needs to be directly inside a set of
parentheses and cannot have a comma on either side. Which basically means both the
examples below are valid generator expression usage example.
We can do the same using a shell command tail -f /var/log/cron |grep anacron
Closures
Closures are nothing but functions that are returned by another function. We use
closures to remove code duplication. In the following example we create a simple
closure for adding numbers.
Decorators
Decorator is way to dynamically add some new behavior to some objects. We achieve
the same in Python by using closures.
In the example we will create a simple example which will print some statement before
and after the execution of a function.