0% found this document useful (0 votes)
30 views13 pages

Understanding Object-Oriented Programming

Module 5 covers object-oriented programming concepts including mutability of objects, identity vs equality, copying objects, inheritance, pure functions, modifiers, generalization, operator overloading, polymorphism, and exception handling. It explains how to manage object states, compare objects, and utilize inheritance for code reuse. Additionally, it details exception handling techniques, including built-in exceptions, catching multiple exceptions, raising exceptions, and creating custom exception classes.

Uploaded by

vikasm0427
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views13 pages

Understanding Object-Oriented Programming

Module 5 covers object-oriented programming concepts including mutability of objects, identity vs equality, copying objects, inheritance, pure functions, modifiers, generalization, operator overloading, polymorphism, and exception handling. It explains how to manage object states, compare objects, and utilize inheritance for code reuse. Additionally, it details exception handling techniques, including built-in exceptions, catching multiple exceptions, raising exceptions, and creating custom exception classes.

Uploaded by

vikasm0427
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Module-5

Object oriented programming

1. Objects Are Mutable

The dot operator composes. The expression [Link].x means, “Go to


the object that box refers to and select its attribute named corner, then go
to that object and select its attribute named x”.
The figure shows the state of this object:

We can change the state of an object by making an assignment to one of


its attributes. For example, to grow the size of a rectangle without
changing its position, we could modify the values of width and height.

[Link] += 50
[Link] += 100
Of course, we’d probably like to provide a method to encapsulate this
inside the class. We will also provide another method to move the position
of the rectangle elsewhere:
Let us try this:

2. Sameness: Identity vs Equality


The meaning of the word “same” seems perfectly clear until we give it
some thought, and then we realize there is more
to it than we initially expected.
For example, if we say, “Alice and Bob have the same car”, we mean that
her car and his are the same make and model,
but that they are two different cars. If we say, “Alice and Bob have the
same mother”, we mean that her mother and
his are the same person.
When we talk about objects, there is a similar ambiguity. For example, if
two Points are the same, does that mean
they contain the same data (coordinates) or that they are actually the
same object?
We’ve already seen the is operator in the chapter on lists, where we
talked about aliases: it allows us to find out if
two references refer to the same object:
>>> p1 = Point(3, 4)
>>> p2 = Point(3, 4)
>>> p1 is p2
False
Even though p1 and p2 contain the same coordinates, they are not the
same object. If we assign p1 to p3, then the two variables are aliases of
the same object:

>>> p3 = p1
>>> p1 is p3
True
This type of equality is called shallow equality because it compares only
the references, not the contents of the objects.
To compare the contents of the objects — deep equality — we can write
a function called same_coordinates:
1 def same_coordinates(p1, p2):
2 return (p1.x == p2.x) and (p1.y == p2.y)

Now if we create two different objects that contain the same data, we
can use same_point to find out if they represent points with the same
coordinates.

>>> p1 = Point(3, 4)
>>> p2 = Point(3, 4)
>>> same_coordinates(p1, p2)
True

Of course, if the two variables refer to the same object, they have both
shallow and deep equality.

Beware of == “When I use a word,” Humpty Dumpty said, in a rather


scornful tone, “it means just what I choose it to mean —
neither more nor less.” Alice in Wonderland Python has a powerful
feature that allows a designer of a class to decide what an operation like
== or < should mean. (We’ve just shown how we can control how our
own objects are converted to strings, so we’ve already made a start!)
We’ll cover more detail later. But sometimes the implementors will
attach shallow equality semantics, and sometimes deep equality, as
shown in this little experiment:

So we conclude that even though the two lists (or tuples, etc.) are distinct
objects with different memory addresses,
for lists the == operator tests for deep equality, while in the case of points
it makes a shallow test.
3. Copying Objects
Aliasing can make a program difficult to read because changes made in
one place might have unexpected effects in another place. It is hard to
keep track of all the variables that might refer to a given object.

Copying an object is often an alternative to aliasing. The copy module


contains a function called copy that can duplicate any object:

>>> import copy


>>> p1 = Point(3, 4)
>>> p2 = [Link](p1)
>>> p1 is p2
False
>>> same_coordinates(p1, p2)
True

Once we import the copy module, we can use the copy function to make a
new Point. p1 and p2 are not the same point, but they contain the same
data.
To copy a simple object like a Point, which doesn’t contain any embedded
objects, copy is sufficient. This is called shallow copying.

