Unit 3 Class and Objects
Unit 3 Class and Objects
Class Definition - Creating Objects - Built-in Attribute Methods - Built-in Class Attributes -
Destructors in Python - Encapsulation - Data Hiding - Inheritance - Method Overriding Polymorphism.
Class Definition
Classes provide a means of bundling data and functionality together.
Creating a new class creates a new type of object, allowing new instances of that type to be made. Each
class instance can have attributes attached to it for maintaining its state. Class instances can also have
methods for modifying its state.
Classes and instances are related to each other.
Classes provide the definition of an object.
Instances are the objects specified in the class definition.
Compared with other programming languages, Python’s class mechanism adds classes with a minimum
of new syntax and semantics. It is a mixture of the class mechanisms found in C++ and Modula-3.
Python classes provide all the standard features of Object Oriented Programming:
Class inheritance mechanism allows multiple base classes, a derived class can override any
methods of its base class or classes, and a method can call the method of a base class with the
same name.
Objects can contain arbitrary amounts and kinds of data.
Classes share the dynamic nature of Python: they are created at runtime, and can be modified
further after creation.
Class Definition
Class definitions, like function definitions (def statements) must be executed before they have any
effect.
The statements inside a class definition will usually be function definitions, but other statements are
allowed. The function definitions inside a class have a peculiar form of argument list, dictated by the
calling conventions for methods.
When a class definition is entered, a new namespace is created, and used as the local scope, thus, all
assignments to local variables go into this new namespace. In particular, function definitions bind the
name of the new function here.
Create a Class
To create a class, use the keyword class:
class MyClass:
x=5
print(MyClass)
Class Objects
Create Object
Attribute references use the standard syntax used for all attribute references in Python: obj.name. Valid
attribute names are all the names that were in the class’s namespace when the class object was created.
So, if the class definition looked like this:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
then MyClass.i and MyClass.f are valid attribute references, returning an integer and a function object,
respectively.
Class attributes can also be assigned to, so you can change the value of MyClass.i by assignment.
Class instantiation uses function notation. Just pretend that the class object is a parameter-less function
that returns a new instance of the class. For example (assuming the above class):
x = MyClass()
creates a new instance of the class and assigns this object to the local variable x.
All classes have a function called __init__(), which is always executed when the class is being initiated.
__init__() function is used to assign values to the object properties, or other operations that are
necessary to do when the object is being created.
Many classes like to create objects with instances customized to a specific initial state. Therefore a
class may define a special method named __init__(), like this:
def __init__(self):
self.data = []
When a class defines an __init__() method, class instantiation automatically invokes __init__() for the
newly created class instance. So in this example, a new, initialized instance can be obtained by:
x = MyClass()
Of course, the __init__() method may have arguments for greater flexibility. In that case, arguments
given to the class instantiation operator are passed on to __init__().
Create a class named Person, use the __init__() function to assign values for name and age:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p1 = Person("John", 36)
print(p1.name)
print(p1.age)
Another example,
>>> class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
The self parameter is a reference to the current instance of the class, and is used to access variables that
belongs to the class.
It does not have to be named self, you can call it whatever you like, but it has to be the first parameter
of any function in the class:
class Person:
def __init__(mysillyobject, name, age):
mysillyobject.name = name
mysillyobject.age = age
def myfunc(abc):
print("Hello my name is " + abc.name)
p1 = Person("John", 36)
p1.myfunc()
Output: Hello my name is John
The __str__() Function
The __str__() function controls what should be returned when the class object is represented as a string.
If the __str__() function is not set, the string representation of the object is returned:
def __str__(self):
return f"{self.name}({self.age})"
p1 = Person("John", 36)
print(p1)
Output: John(36)
Object Methods
Objects can also contain methods. Methods in objects are functions that belong to the object.
p1 = Person("John", 36)
p1.myfunc()
Output: Hello my name is John
Delete Objects
class definitions cannot be empty, but if for some reason have a class definition with no content, put in
the pass statement to avoid getting an error.
class Person:
pass
Instance variables are for data, unique to each instance and class variables are for
attributes and methods shared by all instances of the class.
Instance variables are variables whose value is assigned inside a constructor or method
with self, whereas class variables are variables whose value is assigned in the class.
Defining instance variables using the normal method:
class Dog:
# Class Variable
animal = 'dog'
# Instance Variable
self.breed = breed
# Driver Code
Rodger = Dog("pug")
Rodger.setColor("brown")
print(Rodger.getColor())
Output:
brown
Defining instance variables using a constructor.
class Dog:
# Class Variable
animal = 'dog'
# Instance Variable
self.breed = breed
self.color = color
print('Rodger details:')
print('Rodger is a', Rodger.animal)
print('Breed: ', Rodger.breed)
print('Color: ', Rodger.color)
print('\nBuzo details:')
print('Buzo is a', Buzo.animal)
print('Breed: ', Buzo.breed)
print('Color: ', Buzo.color)
Output:
Rodger details:
Rodger is a dog
Breed: Pug
Color: brown
Buzo details:
Buzo is a dog
Breed: Bulldog
Color: black
Accessing class variable using class name
dog
Built-in Attribute Methods
Attributes of a class are function objects that define corresponding methods of its instances.
They are used to implement access controls of the classes.
Attributes of a class can also be accessed using the following built-in methods and functions :
1. getattr() – This function is used to access the attribute of object.
2. hasattr() – This function is used to check if an attribute exist or not.
3. setattr() – This function is used to set an attribute. If the attribute does not exist, then it would be
created.
4. delattr() – This function is used to delete an attribute. If you are accessing the attribute after
deleting it raises error “class has no attribute”.
The following methods are explained with the example given below:
class emp:
name='Harish'
salary='25000'
def show(self):
print (self.name)
print (self.salary)
e1 = emp()
# Use getattr instead of e1.name
print (getattr(e1,'name'))
# sets an attribute
setattr(e1,'height',152)
Output :
Harish
True
152
Static methods: A static method is a method [member function] that don’t use argument self at all.
To declare a static method, proceed it with the statement “@staticmethod”.
class test:
@staticmethod
def square(x):
test.result = x*x
t2.square(3)
print (t2.result)
Output :
4
9
9
Accessing attributes and methods of one class in another class is done by passing the object of one
class to another.
class ClassA():
def __init__(self):
self.var1 = 1
self.var2 = 2
def methodA(self):
self.var1 = self.var1 + self.var2
return self.var1
class ClassB(ClassA):
def __init__(self, class_a):
self.var1 = class_a.var1
self.var2 = class_a.var2
object1 = ClassA()
# updates the value of var1
summ = object1.methodA()
Output : 3 3 2
Attributes Description
__doc__ If there is a class documentation class, this returns it. Otherwise, None
__module__ Module name in which the class is defined. This attribute is "__main__" in
interactive mode.
__bases__ A possibly empty tuple containing the base classes, in the order of their
occurrence in the base class list.
Output
Welcome to Python
Output
Sample
Output
__main__
Output
(<class 'object'>,)
# printing the name of the Employee class using __name__ class attribute
print("Name of the class using the __name__ attribute:\n", Employee.__name__)
# printing the module of the Employee class using __module__ class attribute
print("Module of the class using the __module__ attribute:\n", Employee.__module__)
# printing all the base classes for the derived class using __bases__ class attribute
print("Base classes of subEmployee class:\n", subEmployee.__bases__)
e=Employee('john',24000)
print(e.name,e.salary)
e1=subEmployee(e.name,e.salary)
print(e1.name)
print(e1.salary)
Output
Employee class documentation using the __doc__ attribute:
Common base class for all employees
Name of the class using the __name__ attribute:
Employee
Module of the class using the __module__ attribute:
__main__
Dictionary containing the Employee class namespace:
{'__module__': '__main__', '__doc__': 'Common base class for all employees', 'empCount': 0, '__init__':
<function Employee.__init__ at 0x7f9532090050>, 'displayCount': <function Employee.displayCount
at 0x7f95320900e0>, 'displayEmployee': <function Employee.displayEmployee at 0x7f9532090170>,
'__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of
'Employee' objects>}
Base classes of subEmployee class: (<class '__main__.Employee'>,)
john 24000
john 24000
Destructors in Python
Destructors are called when an object gets destroyed. In Python, destructors are not needed as much
as in C++ because Python has a garbage collector that handles memory management automatically.
The __del__() method is a known as a destructor method in Python. It is called when all references to
the object have been deleted i.e when an object is garbage collected.
def __del__(self):
# body of destructor
Note : A reference to objects is also deleted when the object goes out of reference or when the
program ends.
Example 1 : Here is the simple example of destructor. By using del keyword we deleted all the
references of object ‘obj’, therefore destructor invoked automatically.
class Employee:
# Initializing
def __init__(self):
print('Employee created.')
obj = Employee()
del obj
Output
Employee created.
Destructor called, Employee deleted.
Note : The destructor was called after the program ended or when all the references to object are
deleted i.e when the reference count becomes zero, not when object went out of scope.
Example 2: This example gives the explanation of above-mentioned note. Here, notice that the
destructor is called after the ‘Program End…’ printed.
class Employee:
# Initializing
def __init__(self):
print('Employee created')
# Calling destructor
def __del__(self):
print("Destructor called")
def Create_obj():
print('Making Object...')
obj = Employee()
print('function end...')
return obj
Output
Calling Create_obj() function...
Making Object...
Employee created
function end...
Program End...
Destructor called
class A:
def __init__(self, bb):
self.b = bb
class B:
def __init__(self):
self.a = A(self)
def __del__(self):
print("die")
def fun():
b = B()
fun()
Output
die
In this example when the function fun() is called, it creates an instance of class B which passes itself
to class A, which then sets a reference to class B and resulting in a circular reference.
Generally, Python’s garbage collector which is used to detect these types of cyclic references would
remove it but in this example the use of custom destructor marks this item as “uncollectable”.
Simply, it doesn’t know the order in which to destroy the objects, so it leaves them. Therefore, if
your instances are involved in circular references they will live in memory for as long as the
application run.
Example: Destruction in recursion
Here’s an example of how to use a destructor in a recursive function:
class RecursiveFunction:
def __init__(self, n):
self.n = n
print("Recursive function initialized with n =", n)
def __del__(self):
print("Recursive function object destroyed")
Output
('Recursive function initialized with n =', 5)
('Running recursive function with n =', 5)
('Running recursive function with n =', 4)
('Running recursive function with n =', 3)
('Running recursive function with n =', 2)
('Running recursive function with n =', 1)
Recursive function object destroyed
In this example, we define a class RecursiveFunction with an __init__() method that takes in a
parameter n. This parameter is stored as an attribute of the object.
We also define a run() method that takes in an optional parameter n. If n is not provided, it defaults to
the value of self.n. The run() method runs a recursive function that prints a message to the console
and calls itself with n-1.
We define a destructor using the __del__() method, which simply prints a message to the console
indicating that the object has been destroyed.
We create an object of the class RecursiveFunction with n set to 5, and call the run() method. This
runs the recursive function, printing a message to the console for each call.
Finally, we destroy the object using the del statement. This triggers the destructor, which prints a
message to the console indicating that the object has been destroyed.
Note that in this example, the recursive function will continue running until n reaches 0. When n is 0,
the function will return and the object will be destroyed by the garbage collector. The destructor will
then be called automatically.
Overall, destructors are an important feature of Python and can help to ensure that objects are
properly cleaned up and resources are not wasted. They are easy to use and can be useful for
enforcing encapsulation and other principles of object-oriented design.
Encapsulation
A class is an example of encapsulation as it encapsulates all the data that is member functions,
variables, etc. The goal of information hiding is to ensure that an object’s state is always valid by
controlling access to attributes that are hidden from the outside world.
Consider a real-life example of encapsulation, in a company, there are different sections like the
accounts section, finance section, sales section etc. The finance section handles all the financial
transactions and keeps records of all the data related to finance. Similarly, the sales section handles
all the sales-related activities and keeps records of all the sales. Now there may arise a situation when
due to some reason an official from the finance section needs all the data about sales in a particular
month. In this case, he is not allowed to directly access the data of the sales section. He will first have
to contact some other officer in the sales section and then request him to give the particular data. This
is what encapsulation is. Here the data of the sales section and the employees that can manipulate
them are wrapped under a single name “sales section”. Using encapsulation also hides the data. In
this example, the data of the sections like sales, finance, or accounts are hidden from any other
section.
Protected members
Protected members (in C++ and JAVA) 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 in
Python, just follow the convention by prefixing the name of the member by a single underscore
“_”.
Although the protected variable can be accessed out of the class as well as in the derived class
(modified too in derived class), it is customary(convention not a rule) to not access the protected out
the class body.
Note: The __init__ method is a constructor and runs as soon as an object of a class is instantiated.
# Protected member
self._a = 2
obj1 = Derived()
obj2 = Base()
# Calling protected member can be accessed but should not be done due to convention
print("Accessing protected member of obj1: ", obj1._a)
Output:
Calling protected member of base class: 2
Calling modified protected member outside class: 3
Accessing protected member of obj1: 3
Accessing protected member of obj2: 2
Private members
Private members are similar to protected members, the difference is that the class members declared
private should neither be accessed outside the class nor by any base class. In Python, there is no
existence of Private instance variables that cannot be accessed except inside a class.
However, to define a private member prefix the member name with double underscore “__”.
Note: Python’s private and protected members can be accessed outside the class through python
name mangling.
# Uncommenting obj2 = Derived() will also raise an AttributeError as private member of base class
# is called inside derived class
Output:
Good
Traceback (most recent call last):
File "/home/f4905b43bfcf29567e360c709d3c52bd.py", line 25, in <module>
print(obj1.c)
AttributeError: 'Base' object has no attribute 'c'
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.
Example:
class Solution:
__privateCounter = 0
def sum(self):
self.__privateCounter += 1
print(self.__privateCounter)
count = Solution()
count.sum()
count.sum()
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()
# Here we have accessed the private data member through class name.
print(count._Solution__privateCounter)
Output:
1
2
2
Inheritance
Inheritance is defined as the mechanism of inheriting the properties of the base class to the
child class.
One of the core concepts in object-oriented programming (OOP) languages is inheritance.
It is a mechanism that allows you to create a hierarchy of classes that share a set of properties
and methods by deriving a class from another class.
Inheritance is the capability of one class to derive or inherit the properties from another class.
class BaseClass:
{Body}
class DerivedClass(BaseClass):
{Body}
class Person(object):
# Constructor
def __init__(self, name, id):
self.name = name
self.id = id
# Driver code
emp = Person("Satyam", 102) # An Object of Person
emp.Display()
Output:
Satyam 102
Creating a Child Class
A child class is a class that drives the properties from its parent class. Here Emp is another class that
is going to inherit the properties of the Person class(base class).
class Emp(Person):
def Print(self):
print("Emp class called")
Output:
Mayank 10
Emp class called
class Person(object):
# Constructor
def __init__(self, name):
self.name = name
# To get name
def getName(self):
return self.name
# Driver code
emp = Person("Craig") # An Object of Person
print(emp.getName(), emp.isEmployee())
Output:
Craig False
Suman True
In this example, ‘a’ is the instance created for the class Person. It invokes the __init__() of the
referred class. You can see ‘object’ written in the declaration of the class Person. In Python, every
class inherits from a built-in basic class called ‘object’. The constructor i.e. the ‘__init__’ function of
a class is invoked when we create an object variable or an instance of the class.
The variables defined within __init__() are called instance variables or objects. Hence, ‘name’ and
‘idnumber’ are the objects of the class Person. Similarly, ‘salary’ and ‘post’ are the objects of the
class Employee. Since the class Employee inherits from class Person, ‘name’ and ‘idnumber’ are also
the objects of class Employee.
# parent class
class Person(object):
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
Output:
Rahul
886012
Class A:
def __init__(self, n='Rahul'):
self.name = n
class B(A):
def __init__(self, roll):
self.roll = roll
object = B(23)
print(object.name)
Output :
Traceback (most recent call last):
File "/home/de4570cca20263ac2c4149f435dba22c.py", line 12, in
print (object.name)
AttributeError: 'B' object has no attribute 'name'
def display(self):
print(self.name, self.age)
# child class
class Student(Person):
def __init__(self, name, age):
self.sName = name
self.sAge = age
# inheriting the properties of parent class
super().__init__("Rahul", age)
def displayInfo(self):
print(self.sName, self.sAge)
Output:
Rahul 23
Mayank 23
Adding Properties
One of the features that inheritance provides is inheriting the properties of the parent class as well as
adding new properties of our own to the child class. Let us see this with an example:
#parent class
class Person():
def __init__(self, name, age):
self.name = name
self.age = age
def display(self):
print(self.name, self.age)
# child class
class Student(Person):
def __init__(self, name, age, dob):
self.sName = name
self.sAge = age
self.dob = dob
# inheriting the properties of parent class
super().__init__("Rahul", age)
def displayInfo(self):
print(self.sName, self.sAge, self.dob)
Output:
Here we can see that we added a new property to the child class, i.e., date of birth (dob).
Rahul 23
Mayank 23 16-03-2000
class Base1(object):
def __init__(self):
self.str1 = "Good"
print("Base1")
class Base2(object):
def __init__(self):
self.str2 = "Morning"
print("Base2")
class Derived(Base1, Base2):
def __init__(self):
def printStrs(self):
print(self.str1, self.str2)
ob = Derived()
ob.printStrs()
Output:
Base1
Base2
Derived
Good Morning
Multilevel inheritance: When we have a child and grandchild relationship. This means that a
child class will inherit from its parent class, which in turn is inheriting from its parent class.
class Base(object):
# Constructor
def __init__(self, name):
self.name = name
# To get name
def getName(self):
return self.name
# Constructor
def __init__(self, name, age):
Base.__init__(self, name)
self.age = age
# To get name
def getAge(self):
return self.age
# Inherited or Sub class (Note Person in bracket)
class GrandChild(Child):
# Constructor
def __init__(self, name, age, address):
Child.__init__(self, name, age)
self.address = address
# To get address
def getAddress(self):
return self.address
# Driver code
g = GrandChild("Sandeep", 23, "Noida")
print(g.getName(), g.getAge(), g.getAddress())
Output:
Sandeep 23 Noida
Hierarchical inheritance More than one derived class can be created from a single base.
Hybrid inheritance: This form combines more than one form of inheritance. Basically, it is a
blend of more than one type of inheritance.
class C(object):
def __init__(self):
self.c = 21
class D(C):
def __init__(self):
self.e = 84
C.__init__(self)
object1 = D()
Output :
Here we can see that when we tried to print the variable ‘c’, its value 21 is printed on the console.
Whereas when we tried to print ‘d’, it generated the error. This is because the variable ‘d’ is made
private by using the underscores. It is not available to the child class ‘D’ and hence the error.
21
File "/home/993bb61c3e76cda5bb67bd9ea05956a1.py", line 16, in
print (object1.d)
AttributeError: type object 'D' has no attribute 'd'
Polymorphism
Polymorphism means having many forms. In programming, polymorphism means the same function
name (but different signatures) being used for different types. The key difference is the data types
and number of arguments used in function.
Output
5
3
# Driver code
print(add(2, 3))
print(add(2, 3, 4))
Output
5
9
class India():
def capital(self):
print("New Delhi is the capital of India.")
def language(self):
print("Hindi is the most widely spoken language of India.")
def type(self):
print("India is a developing country.")
class USA():
def capital(self):
print("Washington, D.C. is the capital of USA.")
def language(self):
print("English is the primary language of USA.")
def type(self):
print("USA is a developed country.")
obj_ind = India()
obj_usa = USA()
for country in (obj_ind, obj_usa):
country.capital()
country.language()
country.type()
Output
New Delhi is the capital of India.
Hindi is the most widely spoken language of India.
India is a developing country.
Washington, D.C. is the capital of USA.
English is the primary language of USA.
USA is a developed country.
In Python, Polymorphism lets us define methods in the child class that have the same name as the
methods in the parent class. In inheritance, the child class inherits the methods from the parent class.
However, it is possible to modify a method in a child class that it has inherited from the parent class.
This is particularly useful in cases where the method inherited from the parent class doesn’t quite fit
the child class. In such cases, we re-implement the method in the child class. This process of re-
implementing a method in the child class is known as Method Overriding.
class Bird:
def intro(self):
print("There are many types of birds.")
def flight(self):
print("Most of the birds can fly but some cannot.")
class sparrow(Bird):
def flight(self):
print("Sparrows can fly.")
class ostrich(Bird):
def flight(self):
print("Ostriches cannot fly.")
obj_bird = Bird()
obj_spr = sparrow()
obj_ost = ostrich()
obj_bird.intro()
obj_bird.flight()
obj_spr.intro()
obj_spr.flight()
obj_ost.intro()
obj_ost.flight()
Output
There are many types of birds.
Most of the birds can fly but some cannot.
There are many types of birds.
Sparrows can fly.
There are many types of birds.
Ostriches cannot fly.
def func(obj):
obj.capital()
obj.language()
obj.type()
obj_ind = India()
obj_usa = USA()
func(obj_ind)
func(obj_usa)
class India():
def capital(self):
print("New Delhi is the capital of India.")
def language(self):
print("Hindi is the most widely spoken language of India.")
def type(self):
print("India is a developing country.")
class USA():
def capital(self):
print("Washington, D.C. is the capital of USA.")
def language(self):
print("English is the primary language of USA.")
def type(self):
print("USA is a developed country.")
def func(obj):
obj.capital()
obj.language()
obj.type()
obj_ind = India()
obj_usa = USA()
func(obj_ind)
func(obj_usa)
Output
New Delhi is the capital of India.
Hindi is the most widely spoken language of India.
India is a developing country.
Washington, D.C. is the capital of USA.
English is the primary language of USA.
USA is a developed country.
class Animal:
def speak(self):
raise NotImplementedError("Subclass must implement this method")
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
Output
Woof!
Meow!