Before_Summarize
Before_Summarize
Python is a versatile, high-level programming language that's easy to learn and widely used in web
development, data analysis, artificial intelligence, scientific computing, automation, and more.
Let’s break down the basics for someone starting fresh.
1. What is Python?
Python is:
Interpreted: Executes code line-by-line, making it beginner-friendly.
Dynamically Typed: No need to declare variable types.
High-Level: Abstracts low-level details, allowing you to focus on problem-solving.
Versatile: Used in various fields like web development, data science, and machine learning.
2. Installing Python
To start using Python:
1. Download Python from the official website.
2. Install Python, ensuring the option "Add Python to PATH" is checked during installation.
3. Verify installation by typing python --version in the terminal or command prompt.
3. Writing Your First Python Program
You can run Python code in:
Python Shell: Interactive console for running Python commands.
Script File: Save your code in a .py file and run it using python filename.py.
Hello, World! Example
print("Hello, World!")
Run the code, and you’ll see:
Copy code
Hello, World!
4. Python Basics
4.1 Variables and Data Types
Variables store data. You don’t need to declare their type.
# Variables
name = "Alice" # String
age = 25 # Integer
height = 5.7 # Float
is_student = True # Boolean
# While loop
count = 0
while count < 5:
print(count)
count += 1
4.4 Functions
Functions help organize reusable code.
def greet(name):
return f"Hello, {name}!"
print(greet("Alice"))
5. Python Data Structures
Python provides built-in data structures for organizing data.
Lists: Ordered, mutable collections.
fruits = ["apple", "banana", "cherry"]
fruits.append("orange")
print(fruits)
Tuples: Ordered, immutable collections.
coordinates = (10, 20)
print(coordinates)
Dictionaries: Key-value pairs.
person = {"name": "Alice", "age": 25}
print(person["name"])
Sets: Unordered collections of unique elements.
unique_numbers = {1, 2, 3, 2}
print(unique_numbers) # Output: {1, 2, 3}
6. Working with Libraries
Python’s strength lies in its rich ecosystem of libraries.
Install Libraries: Use pip (Python's package manager) to install libraries.
pip install numpy
Example: Using the math Library
import math
def introduce(self):
return f"My name is {self.name}, and I am {self.age} years old."
1. What is Python?
High-Level Language: Abstracts away complex machine-level details.
Interpreted: Executes code line-by-line, providing immediate feedback during development.
Dynamically Typed: No need to declare variable types explicitly.
Multi-Paradigm: Supports object-oriented, procedural, and functional programming.
4. Installing Python
1. Download Python from the official website.
2. Install Python and ensure you check the box to "Add Python to PATH".
3. Verify installation:
python --version
4. Use an Integrated Development Environment (IDE) like:
o VS Code
o PyCharm
o Jupyter Notebook
# Output
print(f"Hello, {name}!")
6.3 Control Flow
Conditional Statements
age = int(input("Enter your age: "))
# While Loop
count = 0
while count < 5:
print(count)
count += 1
print(greet("Alice"))
9. File Handling
Python makes it easy to read and write files.
# Writing to a file
with open("example.txt", "w") as file:
file.write("Hello, World!")
def introduce(self):
return f"My name is {self.name} and I am {self.age} years old."
Summary
- Sets are unordered, mutable collections of unique elements.
- Common applications include removing duplicates, managing unique data, and performing set
operations like union and intersection.
- Real-world use cases: Deduplicating lists, comparing unique identifiers across datasets,
managing tags/categories.
Sets can greatly simplify certain tasks, making them a valuable addition to any developer’s toolkit
in Python. Let me know if you’d like more advanced examples or any specific details!
Dictionary Methods
Here are some helpful dictionary methods for working with dictionaries:
1. keys(): Returns all the keys in the dictionary.
print(inventory.keys()) # Output: dict_keys(['apple', 'banana', 'orange'])
2. values(): Returns all values in the dictionary.
print(inventory.values()) # Output: dict_values([50, 30, 30])
3. items(): Returns a list of `(key, value)` pairs, useful for iterating.
for item, quantity in inventory.items():
print(item, quantity)
Output:
# apple 50
# banana 30
# orange 30
4. update(): Merges another dictionary into the current one, updating values if keys match.
python
new_stock = {"apple": 10, "grape": 40}
inventory.update(new_stock)
print(inventory) # Output: {'apple': 10, 'banana': 30, 'orange': 30, 'grape': 40}
Example 3: Student Grades System
A school system might store students’ grades in a dictionary, with each student’s name as the key
and their grade as the value.
grades = {
"Alice": 88,
"Bob": 95,
"Charlie": 72
}
# Update Alice's grade
grades["Alice"] = 90
# Print each student's grade
for student, grade in grades.items():
print(f"{student}: {grade}")
# Output:
# Alice: 90
# Bob: 95
# Charlie: 72
This structure allows for quick lookups, updates, and iterating over student data.
Nested Dictionaries
Dictionaries can contain other dictionaries as values, which is useful for representing complex data
structures, like storing each user’s complete profile in a nested dictionary.
users = {
"madhu": {"age": 28, "city": "Bangalore"},
"babu": {"age": 34, "city": "Los Angeles"},
}
# Access nested data
print(users["madhu"]["city"]) # Output: Bangalore
When to Use Dictionaries
1. Data with Unique Identifiers: Dictionaries are excellent when each piece of data is uniquely
identified by a key (e.g., usernames, product IDs).
2. Efficient Lookups: With O(1) average time complexity for lookups, dictionaries are efficient for
retrieving data by key.
3. Flexible Structures: Dictionaries can represent a wide range of data structures and enable
easy access and modification.
Summary
- Dictionaries store key-value pairs, making them ideal for mappings.
- Real-world applications include user profiles, inventory management, and structured data
storage.
- They support fast lookups and are mutable, allowing easy modification of data.
Dictionaries provide flexibility and efficiency for handling complex data in Python. Let me know if
you’d like more advanced examples or any specific functionality explained further!
def log_decorator(func):
def wrapper (*args, **kwargs):
print (f"Calling function: {func.__name__}")
result = func(*args, **kwargs) # Call the original function
print (f"Function {func.__name__} finished")
return result
return wrapper
@log_decorator
def greet(name):
return f"Hello, {name}!"
# Usage
print(greet("Madhu"))
Output
When you run the code, you’ll see:
Calling function: greet
Function greet finished
Hello, Madhu!
Simplicity: This example illustrates how decorators can add functionality (logging) in a clear and
simple way.
Functionality Separation: The logging is separated from the core logic of the greet function, making
the code cleaner and easier to maintain.
This example shows the basic mechanism of decorators in a straightforward manner!
@requires_authentication
def access_secure_resource(user):
return "Access granted to secure resource!"
# Usage example
user1 = {'name': 'Alice', 'is_authenticated': True}
user2 = {'name': 'Bob', 'is_authenticated': False}
# Authorized user
try:
print(access_secure_resource(user1)) # Output: Access granted to secure resource!
except PermissionError as e:
print(e)
# Unauthorized user
try:
print(access_secure_resource(user2)) # Raises PermissionError
except PermissionError as e:
print(e) # Output: User is not authorized to perform this action.
Explanation
1. Decorator Definition:
The requires_authentication decorator takes a function func as an argument.
Inside, it defines a wrapper function that checks the user object for an is_authenticated attribute.
2. Authorization Check:
If the user is not authenticated, a PermissionError is raised.
If the user is authenticated, the original function func is called with the user and any additional
arguments.
Flexibility: You can customize the authorization logic based on your application needs.
Error Handling: The decorator can raise exceptions, which can be caught and handled in the calling
code.
Reusable: This decorator can be reused with any function that requires user authentication.
This example showcases how decorators can encapsulate cross-cutting concerns like
authentication, keeping your code clean and focused on its core functionality.
o stop: The loop runs up to but does not include this number.
Example:
for num in range (0, 10, 2): # 0 to 8, increment by 2
print(num)
Output:
0
2
4
6
8
You can use else with a for loop. It executes after the loop completes unless the loop is
exited with break.
Next Steps
Practice writing loops with lists, ranges, and strings.
Experiment with break, continue, and else for control.
Try nested loops to solve multi-layered problems.
Let me know if you'd like more advanced examples or additional help!
try:
result = 10 / 2
except ZeroDivisionError:
print("Error!")
else:
print(f"Success: {result}")
finally:
print("Done.")
4. Log Errors:
o In real-world applications, log exceptions for debugging instead of just printing them.
Key Takeaways
try: Protects risky code.
except: Handles errors gracefully.
finally: Ensures cleanup or final actions.
This structure ensures your program can recover from unexpected situations without breaking. Let
me know if you'd like additional examples or clarification!
Real-Life Analogy
1. and Operator
The and operator returns True only if both conditions are True.
a = True
b = False
print(a and b) # Output: False
age = 20
has_permit = True
2. or Operator
a = True
b = False
print(a or b) # Output: True
weekend = False
finished_work = True
if weekend or finished_work:
print("Let's watch a movie.")
else:
print("Not now.")
3. not Operator
a = True
print(not a) # Output: False
is_raining = True
if not is_raining:
print("Go outside.")
else:
print("Stay indoors.")
credit_score = 750
income = 60000
blacklisted = False
if credit_score >= 700 and income > 50000 and not blacklisted:
print("Loan Approved.")
else:
print("Loan Denied.")
Example 2: Fitness Goals
The user didn’t meet their step goal (steps < goal) or
They ate more calories than recommended.
steps = 8000
goal = 10000
calories_consumed = 2500
calories_limit = 2000
Short-Circuit Evaluation
1. and: Stops evaluating as soon as it encounters False (because the entire expression can’t be True).
2. or: Stops evaluating as soon as it encounters True (because the entire expression is already True).
Example:
a = False
b = True
Real-Life Application
is_member = True
cart_value = 80
Important Tips
a = True
b = False
c = True
result = a or b and c # `b and c` is evaluated first
print(result) # Output: True
2. Readable Conditions: Avoid overly complex conditions. Use variables or functions for clarity.
# Instead of:
if age >= 18 and has_permit and not blacklisted:
...
# Use:
is_eligible = age >= 18 and has_permit and not blacklisted
if is_eligible:
Key Takeaways
Logical operators (and, or, not) combine or modify conditions to form powerful decision-
making logic.
Boolean values (True, False) are the building blocks for these operators.
They are heavily used in decision-making (if statements), loops, and filtering data.
Let me know if you'd like advanced use cases or further clarifications!
Let’s explore the concept from scratch with examples, using real-life analogies.
What is a Function?
1. Defining a Function: Use the def keyword followed by the function name, parentheses, and a colon.
2. Calling a Function: Use the function name followed by parentheses to execute it.
Defining a Function
Syntax:
def function_name(parameters):
# Code block
return value
Calling a Function
Syntax:
function_name(arguments)
Define a Function
def greet():
print("Hello, world!")
Example
def greet_person(name):
print(f"Hello, {name}!")
Real-Life Analogy: The greeting machine now customizes its message based on the person.
Imagine an online store where you calculate the total price of items.
Define a Function
total = calculate_total_price(50, 3)
print(f"Total Price: ${total}") # Output: Total Price: $150
Default Parameters
Example
def greet(name="Guest"):
print(f"Hello, {name}!")
Keyword Arguments
Example
def introduce(name, age):
print(f"I am {name} and I am {age} years old.")
Variable-Length Arguments
Example: *args
def sum_numbers(*args):
return sum(args)
Example: **kwargs
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
Scope of Variables
Example
x = 10 # Global variable
def multiply_by_two():
x = 5 # Local variable
return x * 2
print(multiply_by_two()) # Output: 10
print(x) # Output: 10
Lambda Functions
Example
square = lambda x: x ** 2
print(square(4)) # Output: 16
Use Case: Great for short, throwaway functions or when used as arguments to higher-order functions like map()
or filter().
if authenticate_user("admin", "1234"):
print("Access Granted.")
else:
print("Access Denied.")
print(calculate_discount(100)) # Output: 90
print(calculate_discount(100, 20)) # Output: 80
def celsius_to_fahrenheit(celsius):
return (celsius * 9/5) + 32
Key Takeaways
Let me know if you'd like examples of recursive functions, higher-order functions, or more complex real-world
cases!
Understanding Lambda Functions in Python
Lambda functions, also known as anonymous functions, are small, single-expression functions that don’t require
a formal definition using the def keyword. They’re often used for short, throwaway functions or when you need a
function as an argument to another function.
A lambda function:
Example
add = lambda x, y: x + y
print(add(2, 3)) # Output: 5
Here, lambda x, y: x + y creates a function that adds two numbers, and add is the reference to that lambda
function.
1. Conciseness: They are compact and easier to write for simple operations.
2. Use-once Logic: Ideal for temporary functions used only once or twice.
3. Inline Use: Great when used directly as arguments for higher-order functions like map(),
filter(), or reduce().
Key Features
Real-Life Analogies
1. Vending Machine Buttons: Each button performs a specific task (e.g., dispense soda,
chips). You don’t need to name the task; pressing the button is enough.
2. One-Time Delivery Service: A courier handles a single delivery. You don’t create a
permanent job profile for them, just a one-time task.
Practical Examples
1. Sorting a List of Tuples
numbers = [1, 2, 3, 4]
numbers = [1, 2, 3, 4]
Handles multi-statement
Complexity Only for simple expressions
logic
Using def:
def square(x):
return x ** 2
print(square(4)) # Output: 16
Using lambda:
square = lambda x: x ** 2
print(square(4)) # Output: 16
Lambda functions are heavily used in data manipulation with libraries like Pandas.
import pandas as pd
Real-Life Applications
discount = lambda price: price * 0.9 if price > 100 else price
print(discount(120)) # Output: 108.0
Best Practices
Summary
What is a Generator?
A generator is a function that:
gen = simple_generator()
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Output: 3
Real-Life Analogy
Imagine a vending machine:
Instead of giving you all the snacks at once, it gives you one snack when you press a
button (lazy evaluation).
Each button press resumes from where it left off (maintaining state).
Examples of Generators
1. Basic Generator
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
gen = infinite_numbers()
for _ in range(5):
print(next(gen)) # Output: 0, 1, 2, 3, 4
Real-Life Use Case: Generating unique IDs or timestamps.
3. Fibonacci Sequence
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib))
Output:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34
4. Filtering Data with Generators
Generators can filter large datasets efficiently.
def even_numbers(sequence):
for number in sequence:
if number % 2 == 0:
yield number
numbers = range(10)
for even in even_numbers(numbers):
print(even)
Output:
0, 2, 4, 6, 8
Using Generator Expressions
A generator expression is a compact way to create a generator, similar to list
comprehensions but with parentheses.
Example
squared = (x ** 2 for x in range(5))
print(next(squared)) # Output: 0
print(next(squared)) # Output: 1
Compare:
List comprehension: [x ** 2 for x in range(5)] creates the entire list in memory.
Generator expression: (x ** 2 for x in range(5)) computes one value at a time.
Advantages of Generators
Memory Usage:
Lists store all elements in memory.
Generators compute elements on demand, making them suitable for large or infinite
sequences.
Example
import sys
list_nums = [x ** 2 for x in range(1000)]
gen_nums = (x ** 2 for x in range(1000))
import time
def live_sensor_data():
while True:
yield f"Sensor Reading at {time.time()}"
time.sleep(1)
def square_numbers(nums):
for n in nums:
yield n ** 2
pipeline = square_numbers(numbers())
for value in pipeline:
print(value)
Comparing Generators with Iterators
Feature Generator Iterator
Definition Created using yield or expressionsCreated using custom __iter__() and
__next__() methods
State Maintenance Automatic Manual
Ease of UseEasy Requires more boilerplate code
Key Takeaways
Generators are ideal for processing large or infinite sequences efficiently.
Use yield to pause and resume execution.
They shine in scenarios like:
Iterating over large datasets (e.g., files, APIs).
Producing infinite or dynamic sequences.
Creating pipelines for data transformation.
Would you like examples of integrating generators into advanced systems, like
streaming or parallel processing? Let me know!
inner_function(*args, **kwargs)
def decorator(func):
def wrapper(*args, **kwargs):
print("Before the function call")
result = func(*args, **kwargs)
print("After the function call")
return result
return wrapper
@decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
Output:
Before the function call
Hello, Alice!
After the function call
3. API Requests
Dynamic APIs may accept varying parameters, which can be handled with **kwargs.
def dynamic_api_call(endpoint, **params):
query = "&".join(f"{k}={v}" for k, v in params.items())
url = f"https://example.com/{endpoint}?{query}"
print(f"Requesting URL: {url}")
dynamic_api_call("search", q="Python", limit=10, sort="asc")
Output:
Requesting URL: https://example.com/search?q=Python&limit=10&sort=asc
Best Practices
Order of Arguments: Always follow this order:
Regular arguments
*args
Default arguments
**kwargs
def function(a, *args, b=0, **kwargs):
pass
Readability: Avoid overusing *args and **kwargs when explicit arguments make the
code clearer.
Documentation: Document the purpose of extra arguments when using *args and
**kwargs.
Key Takeaways
*args captures variable-length positional arguments as a tuple.
**kwargs captures variable-length keyword arguments as a dictionary.
They allow flexibility in designing reusable, dynamic, and modular functions.
Use cases include APIs, decorators, and dynamic argument handling.
class LinkedList:
def __init__(self):
self.head = None
# Usage
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.display() # Output: 1 -> 2 -> 3 -> None
8. Trees
Definition: Hierarchical structure with a root and child nodes.
Real-Life Analogy: File directory system with folders and subfolders.
Example: Binary Tree
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.value, end=" ")
inorder_traversal(root.right)
# Create Tree
root = TreeNode(10)
root.left = TreeNode(5)
root.right = TreeNode(20)
inorder_traversal(root) # Output: 5 10 20
9. Graphs
Definition: Nodes connected by edges, representing relationships.
Real-Life Analogy: A map where cities (nodes) are connected by roads (edges).
Example: Representing a Graph
graph = {
"A": ["B", "C"],
"B": ["A", "D"],
"C": ["A", "D"],
"D": ["B", "C"]
}
# Print connections
for node, neighbors in graph.items():
print(f"{node}: {', '.join(neighbors)}")
10. Heaps
Definition: Specialized tree-based structure that maintains the heap property.
Real-Life Analogy: Priority queues (e.g., hospital ER queue by urgency).
Example:
import heapq
heap = []
heapq.heappush(heap, 3) # Add
heapq.heappush(heap, 1)
heapq.heappush(heap, 5)
2. Object:
o An instance of a class.
3. Attributes:
o Data or properties of an object.
4. Methods:
o Functions defined in a class that operate on an object’s data.
Real-Life Analogy
Think of a class as a blueprint for a car. It defines properties like:
color
brand
engine_type
And behaviors like:
start()
accelerate()
brake()
An object is a specific car built from this blueprint, like:
A red Tesla with an electric engine.
Defining a Class in Python
class Car:
# Constructor
def __init__(self, brand, color):
self.brand = brand
self.color = color
# Method
def start(self):
print(f"{self.color} {self.brand} is starting!")
# Creating an object
my_car = Car("Tesla", "Red")
# Accessing attributes
print(my_car.brand) # Tesla
# Calling a method
my_car.start() # Red Tesla is starting!
Key Points in the Example
1. __init__ Method (Constructor):
o Automatically called when creating an object.
3. Methods (start):
o Define behaviors of the object.
def get_speed(self):
return self.__speed
# Usage
my_car = Car("Tesla", 100)
print(my_car.get_speed()) # 100
my_car.set_speed(120) # Update speed
2. Inheritance
Definition: A class can inherit attributes and methods from another class.
Real-Life Analogy: A sports car is a type of car, so it inherits general car properties
but also has unique features.
class Car:
def __init__(self, brand):
self.brand = brand
def start(self):
print(f"{self.brand} is starting!")
# Usage
ferrari = SportsCar("Ferrari")
ferrari.start() # Ferrari is starting!
ferrari.turbo_boost() # Ferrari is using turbo boost!
3. Polymorphism
Definition: Objects of different classes can be treated the same way if they share
the same method names.
Real-Life Analogy: Whether it’s a car, bike, or airplane, the method move() makes
sense in all contexts.
class Car:
def move(self):
print("Car is driving.")
class Airplane:
def move(self):
print("Airplane is flying.")
# Polymorphic behavior
for vehicle in [Car(), Airplane()]:
vehicle.move()
Output:
csharp
Car is driving.
Airplane is flying.
4. Abstraction
Definition: Hiding the implementation details and exposing only the essential
features.
Real-Life Analogy: You use a smartphone without knowing how its internal
components work.
from abc import ABC, abstractmethod
# Usage
v = Car ()
v.move() # Car is driving.
Real-Life Applications of Classes and Objects
1. E-commerce:
o Classes: Product, Cart, User
2. Gaming:
o Classes: Player, Enemy, Weapon
3. Banking Systems:
o Classes: Account, Transaction, Loan
4. Web Development:
o Classes: Request, Response, View
Best Practices
1. Use Descriptive Class and Method Names:
o Class: Car, BankAccount
print(sqrt(25)) # 5.0
print(pi) # 3.141592653589793
3. Importing with Aliases
# Import and alias the module
import math as m
print(m.sqrt(36)) # 6.0
4. Importing All Symbols (Not Recommended)
from math import *
print(sqrt(49)) # 7.0
print(pi) # 3.141592653589793
⚠ Caution: Avoid this as it can lead to conflicts if multiple modules have functions with
the same name.
How to Create and Import Your Own Module
1. Create a Module File: Save the following code as mymodule.py:
def greet(name):
return f"Hello, {name}!"
pi_value = 3.14
2. Import Your Module:
import mymodule
print(mymodule.greet("Alice")) # Hello, Alice!
print(mymodule.pi_value) # 3.14
3. Import Specific Components:
from mymodule import greet, pi_value
response = requests.get("https://api.github.com")
print(response.status_code) # 200
3. Modular Code Design
Structure: Split a large project into multiple files.
Example:
project/
│
├── main.py # Entry point
├── user.py # Module for user-related functions
└── product.py # Module for product-related functions
user.py:
def create_user(name):
return f"User {name} created."
product.py:
def list_products():
return ["Product A", "Product B"]
main.py:
from user import create_user
from product import list_products
print(create_user("Alice"))
print(list_products())
Common Pitfalls and Best Practices
1. Avoid Circular Imports:
o If two modules depend on each other, it may cause errors.
# mymodule.py
def greet(name):
return f"Hello, {name}!"
if __name__ == "__main__":
print(greet("Testing"))
Advanced Topics
1. Exploring the Python Standard Library
Python's standard library includes powerful modules like:
re: Regular expressions.
json: Working with JSON data.
itertools: Tools for iterators.
Example: Using itertools:
from itertools import permutations
data = [1, 2, 3]
perms = list(permutations(data))
print(perms) # All possible arrangements of [1, 2, 3]
2. Importing from Packages
Packages are directories with an __init__.py file that can contain multiple modules.
Structure:
my_package/
│
├── __init__.py # Makes it a package
├── module1.py
└── module2.py
Usage:
from my_package import module1
Recap
1. Modules:
o Built-in: math, os, random.
2. Import Techniques:
o Full module: import module.
3. Best Practices:
o Modularize code.
import module_name
Key Categories and Examples
1. String and Text Processing
re: Regular expressions for pattern matching.
string: Common string operations.
Example: Validate email addresses using re.
import re
email = "user@example.com"
pattern = r'^\w+@\w+\.\w+$'
if re.match(pattern, email):
print("Valid email!")
else:
print("Invalid email.")
2. Data and Time
datetime: Manipulate dates and times.
time: Work with timestamps.
Example: Calculate the difference between two dates.
import glob
files = glob.glob("*.txt")
print("Text files:", files)
4. Math and Numeric Operations
math: Mathematical functions.
random: Generate random numbers.
statistics: Perform statistical operations.
Example: Calculate the factorial of a number.
import math
print(math.factorial(5)) # 120
5. Data Serialization
json: Work with JSON data.
pickle: Serialize and deserialize Python objects.
Example: Serialize a dictionary to JSON.
import json
import threading
def task():
print("Task is running!")
thread = threading.Thread(target=task)
thread.start()
thread.join()
8. Data Structures
collections: Specialized container types.
heapq: Heap queue algorithms.
deque: Efficient double-ended queue.
Example: Use Counter to count occurrences.
import logging
logging.basicConfig(level=logging.INFO)
logging.info("This is an info message.")
How to Explore the Standard Library?
1. Check the Official Documentation
Python's standard library documentation is comprehensive:
Python Standard Library Docs
2. Use the Built-in help() Function
import math
help(math) # Displays the documentation for the math module
3. Explore Using dir()
import math
print(dir(math)) # Lists all functions and attributes in the math module
4. Experiment in the REPL
Open a Python shell and try small snippets interactively.
Real-Life Applications
Web Scraping:
Before installing a third-party library, check if the standard library provides the
functionality.
Keep Imports Organized:
Use a clear structure and avoid importing unnecessary modules.
Stay Up-to-Date:
Standard library features evolve with Python versions. Check release notes for updates.
Read Source Code:
The standard library is open source. Reading its implementation is a great way to learn.
Recap
The Python Standard Library is a powerful toolkit for everyday programming tasks.
It includes modules for:
String manipulation (re, string)
File handling (os, shutil)
Networking (http, urllib)
Data structures (collections, deque)
Debugging (logging, pdb)
Explore it via documentation, help(), and dir().
Exception handling allows you to manage unexpected errors that occur during program execution by using try,
except, else, and finally blocks.
1. Reading a File
python
Copy code
try:
with open("nonexistent_file.txt", "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print("Error: The file does not exist.")
except PermissionError:
print("Error: You do not have permission to read the file.")
else:
print("File read successfully!")
finally:
print("Finished file operation.")
2. Writing to a File
try:
with open("output.txt", "w") as file:
file.write("Hello, World!")
print("Data written to file.")
except PermissionError:
print("Error: Insufficient permissions to write to the file.")
except IOError as e:
print(f"An I/O error occurred: {e}")
else:
print("File written successfully.")
finally:
print("File operation completed.")
file = None
try:
file = open("example.txt", "r")
content = file.read()
print(content)
except FileNotFoundError:
print("Error: File not found.")
finally:
if file:
file.close()
print("File closed.")
try:
filename = "data.txt"
mode = "x" # "x" mode creates a file, raises error if file exists
with open(filename, mode) as file:
file.write("Some data")
except FileExistsError:
print("Error: The file already exists.")
except IOError as e:
print(f"General I/O error: {e}")
else:
print("File written successfully!")
try:
with open("file.txt", "r") as file:
content = file.read()
except Exception as e:
print(f"An unexpected error occurred: {e}")
Real-Life Scenarios
If file operations fail, log the error instead of crashing the program.
import logging
logging.basicConfig(filename="errors.log", level=logging.ERROR)
try:
with open("logfile.txt", "r") as file:
content = file.read()
except Exception as e:
logging.error(f"An error occurred: {e}")
import time
retries = 3
for attempt in range(retries):
try:
with open("important_data.txt", "r") as file:
print(file.read())
break
except FileNotFoundError:
print(f"Attempt {attempt + 1}: File not found. Retrying...")
time.sleep(1)
except Exception as e:
print(f"An error occurred: {e}")
break
else:
print("Failed after multiple attempts.")
Handle errors for individual files without halting the entire operation.
Best Practices
Summary
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class CSVFile:
def read(self):
return "Reading CSV file"
class JSONFile:
def read(self):
return "Reading JSON file"
def read_file(file):
print(file.read())
o Method overriding.
o Operator overloading.
Enables flexible, reusable, and scalable code.
3. Hierarchy Representation:
o Model "is-a" relationships in real-world problems (e.g., a Dog is a type of
Animal).
4. Overriding and Customization:
o Customize or override behavior for specific cases while retaining shared
functionality.
Basic Syntax of Inheritance
class ParentClass:
# Parent class attributes and methods
pass
class ChildClass(ParentClass):
# Inherits ParentClass attributes and methods
pass
Real-Life Analogy
Imagine a Vehicle class:
All vehicles share common attributes like speed and color, and methods like
move() and stop().
A Car class inherits these common features but may have additional attributes like
num_doors or methods like play_music().
Similarly, a Bike class may inherit the same features but add a type_of_handle()
method.
Examples of Inheritance in Python
1. Single Inheritance
A child class inherits from one parent class.
class Animal:
def speak(self):
return "I make a sound"
class Dog(Animal):
def speak(self):
return "Woof!"
# Usage
animal = Animal()
dog = Dog()
print(animal.speak()) # Output: I make a sound
print(dog.speak()) # Output: Woof!
2. Multilevel Inheritance
A class inherits from another class, which itself inherits from a third class.
class Vehicle:
def move(self):
return "Vehicle is moving"
class Car(Vehicle):
def move(self):
return "Car is driving on the road"
class SportsCar(Car):
def move(self):
return "SportsCar is zooming on the highway"
# Usage
sports_car = SportsCar()
print(sports_car.move()) # Output: SportsCar is zooming on the highway
3. Hierarchical Inheritance
Multiple classes inherit from the same parent class.
class Animal:
def speak(self):
return "I make a sound"
class Cat(Animal):
def speak(self):
return "Meow!"
class Bird(Animal):
def speak(self):
return "Chirp!"
# Usage
cat = Cat()
bird = Bird()
print(cat.speak()) # Output: Meow!
print(bird.speak()) # Output: Chirp!
4. Multiple Inheritance
A class inherits from multiple parent classes.
class Engine:
def start(self):
return "Engine started"
class Wheels:
def roll(self):
return "Wheels are rolling"
# Usage
car = Car()
print(car.start()) # Output: Engine started
print(car.roll()) # Output: Wheels are rolling
Overriding Methods
Child classes can override methods from the parent class to provide specific
behavior.
class Parent:
def greet(self):
return "Hello from Parent"
class Child(Parent):
def greet(self):
return "Hello from Child"
# Usage
child = Child()
print(child.greet()) # Output: Hello from Child
Using super() to Call Parent Methods
The super() function is used to call a method from the parent class, ensuring the
parent’s functionality is still available.
class Parent:
def greet(self):
return "Hello from Parent"
class Child(Parent):
def greet(self):
parent_message = super().greet()
return f"{parent_message} and Hello from Child"
# Usage
child = Child()
print(child.greet()) # Output: Hello from Parent and Hello from Child
Real-World Example: Employee Hierarchy
Imagine a company's employee structure where all employees share common
attributes like name and id. Managers and Developers have additional unique
features.
class Employee:
def __init__(self, name, id):
self.name = name
self.id = id
def get_details(self):
return f"Name: {self.name}, ID: {self.id}"
class Manager(Employee):
def __init__(self, name, id, team_size):
super().__init__(name, id)
self.team_size = team_size
def get_details(self):
return f"{super().get_details()}, Team Size: {self.team_size}"
class Developer(Employee):
def __init__(self, name, id, programming_language):
super().__init__(name, id)
self.programming_language = programming_language
def get_details(self):
return f"{super().get_details()}, Programming Language:
{self.programming_language}"
# Usage
manager = Manager("Alice", 101, 10)
developer = Developer("Bob", 102, "Python")
print(manager.get_details()) # Output: Name: Alice, ID: 101, Team Size: 10
print(developer.get_details()) # Output: Name: Bob, ID: 102, Programming
Language: Python
Key Types of Inheritance in Python
Multiple A class inherits from more than one Car from Engine
Inheritance parent class. and Wheels.
Summary
What is Inheritance?
o A mechanism for one class (child) to inherit attributes and methods from
another class (parent).
Benefits:
o Code reuse, extensibility, and clear hierarchy.
Examples:
o Single, multilevel, hierarchical, and multiple inheritance.
2. Controlled Access:
o Expose only the necessary parts of an object to the outside world via
getters, setters, or other methods.
3. Improved Maintainability:
o Makes debugging and updating the code easier by isolating functionality.
4. Abstraction:
o Helps abstract complex systems into simpler interfaces, showing only what
is necessary.
How Encapsulation Works in Python
In Python, encapsulation is implemented using:
1. Public Attributes: Accessible from anywhere.
2. Protected Attributes: Indicated by a single underscore _attribute, intended for
internal use but still accessible.
3. Private Attributes: Indicated by a double underscore __attribute, name-mangled to
make access difficult from outside the class.
Real-Life Analogy
Think of a bank account:
Your account balance is private (you can’t access it directly).
You can use methods like deposit() and withdraw() to safely interact with your
balance.
This ensures your account isn't accidentally or maliciously modified.
# Usage
my_car = Car("Toyota")
print(my_car.brand) # Output: Toyota
def start_engine(self):
if not self._engine_status:
self._engine_status = True
return "Engine started"
return "Engine is already running"
# Usage
my_car = Car("Toyota", False)
# Usage
account = BankAccount(1000)
print(account.deposit(500)) # Output: Deposited 500, New Balance: 1500
print(account.withdraw(200)) # Output: Withdrew 200, Remaining Balance: 1300
# Usage
person = Person("Alice", 30)
print(person.get_name()) # Output: Alice
print(person.get_age()) # Output: 30
person.set_name("Bob")
person.set_age(35)
print(person.get_name()) # Output: Bob
print(person.get_age()) # Output: 35
Real-World Example: Library System
python
Copy code
class Library:
def __init__(self):
self.__books = ["Python Basics", "Data Science", "AI and ML"]
# Getter method
def get_books(self):
return self.__books
# Setter method
def add_book(self, book):
if book:
self.__books.append(book)
return f"{book} added to library"
return "Invalid book name"
# Usage
library = Library()
print(library.get_books()) # Output: ['Python Basics', 'Data Science', 'AI and ML']
2. Reduces Complexity:
o Hides internal implementation details from the outside world.
3. Increases Flexibility:
o Enables controlled access to data via getter and setter methods.
4. Better Code Organization:
o Groups related data and methods together in a single class.
Best Practices
1. Use Private Attributes for Sensitive Data:
o Protect sensitive data by making them private (__attribute).
Benefits:
o Data protection, controlled access, abstraction, and maintainability.
Techniques:
o Use private attributes with getters and setters for controlled access.
Real-Life Analogy
Imagine a Payment System:
All payment methods (like CreditCard, PayPal, and BankTransfer) share common
behaviors, such as pay and refund.
An ABC called PaymentMethod could define these behaviors as abstract methods.
Any subclass of PaymentMethod must implement the pay and refund methods,
ensuring a consistent interface across all payment types.
class AbstractClass(ABC):
@abstractmethod
def some_method(self):
pass
Example: Payment System
from abc import ABC, abstractmethod
@abstractmethod
def refund(self, amount):
"""Process the refund"""
pass
# Subclass: Credit Card
class CreditCard(PaymentMethod):
def pay(self, amount):
return f"Paid ${amount} using Credit Card."
# Subclass: PayPal
class PayPal(PaymentMethod):
def pay(self, amount):
return f"Paid ${amount} using PayPal."
# Usage
def process_payment(payment_method, amount):
print(payment_method.pay(amount))
print(payment_method.refund(amount))
credit_card = CreditCard()
paypal = PayPal()
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius**2
def perimeter(self):
return 2 * math.pi * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
# Usage
shapes = [Circle(5), Rectangle(4, 6)]
class Employee(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def get_salary(self):
pass
def display(self):
return f"Employee Name: {self.name}"
class FullTimeEmployee(Employee):
def get_salary(self):
return "Salary is $5000/month"
class PartTimeEmployee(Employee):
def get_salary(self):
return "Salary is $20/hour"
# Usage
fte = FullTimeEmployee("Alice")
pte = PartTimeEmployee("Bob")
class CustomList(MutableSequence):
def __init__(self):
self._data = []
def __len__(self):
return len(self._data)
# Usage
cl = CustomList()
cl.insert(0, "a")
cl.insert(1, "b")
print(cl) # Output: ['a', 'b']
Key Benefits of Abstract Base Classes
1. Consistency:
o Ensures subclasses follow a consistent interface.
2. Readability:
o Clearly defines what a class is expected to implement.
3. Polymorphism:
o Enables using objects interchangeably, as long as they implement the
required methods.
When to Use Abstract Base Classes
1. Defining Interfaces:
o When you want to enforce that all subclasses implement specific methods.
2. Extending Frameworks:
o For example, building plugins or APIs where subclasses must adhere to a
contract.
3. Shared Behavior with Default Implementations:
o Provide shared behavior through concrete methods in the ABC while
allowing customization.
Best Practices
1. Avoid Overuse:
o Use ABCs only when you need to enforce strict method implementation.
2. Keep It Simple:
o Avoid creating overly complex hierarchies with multiple ABCs.
Key Features:
o Abstract methods must be implemented.
class CustomABCMeta(ABCMeta):
def __new__(mcls, name, bases, namespace):
# Check if all methods in the class follow a naming convention
for attr_name in namespace:
if callable(namespace[attr_name]) and not attr_name.startswith('custom_'):
raise TypeError(f"Method '{attr_name}' must start with 'custom_'")
return super().__new__(mcls, name, bases, namespace)
class AbstractBase(metaclass=CustomABCMeta):
@abstractmethod
def custom_method(self):
pass
# Correct Implementation
class ValidClass(AbstractBase):
def custom_method(self):
return "Valid method implementation"
# Invalid Implementation
# class InvalidClass(AbstractBase):
# def invalid_method(self): # This raises a TypeError
# pass
2. Abstract Base Classes and Multiple Inheritance
Python supports multiple inheritance, and ABCs can play a crucial role in defining a
consistent interface for subclasses in complex hierarchies.
Example: A File System Hierarchy
from abc import ABC, abstractmethod
class Readable(ABC):
@abstractmethod
def read(self):
pass
class Writable(ABC):
@abstractmethod
def write(self, data):
pass
def read(self):
return self.content
# Usage
file = File("example.txt")
file.write("Hello, World!")
print(file.read()) # Output: Hello, World!
In this example:
Readable and Writable define separate behaviors.
The File class inherits from both and implements their methods.
class Shape(ABC):
@abstractmethod
def draw(self):
pass
class Circle(Shape):
def draw(self):
return "Drawing a Circle"
class Square(Shape):
def draw(self):
return "Drawing a Square"
# Factory
class ShapeFactory:
@staticmethod
def create_shape(shape_type):
if shape_type == "circle":
return Circle()
elif shape_type == "square":
return Square()
else:
raise ValueError("Unknown shape type")
# Usage
shape = ShapeFactory.create_shape("circle")
print(shape.draw()) # Output: Drawing a Circle
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, file):
pass
class ZipCompression(CompressionStrategy):
def compress(self, file):
return f"Compressing {file} using ZIP"
class TarCompression(CompressionStrategy):
def compress(self, file):
return f"Compressing {file} using TAR"
# Context
class Compressor:
def __init__(self, strategy: CompressionStrategy):
self.strategy = strategy
# Usage
compressor = Compressor(ZipCompression())
print(compressor.compress("example.txt")) # Output: Compressing example.txt using
ZIP
compressor.strategy = TarCompression()
print(compressor.compress("example.txt")) # Output: Compressing example.txt using
TAR
3.3 Template Method Pattern
The Template Method Pattern defines the skeleton of an algorithm in an abstract class
and allows subclasses to provide specific implementations for some steps.
from abc import ABC, abstractmethod
class DataProcessor(ABC):
def process(self):
self.read_data()
self.transform_data()
self.save_data()
@abstractmethod
def read_data(self):
pass
@abstractmethod
def transform_data(self):
pass
def save_data(self):
print("Saving data to database")
class CSVProcessor(DataProcessor):
def read_data(self):
print("Reading data from CSV file")
def transform_data(self):
print("Transforming CSV data")
# Usage
processor = CSVProcessor()
processor.process()
# Output:
# Reading data from CSV file
# Transforming CSV data
# Saving data to database
4. Mixing Abstract Base Classes with Concrete Classes
You can mix concrete methods with abstract methods in ABCs to provide default
implementations that subclasses can override.
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def make_sound(self):
pass
def sleep(self):
return "Sleeping..."
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
# Usage
dog = Dog()
cat = Cat()
class JSONSerializable(ABC):
pass
class MyClass:
def to_json(self):
return '{"key": "value"}'
2. Avoid Over-Engineering:
o Keep hierarchies simple. Deep inheritance chains can make the code harder
to maintain.
3. Leverage Concrete Methods:
o Provide default behavior for shared functionality to reduce code duplication.