For something like a Rectangle, which contains a reference to a Point,


copy doesn’t do quite the right thing. It copies the reference to the Point
object, so both the old Rectangle and the new one refer to a single Point.
If we create a box, b1, in the usual way and then make a copy, b2, using
copy, the resulting state diagram looks like this:

This is almost certainly not what we want. In this case, invoking grow on
one of the Rectangle objects would not affect the other, but invoking
move on either would affect both! This behavior is confusing and error-
prone. The shallow copy has created an alias to the Point that represents
the corner.
Fortunately, the copy module contains a function named deepcopy that
copies not only the object but also any embedded objects. It won’t be
surprising to learn that this operation is called a deep copy.
>>> b2 = [Link](b1)
Now b1 and b2 are completely separate objects.

Inheritance
The language feature most often associated with object-oriented
programming is inheritance. Inheritance is the ability to define a new
class that is a modified version of an existing class.

The primary advantage of this feature is that you can add new methods
to a class without modifying the existing class. It is called inheritance
because the new class inherits all of the methods of the existing class.
Extending this metaphor, the existing class is sometimes called the
parent class. The new class may be called the child class or sometimes
subclass.

Inheritance allows a class (subclass) to inherit attributes and methods


from another class (superclass). It promotes code reuse and establishes
a relationship between classes.

1. Pure Functions

A pure function is a function that:


 Always returns the same output for the same input.
 Has no side effects—it doesn’t alter any external state (like
modifying a global variable, printing to the console, or changing a
file).

Example

We can instantiate a new MyTime object:


tim1 = MyTime(11, 59, 30)
In the next few sections, we’ll write two versions of a function called
add_time, which calculates the sum of two MyTime objects. They will
demonstrate two kinds of functions:pure functions and modifiers.
The following is a rough version of add_time:

The function creates a new MyTime object and returns a reference to the new object.
This is called a pure function because it does not modify any of the objects
passed to it as parameters and it has no side effects, such as updating
global variables, displaying a value, or getting user input.
Here is an example of how to use this function. We’ll create two MyTime
objects: current_time, which contains the current time; and bread_time,
which contains the amount of time it takes for a breadmaker to make
bread. Then we’ll use add_time to figure out when the bread will be done.
>>> current_time = MyTime(9, 14, 30)
>>> bread_time = MyTime(3, 35, 0)
>>> done_time = add_time(current_time, bread_time)
>>> print(done_time)
[Link]

2. Modifiers

There are times when it is useful for a function to modify one or more of
the objects it gets as parameters. Usually, the caller keeps a reference to
the objects it passes, so any changes the function makes are visible to the
caller. Functions hat work this way are called modifiers.
increment, which adds a given number of seconds to a MyTime object,
would be written most naturally as a modifier. A rough draft of the
function looks like this:
The first line performs the basic operation; the remainder deals with the
special cases we saw before.
Is this function correct? What happens if the parameter seconds is much
greater than sixty? In that case, it is not enough to carry once; we have to
keep doing it until seconds is less than sixty. One solution is to replace the
if statements with while statements:

This function is now correct when seconds is not negative, and when
hours does not exceed 23, but it is not a particularly good solution.

3. Generalization
Generalization is the process of extracting shared characteristics from
two or more classes and combining them into a generalized superclass.
It’s a top-down approach used to simplify and organize code by
identifying commonalities.

Purpose of Generalization
 Code Reusability: Shared methods and attributes are written
once in the superclass.
 Simplified Design: Reduces duplication and improves
maintainability.
 Logical Hierarchy: Helps create a structured class hierarchy.

Example
Let’s say you have two classes: Car and Bike.

class Car:
def start_engine(self):
print("Car engine started")

class Bike:
def start_engine(self):
print("Bike engine started")

Both have a start_engine() method. You can generalize them into a


Vehicle class:
class Vehicle:
def start_engine(self):
print("Engine started")

class Car(Vehicle):
pass

class Bike(Vehicle):
Pass

Now Car and Bike inherit from Vehicle, and you avoid repeating code

4. Operator Overloading
Operator Overloading allows you to redefine the behavior of built-in
operators (like +, -, *, ==, etc.) for user-defined types (i.e., classes).
Instead of using a method name, you use familiar symbols to perform
operations on objects.

