Unit III Python
Unit III Python
STANDALONE PROGRAMS
1. Self-contained: They include all the logic and components necessary for execution.
2. Executable directly: They can be run using python script_name.py.
3. Conditional execution: They often use the if __name__ == "__main__": block to ensure
the program's main functionality executes only when run directly.
In Python:
When a script is executed directly, Python sets the special variable __name__ to
"__main__".
If the script is imported as a module in another script, __name__ is set to the script's
name instead.
This makes it possible to differentiate between executing a script directly and importing it.
1
# Define functions
def greet_user(name):
return f"Hello, {name}!"
# Main function
def main():
if len([Link]) > 1:
name = [Link][1]
print(greet_user(name))
else:
print("Usage: python script_name.py <name>")
# Conditional execution
if __name__ == "__main__":
main()
def main():
print("Simple Calculator")
print("1. Add")
print("2. Subtract")
print("3. Multiply")
print("4. Divide")
2
if choice in ('1', '2', '3', '4'):
num1 = float(input("Enter first number: "))
num2 = float(input("Enter second number: "))
if choice == '1':
print(f"Result: {add(num1, num2)}")
elif choice == '2':
print(f"Result: {subtract(num1, num2)}")
elif choice == '3':
print(f"Result: {multiply(num1, num2)}")
elif choice == '4':
print(f"Result: {divide(num1, num2)}")
else:
print("Invalid input")
if __name__ == "__main__":
main()
1. Save the script: Save the program in a .py file, e.g., [Link].
2. Execute the script:
o Open a terminal or command prompt.
o Navigate to the directory containing the script.
o Run the script with python [Link].
3
Accessing Command-Line Arguments in Python
Python provides the [Link] list from the sys module to handle command-line arguments. Here's
how it works:
1. [Link]:
o A list containing the command-line arguments passed to the script.
o The first element, [Link][0], is the name of the script itself.
o Subsequent elements ([Link][1], [Link][2], etc.) are the additional arguments
passed.
Example of [Link]
Script: [Link]
import sys
def main():
print(f"Script name: {[Link][0]}")
if len([Link]) > 1:
print("Arguments passed:")
for i, arg in enumerate([Link][1:], start=1):
print(f"Argument {i}: {arg}")
else:
print("No arguments passed.")
if __name__ == "__main__":
main()
The argparse module offers a more robust way to handle arguments. It supports features like:
4
Type-checking.
def main():
parser = [Link](description="A simple script to demonstrate argparse.")
parser.add_argument("name", type=str, help="Your name")
parser.add_argument("--age", type=int, help="Your age (optional)", required=False)
args = parser.parse_args()
print(f"Hello, {[Link]}!")
if [Link]:
print(f"You are {[Link]} years old.")
if __name__ == "__main__":
main()
5
MODULES AND THE IMPORT STATEMENT
Modules in Python are files containing Python code (functions, classes, or variables) that
can be reused across different programs. Modules help organize code logically, improve
reusability, and enhance maintainability.
What is a Module?
A module is simply a Python file with a .py extension that can define functions, classes,
and variables.
Modules can also include runnable code, but their primary purpose is to make reusable
components available to other programs.
PI = 3.14159
Importing a Module
You can use the import statement to include a module in your script and access its components.
Syntax:
import module_name
Example:
import my_module
You can import specific functions, classes, or variables from a module using the from ... import
... syntax.
Example:
from my_module import greet, PI
print(greet("Bob"))
6
print(f"Value of PI: {PI}")
You can assign an alias to a module or its components using the as keyword for brevity or
clarity.
Example:
import my_module as mm
print([Link]("Charlie"))
print(f"Value of PI: {[Link]}")
Types of Modules
1. Built-in Modules:
o Python provides many pre-installed modules like math, os, sys, etc.
o Example:
import math
import random
3. Third-Party Modules:
o Installed via package managers like pip (e.g., numpy, pandas).
o Example:
import numpy as np
4. Custom Modules:
o User-created modules like my_module.
7
The __name__ Variable and Module Execution
Modules have a special variable called __name__. When a module is run directly, __name__ is
set to "__main__". If the module is imported, __name__ is set to the module's name.
Example:
# my_module.py
def greet(name):
return f"Hello, {name}!"
if __name__ == "__main__":
print("Running my_module directly.")
print(greet("Direct User"))
Behavior:
$ python my_module.py
Output:
2. When imported:
import my_module
print(my_module.greet("Imported User"))
Output:
You can import all components of a module using from module_name import *.
Example:
from my_module import *
print(greet("Dave"))
8
print(PI)
import sys
print([Link])
The Python Standard Library is a collection of pre-installed modules and packages that
provide standardized solutions to common programming tasks. These modules allow developers
to perform tasks like file handling, string manipulation, mathematical computations, data
serialization, networking, and more without installing additional packages.
1. Comprehensive: Offers modules for various domains like mathematics, file I/O, system
operations, and more.
2. Cross-Platform: Works seamlessly across operating systems (e.g., Windows, macOS,
Linux).
3. Pre-installed: Available with every Python distribution.
4. Time-Saving: Reduces the need to write custom code for common tasks.
9
1. String and Text Handling
Example:
import re
Example:
import math
Example:
import os
Example:
import json
10
5. Date and Time
now = [Link]()
print([Link]("%Y-%m-%d %H:%M:%S")) # Outputs current date and time
Example:
python
Copy code
import [Link]
response = [Link]('[Link]
print([Link]) # Output: 200
Example:
import sys
8. Concurrent Programming
Example:
import threading
def print_numbers():
for i in range(5):
print(i)
11
thread = [Link](target=print_numbers)
[Link]()
Example:
python
Copy code
import unittest
class TestSum([Link]):
def test_addition(self):
[Link](1 + 1, 2)
[Link]()
Example:
import zipfile
You can view a list of available modules in your Python installation by running:
help('modules')
12
Popular Modules at a Glance
Module Purpose
os Interact with the operating system.
In Python, defining a class is done using the class keyword. A class is a blueprint for
creating objects, and it can include attributes (data) and methods (functions) that operate on that
data.
1. Define the Class: Use the class keyword followed by the class name and a colon (:).
2. Initialize with __init__ Method: The __init__ method is a special method called the
constructor. It initializes the object's attributes when an instance of the class is created.
3. Create Instance Variables: These are variables specific to each object created from the
class.
4. Define Methods: Methods are functions that belong to a class. They can operate on the
data (attributes) within the class.
Let’s create a simple Car class with attributes like make and year, and methods to display
information and update the year.
13
def __init__(self, make, year):
[Link] = make # Instance variable for car make
[Link] = year # Instance variable for car year
OUTPUT :
Encapsulation: Classes help to bundle data and methods together, making it easier to
organize code.
Reusability: Once a class is defined, you can create multiple objects (instances) from it.
Modularity: Methods within a class can be easily modified without affecting other parts
of the code.
This is a basic example, but classes in Python can be much more complex and can include
features like inheritance, polymorphism, and encapsulation, allowing for advanced and organized
programming.
INHERITANCE IN PYTHON
In Python, inheritance is a feature that allows a class (called the child class or subclass) to
inherit attributes and methods from another class (called the parent class or superclass). This
helps in reusing code and establishing relationships between classes, like an "is-a" relationship.
14
Types of Inheritance in Python
1. Single Inheritance
class Animal:
def speak(self):
print("Animal speaks")
Output:
Animal speaks
Dog barks
2. Multiple Inheritance
In multiple inheritance, a child class inherits from more than one parent class.
class Father:
def show_father(self):
print("This is the Father class")
class Mother:
def show_mother(self):
print("This is the Mother class")
class Child(Father, Mother): # Child class inherits from both Father and Mother
def show_child(self):
print("This is the Child class")
15
# Creating an object of Child class
child = Child()
child.show_father()
child.show_mother()
child.show_child()
Output:
3. Multilevel Inheritance
In multilevel inheritance, a class inherits from another child class, forming a chain.
class Animal:
def eat(self):
print("Animal eats")
Output:
Animal eats
Mammal walks
Dog barks
4. Hierarchical Inheritance
In hierarchical inheritance, multiple child classes inherit from the same parent class.
16
class Animal:
def sound(self):
print("Animal makes a sound")
Output:
5. Hybrid Inheritance
Hybrid inheritance is a combination of two or more types of inheritance. Here’s a basic example
that combines multiple and multilevel inheritance.
class Animal:
def eat(self):
print("Animal eats")
17
print("Bird flies")
class Bat(Mammal, Bird): # Bat inherits from both Mammal and Bird
def hang(self):
print("Bat hangs upside down")
Output:
Animal eats
Mammal walks
Bird flies
Bat hangs upside down
METHOD OVERRIDING
Method overriding in Python is a concept where a method in a child class has the same
name as a method in the parent class. The method in the child class "overrides" the method in the
parent class. This allows the child class to provide a specific implementation for that method,
which will be used when called on an object of the child class.
Example Code
class Animal:
# Parent class method
def sound(self):
print("Animals make different sounds")
class Dog(Animal):
18
# Overriding the sound method in the Dog class
def sound(self):
print("Dog barks")
Explanation
Expected Output
Summary
This is useful in cases where subclasses need to behave differently from their parent classes
while sharing the same method names.
19
In Python, adding a method to a class allows you to define a specific action or behavior
that objects of that class can perform. A method is essentially a function defined within a class,
with self as its first parameter, which refers to the instance of the class.
Let's define a Car class with a method to display information about the car. Then, we’ll add
another method to start the car.
class Car:
# Constructor to initialize the Car object with make and model
def __init__(self, make, model):
[Link] = make
[Link] = model
Explanation
1. Constructor (__init__):
o The __init__ method initializes the make and model attributes when an object of
the Car class is created.
2. display_info Method:
o This method prints out the details of the car (make and model).
o display_info is called by using the my_car.display_info() syntax on the my_car
object.
3. start Method (Newly Added):
o We added a new method, start, which prints a message indicating that the car is
starting.
o This method can also be called using the my_car.start() syntax.
20
Expected Output
Summary
Adding a Method: Adding a method involves defining a new function inside the class,
with self as the first parameter.
Accessing Methods: Once the method is added, it can be called using the syntax
object_name.method_name().
Adding methods like this allows you to define actions or behaviors that objects of the class
can perform, making the class more functional and versatile.
In Python, the super() function is used to call methods from a parent (or superclass) in a
child (or subclass). This is especially useful when you want to add functionality in the child class
while still using some logic or behavior from the parent class.
Using super() helps in maintaining code reusability and prevents duplicate code.
In this example, the Animal class has a constructor that initializes the name attribute, and
the Dog class extends it by adding the breed attribute. The super() function allows us to call the
Animal constructor from the Dog constructor.
class Animal:
# Constructor for Animal class
def __init__(self, name):
[Link] = name
class Dog(Animal):
# Constructor for Dog class
def __init__(self, name, breed):
# Using super() to call the parent (Animal) class's constructor
super().__init__(name)
[Link] = breed
21
def display_breed(self):
print(f"{[Link]} is a {[Link]}")
Explanation
Expected Output
Summary
super(): This function is used to access methods from the parent class.
Code Reusability: By using super(), the child class can reuse methods from the parent
class without duplicating code.
Constructor Chaining: super().__init__(...) allows the child class to initialize attributes
from the parent class.
This approach is helpful in cases where you want to extend the functionality of a method but also
want to retain some functionality from the parent class.
In Python, getter and setter methods are used to retrieve and modify the values of an
object’s attributes, especially if we want to add some control or validation on attribute access.
These methods help us control access to private attributes and manage data safely.
22
What Are Getter and Setter Methods?
1. Getter Method:
o A method used to retrieve the value of an attribute.
o It is typically used to access a private attribute.
2. Setter Method:
o A method used to set or modify the value of an attribute.
o It can include validation to ensure the attribute is set to a valid value.
Let’s create a Person class with an attribute age. We’ll use a getter to retrieve the age and
a setter to validate that age is not negative.
class Person:
def __init__(self, name, age):
[Link] = name
self._age = age # Private attribute for age
def display(self):
print(f"{[Link]} is {self.get_age()} years old.")
23
# Modifying age using the setter
person1.set_age(35)
print(person1.get_age()) # Output: 35
Output
Summary
In Python, name mangling is a technique used to make an attribute private and avoid
accidental or unintended access or modification. It is mainly used to prevent subclass overrides.
24
Name mangling is achieved by prefixing an attribute name with two underscores (__). This
makes it harder to access the attribute from outside the class directly, but it’s still accessible
through a specific name-mangled format.
When an attribute is prefixed with __, Python changes its name internally to include the
class name as a prefix. This modified name prevents accidental access and overrides, but it’s still
accessible if needed using a special syntax.
For example, if you have a class MyClass with a private attribute __data, Python
internally changes the attribute name to _MyClass__data. This is done to avoid conflicts in
subclasses and to signal that it’s a private attribute.
Let’s look at an example where we define a class with a private attribute and demonstrate
how name mangling affects access.
class MyClass:
def __init__(self, value):
self.__data = value # Private attribute with name mangling
def get_data(self):
return self.__data
25
1. Private Attribute: __data is a private attribute in MyClass. The double underscore
before data triggers name mangling.
2. Accessing the Attribute Directly:
o print(obj.__data) raises an AttributeError because __data has been renamed
internally to _MyClass__data.
3. Accessing with Name Mangling:
o To access the attribute directly, we can use obj._MyClass__data. This accesses
the actual underlying name of the attribute, which is _MyClass__data.
class Parent:
def __init__(self):
self.__value = 42 # Private attribute in the parent class
class Child(Parent):
def __init__(self):
super().__init__()
self.__value = 99 # Attempt to define a similar private attribute in the child class
1. Parent Class:
26
o Parent has a private attribute __value, which is name-mangled to _Parent__value.
2. Child Class:
o Child also defines an attribute __value, which is name-mangled to _Child__value.
o This avoids conflict with the parent class attribute since both have been name-
mangled to unique names based on their classes.
3. Accessing Attributes:
o Using child._Parent__value retrieves the __value from the Parent class.
o Using child._Child__value retrieves the __value from the Child class.
Summary
Name Mangling is a technique where Python renames an attribute prefixed with double
underscores by adding the class name as a prefix.
Purpose: To signal the attribute is private and avoid name conflicts in subclasses.
Access: Though it makes direct access less straightforward, the attribute is still accessible
using the name-mangled version (_ClassName__attribute).
In Python, getattr and setattr are built-in functions that allow dynamic access to object
attributes, including properties. Properties in Python are a way to manage access to an attribute,
allowing additional logic to be executed when the attribute is retrieved, set, or deleted.
1. getattr:
o Retrieves the value of an attribute of an object.
o Syntax: getattr(object, attribute_name[, default])
o If the attribute doesn't exist, a default value can be provided to avoid raising an
AttributeError.
2. setattr:
o Sets the value of an attribute of an object.
o Syntax: setattr(object, attribute_name, value)
o If the attribute doesn't exist, it is created dynamically.
class Person:
def __init__(self, name, age):
[Link] = name
[Link] = age
# Create an instance
person = Person("Alice", 25)
27
print(getattr(person, "name")) # Output: Alice
print(getattr(person, "age")) # Output: 25
Properties provide controlled access to an object's attributes by defining methods for getting,
setting, or deleting the attribute. They use the @property decorator.
Defining a Property
@property
def radius(self):
return self._radius
@[Link]
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
import math
return [Link] * (self._radius ** 2)
# Create an instance
circle = Circle(10)
28
# Set radius using setattr (setter is called automatically)
setattr(circle, "radius", 15)
print([Link]) # Output: 15
Key Points
1. Dynamic Access:
o getattr and setattr allow dynamic interaction with attributes, even for those
defined with properties.
2. Validation via Properties:
o Properties ensure that attribute access or updates include logic, such as validation
or computed values.
3. Error Handling:
o Use getattr with a default argument to avoid errors when accessing non-existent
attributes.
o setattr will dynamically create attributes if they don’t exist, which should be used
cautiously.
In Python, methods are functions defined within a class that operate on instances of the
class or the class itself. Methods can be categorized based on their behavior and the type of
access they have to the class or its instances.
29
1. Instance Methods
2. Class Methods
3. Static Methods
1. Instance Methods
Definition: Operate on instances of the class and have access to instance attributes and
methods. They are the most common type of method in Python.
Access: Requires the instance (self) as the first parameter.
Usage: Used for working with the object’s data.
Example:
class Circle:
def __init__(self, radius):
[Link] = radius
# Create an instance
circle = Circle(5)
print(circle.calculate_area()) # Output: 78.53981633974483
2. Class Methods
Definition: Operate on the class itself rather than instances. These methods can modify
class-level attributes but cannot access instance-specific data directly.
Access: Requires the class (cls) as the first parameter.
Declaration: Defined using the @classmethod decorator.
Usage: Useful for factory methods or modifying class-level data.
Example:
class Circle:
count = 0 # Class-level attribute
@classmethod
def get_count(cls): # Class method
return [Link]
# Create instances
30
circle1 = Circle(5)
circle2 = Circle(10)
print(Circle.get_count()) # Output: 2
3. Static Methods
Definition: Do not operate on the instance or class directly. They work like regular
functions but are included in the class for logical grouping.
Access: Do not require self or cls.
Declaration: Defined using the @staticmethod decorator.
Usage: Used for utility methods that perform operations independent of the instance or
class.
Example:
class Circle:
@staticmethod
def calculate_circumference(radius): # Static method
import math
return 2 * [Link] * radius
Comparison Table
31
o When you need to operate on class-level data or create instances in a specific way
(factory methods).
o Example: Tracking the number of instances created.
Use Static Methods:
o When you need a utility method that does not depend on the class or instance.
o Example: Validating input values or performing standalone calculations.
Mixed Example
class Account:
bank_name = "XYZ Bank" # Class attribute
@classmethod
def get_bank_name(cls): # Class method
return cls.bank_name
@staticmethod
def validate_amount(amount): # Static method
return amount > 0
# Usage
account = Account("Alice", 1000)
# Instance method
[Link](500)
print([Link]) # Output: 1500
# Class method
print(Account.get_bank_name()) # Output: XYZ Bank
# Static method
print(Account.validate_amount(100)) # Output: True
32
DUCK TYPING IN PYTHON
Duck typing is a concept in Python where the type or class of an object is determined by
its behavior (what it can do) rather than its explicit type or class. The name comes from the
phrase:
"If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck."
This philosophy emphasizes the behavior of an object over its specific type, enabling
more flexible and dynamic code.
1. Behavior-Oriented:
o If an object implements the required behavior or methods, it can be used in place
of another object, regardless of its type.
2. No Explicit Type Checking:
o Python does not require you to declare variable types or use type-checking for
method arguments. It trusts the object to provide the required behavior.
3. Dynamic Typing:
o Python allows objects to be used interchangeably as long as they support the
required operations.
Let’s consider a function that expects an object to have a method named quack:
class Duck:
def quack(self):
print("Quack!")
class Person:
def quack(self):
print("I'm pretending to be a duck!")
def make_it_quack(duck_like):
duck_like.quack()
Here:
33
The make_it_quack function does not care if the object is a Duck or Person. It only cares
that the object has a quack method.
1. Flexibility:
o Encourages writing flexible and reusable code by focusing on behavior rather
than type.
2. Simplicity:
o Avoids complex inheritance or type checks, simplifying the code.
3. Extensibility:
o Easy to add new classes that work with existing functions as long as they conform
to the expected behavior.
1. Runtime Errors:
o Errors due to missing methods or unsupported operations are only caught at
runtime, not during compilation.
o Example:
python
Copy code
class Dog:
def bark(self):
print("Woof!")
2. Lack of Explicitness:
o It may be unclear what type of object a function expects, making code harder to
understand.
34
python
Copy code
def make_it_quack(duck_like):
if hasattr(duck_like, 'quack'):
duck_like.quack()
else:
print("This object cannot quack!")
Use Python type hints to provide a hint about the expected behavior (e.g., using
[Link]).
python
Copy code
from typing import Protocol
class Quackable(Protocol):
def quack(self) -> None:
...
The open function in Python returns file-like objects, but you can use custom objects with
the same methods (read, write, etc.) without worrying about their exact type.
class FileMock:
def write(self, content):
print(f"Mock write: {content}")
def save_to_file(file_obj):
file_obj.write("Hello, Duck Typing!")
Output:
35
Mock write: Hello, Duck Typing!
SPECIAL METHODS
For example:
__init__(self, ...)
Example:
class Person:
def __init__(self, name, age):
[Link] = name
[Link] = age
36
print([Link]) # Output: Alice
__new__(cls, ...)
2. String Representation
__str__(self)
__repr__(self)
Example:
class Person:
def __init__(self, name, age):
[Link] = name
[Link] = age
def __str__(self):
return f"{[Link]}, {[Link]} years old"
def __repr__(self):
return f"Person(name='{[Link]}', age={[Link]})"
Arithmetic Operations
37
Example:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
p1 = Point(2, 3)
p2 = Point(4, 5)
p3 = p1 + p2
print(p3) # Output: (6, 8)
Comparison Operations
Example:
class Person:
def __init__(self, name, age):
[Link] = name
[Link] = age
4. Container Emulation
__getitem__(self, key)
38
__setitem__(self, key, value)
Example:
class MyList:
def __init__(self):
[Link] = []
def __str__(self):
return str([Link])
lst = MyList()
[Link] = [1, 2, 3]
print(lst[1]) # Output: 2
lst[1] = 10
print(lst) # Output: [1, 10, 3]
5. Callable Objects
Example:
class Adder:
def __init__(self, increment):
[Link] = increment
add5 = Adder(5)
print(add5(10)) # Output: 15
6. Context Management
39
__enter__(self) and __exit__(self, exc_type, exc_value, traceback)
Example:
class FileManager:
def __init__(self, filename, mode):
[Link] = open(filename, mode)
def __enter__(self):
return [Link]
Example of __format__:
class Person:
def __init__(self, name, age):
[Link] = name
[Link] = age
COMPOSITION IN PYTHON
40
COMPOSITION in PYTHON
Example of Composition
Let’s demonstrate composition with an example of a Car and its components, Engine and Tires.
Example:
class Engine:
def __init__(self, horsepower):
[Link] = horsepower
def start(self):
return "Engine started."
class Tires:
def __init__(self, brand):
[Link] = brand
def inflate(self):
return f"{[Link]} tires inflated."
class Car:
def __init__(self, brand, engine, tires):
41
[Link] = brand
[Link] = engine # Composition: Car has an Engine
[Link] = tires # Composition: Car has Tires
def drive(self):
return f"{[Link]} is driving. {[Link]()} {[Link]()}"
# Create components
engine = Engine(300)
tires = Tires("Michelin")
Output:
1. Independent Components:
o Engine and Tires are independent classes, not subclasses of Car.
2. Reusability:
o The Engine and Tires classes can be reused in other classes (e.g., a Truck).
3. Modularity:
o You can replace the Engine or Tires objects in the Car class with different
implementations without modifying the Car class.
Composition Inheritance
Used when the relationship is "has-a". Used when the relationship is "is-a".
42
Imagine a system that manages different types of files with shared functionality for encryption
and compression. Composition can be used to combine these features.
Example:
class Encryptor:
def encrypt(self, data):
return f"Encrypted({data})"
class Compressor:
def compress(self, data):
return f"Compressed({data})"
class FileManager:
def __init__(self, encryptor, compressor):
[Link] = encryptor
[Link] = compressor
Output:
Saving: Compressed(Encrypted(MyData))
Advantages of Composition
1. Flexibility:
o You can change parts of the system by swapping out composed objects.
2. Encapsulation:
o Details of composed objects are hidden, reducing complexity.
3. Reuse:
o Components like Engine, Tires, Encryptor, and Compressor can be reused in other
systems.
Disadvantages of Composition
43
1. Slightly More Verbose:
o You need to create and manage multiple objects explicitly.
2. Manual Delegation:
o Methods from composed objects might need to be explicitly delegated.
44