Example
Let’s say you have a Point class:

class Point:
def init (self, x, y):
self.x = x
self.y = y

def add (self, other):


return Point(self.x + other.x, self.y +
other.y)

def str (self):


return f"({self.x}, {self.y})"
Now you can do:

p1 = Point(2, 3)
p2 = Point(4, 5)
print(p1 + p2) # Output: (6, 8)

Here, + is overloaded to add two Point objects.

5. Polymorphism

Polymorphism allows objects of different classes to be treated as objects


of a common superclass. It enables the same method to behave
differently based on the object it is called on.

Example of Polymorphism:
class Bird:
def sound(self):
print("Some bird sound")

class Parrot(Bird):
def sound(self):
print("Squawk")

class Sparrow(Bird):
def sound(self):
print("Chirp")

# Polymorphism in action
def make_sound(bird):
[Link]()
parrot = Parrot()
sparrow = Sparrow()

make_sound(parrot) # Output: Squawk


make_sound(sparrow) # Output: Chirp

Exception Handling
Objective

The primary goal of this module is to give you a comprehensive


understanding of how to handle exceptions in , how to use built-in
exceptions, and how to create your own custom exception classes.
Proper exception handling is crucial in writing robust and error-free code.

1. What is an Exception?

An exception is an event that occurs during the execution of a program


that disrupts the normal flow of the program's instructions. In ,
exceptions are objects that represent errors that occur at runtime.
When an exception occurs, it is said to be "raised," and if not handled, it
will terminate the program.

2. Common Built-in Exceptions

provides several built-in exceptions, including:

 ZeroDivisionError: Raised when you divide by zero.


 IndexError: Raised when you try to access an index that is out
of range.
 KeyError: Raised when a dictionary key is not found.
 TypeError: Raised when an operation is applied to an object of
inappropriate type.
 ValueError: Raised when a function receives an argument of
the correct type but inappropriate value.

1. Catching Exception Handling

To handle exceptions in , you can use the try, except, else, and
finally blocks.
 try block: Code that might raise an exception is placed in the
try block.
 except block: Code that handles the exception is placed in the
except block.
 else block: Code that runs if no exception occurs is placed in the
else block.
 finally block: Code that runs no matter what, whether an
exception occurs or not, is placed in the finally block.

Example: Handling a Division by Zero Error


try:
numerator = int(input("Enter the numerator: "))
denominator = int(input("Enter the denominator: "))
result = numerator / denominator
except ZeroDivisionError:
print("Error: Division by zero is not allowed.")
else:
print(f"Result: {result}")
finally:
print("This will always execute.")

Explanation:

 If the denominator is zero, a ZeroDivisionError is raised,


and the except block is executed.
 If no exception occurs, the else block is executed.
 The finally block is executed regardless of whether an
exception occurs or not.

2. Catching Multiple Exceptions

You can catch multiple exceptions by specifying them as a tuple in the


except block.

Example: Handling Multiple Exceptions


try:
value = int(input("Enter an integer: "))
result = 10 / value
except (ValueError, ZeroDivisionError) as e:
print(f"Error occurred: {e}")
Explanation:

 If the user inputs a non-integer value, a ValueError is raised.


 If the user inputs zero, a ZeroDivisionError is raised.
 The as e syntax allows you to capture the exception object and
print the error message.

3. Raising Exceptions

You can manually raise exceptions using the raise keyword.

Example: Manually Raising an Exception

def check_age(age):
if age < 18:
raise ValueError("Age must be 18 or older.")
return "Access granted."

try:
print(check_age(16))
except ValueError as e:
print(e)

Explanation:

 The check_age function raises a ValueError if the age is less


than 18.
 The exception is caught in the except block and the error
message is printed.

4. Creating Custom Exception Classes

You can create your own exception classes by inheriting from the
Exception class or any other built-in exception class.

Example: Custom Exception Class


class CustomError(Exception):
def init (self, message):
[Link] = message
def check_value(value):
if value < 0:
raise CustomError("Negative values are not
allowed.")
return value

try:
print(check_value(-5))
except CustomError as e:
print(e)

Explanation:

 CustomError is a custom exception class that inherits from the


Exception class.
 The check_value function raises a CustomError if the value
is negative.
 The exception is caught in the except block and the custom
error message is printed.

You might also like