0% found this document useful (0 votes)
2 views19 pages

MITS Python Programming Course v4

The MITS Academy Python Programming Course covers intermediate to advanced topics, including Python foundations, data structures, functions, and object-oriented programming. Key concepts include dynamic typing, string manipulation, list comprehensions, decorators, and magic methods. The course emphasizes practical coding skills through examples, assignments, and projects.

Uploaded by

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

MITS Python Programming Course v4

The MITS Academy Python Programming Course covers intermediate to advanced topics, including Python foundations, data structures, functions, and object-oriented programming. Key concepts include dynamic typing, string manipulation, list comprehensions, decorators, and magic methods. The course emphasizes practical coding skills through examples, assignments, and projects.

Uploaded by

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

MITS Academy — Python Programming Course

MITS ACADEMY
Python Programming Course

Intermediate Level | Advanced Theory, Code Examples, Assignments & Projects


[Link]

Page 1 | MITS Academy | [Link]


MITS Academy — Python Programming Course

Module 1: Python Foundations (Review)


1.1 Variables, Data Types and Type Casting
Python uses dynamic typing — the interpreter infers the type of a variable from the value
assigned. Python supports integers (int), floats (float), strings (str), booleans (bool), NoneType,
and complex numbers. Type casting explicitly converts one type to another using functions like
int(), float(), str(), and bool().
The id() function reveals the memory address of an object. Python caches small integers (-5 to
256) and interned strings for performance — this means two variables pointing to the same
small integer share the same memory address. Understanding this avoids common bugs with
identity (is) vs equality (==) checks.
# Dynamic typing
x = 10
print(type(x)) # <class 'int'>
x = "hello" # Same variable, different type
print(type(x)) # <class 'str'>

# Type casting
print(int("42")) # 42
print(float("3.14")) # 3.14
print(str(100)) # "100"
print(bool(0)) # False
print(bool("")) # False
print(bool("abc")) # True

# is vs ==
a = 1000
b = 1000
print(a == b) # True (same value)
print(a is b) # False (different objects in memory for large ints)

c = 100
d = 100
print(c is d) # True (Python caches small ints)

# Multiple assignment and unpacking


x, y, z = 10, 20, 30
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]

1.2 String Formatting and Manipulation


Python offers three ways to format strings: %-formatting (old style), [Link]() (Python 2.6+),
and f-strings (Python 3.6+ — preferred). F-strings are faster, more readable, and support
expressions and method calls directly inside the braces.
String methods are non-mutating — they always return a new string. Strings support slicing with
the [start:stop:step] syntax. The step=-1 trick reverses a string. join() is more efficient than
concatenation with + in loops because it creates only one new string object.

Page 2 | MITS Academy | [Link]


MITS Academy — Python Programming Course

name = "Alice"
score = 95.5

# %-formatting (legacy)
print("Name: %s, Score: %.1f" % (name, score))

# [Link]()
print("Name: {}, Score: {:.2f}".format(name, score))

# f-strings (recommended)
print(f"Name: {name}, Score: {score:.2f}")
print(f"Score in hex: {int(score):#x}") # f-strings support expressions
print(f"Upper: {[Link]()}") # method calls inside {}

# String slicing
s = "Python Programming"
print(s[0:6]) # Python
print(s[-11:]) # Programming
print(s[::2]) # Pto rgamn (every 2nd char)
print(s[::-1]) # gnimmargorP nohtyP

# Efficient join
words = ["Python", "is", "powerful"]
print(" ".join(words)) # Preferred over "Python" + " " + "is" + ...

# String methods chained


raw = " hello world "
print([Link]().title().replace(" ", "_")) # Hello_World

Module 2: Data Structures — Deep Dive


2.1 List Comprehensions and Generator Expressions
List comprehensions are a concise way to create lists. They are generally faster than equivalent
for-loop code because the iteration is optimized in C. The syntax is [expression for item in
iterable if condition]. The condition (filter) is optional.
Generator expressions have the same syntax but use parentheses instead of brackets. They
produce items one at a time (lazily) instead of building the entire list in memory. Use generators
when working with large datasets to save memory.
# List comprehension vs regular loop
squares_loop = []
for x in range(10):
squares_loop.append(x**2)

# Same result, one line:


squares = [x**2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# With filter condition


evens = [x for x in range(20) if x % 2 == 0]
print(evens) # [0, 2, 4, ..., 18]

# Nested comprehension — flatten 2D list


matrix = [[1,2,3],[4,5,6],[7,8,9]]
flat = [num for row in matrix for num in row]

Page 3 | MITS Academy | [Link]


MITS Academy — Python Programming Course

print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Dictionary comprehension
students = ["Alice", "Bob", "Carol"]
marks = [92, 78, 85]
grades = {s: m for s, m in zip(students, marks)}
print(grades) # {'Alice': 92, 'Bob': 78, 'Carol': 85}

# Set comprehension — unique vowels in a sentence


sentence = "hello world this is python"
vowels = {c for c in sentence if c in "aeiou"}
print(vowels) # {'e', 'o', 'i'}

# Generator expression — memory efficient


import sys
list_obj = [x**2 for x in range(100000)]
gen_obj = (x**2 for x in range(100000))
print([Link](list_obj)) # ~800k bytes
print([Link](gen_obj)) # 104 bytes — much smaller!

2.2 Advanced Dictionary and Set Operations


Dictionaries in Python 3.7+ maintain insertion order. The .get() method safely retrieves a value
with a default fallback. The setdefault() method sets a key only if it doesn't exist. The Counter
class from collections is a specialized dict for counting occurrences. defaultdict automatically
creates a default value for missing keys.
Dictionary merging with the | operator (Python 3.9+) and unpacking with ** allows combining
dictionaries. The ChainMap lets you search multiple dictionaries as one unit without copying.
from collections import Counter, defaultdict, OrderedDict

# Counter — count occurrences


words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
count = Counter(words)
print(count) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(count.most_common(2)) # [('apple', 3), ('banana', 2)]

# Counter with string


letters = Counter("programming")
print(letters['g']) # 2

# defaultdict — no KeyError for missing keys


groups = defaultdict(list) # default value is empty list
for name, subject in [("Alice","Math"),("Bob","Science"),("Alice","English")]:
groups[name].append(subject)
print(dict(groups)) # {'Alice': ['Math', 'English'], 'Bob': ['Science']}

# Merge dictionaries (Python 3.9+)


defaults = {"color": "blue", "size": "medium", "font": "Arial"}
overrides = {"color": "red", "size": "large"}
result = defaults | overrides # overrides wins on conflicts
print(result) # {'color': 'red', 'size': 'large', 'font': 'Arial'}

# Sort dict by value


scores = {"Alice": 92, "Bob": 78, "Carol": 85, "David": 95}
sorted_scores = dict(sorted([Link](), key=lambda x: x[1], reverse=True))
print(sorted_scores) # David, Alice, Carol, Bob

Page 4 | MITS Academy | [Link]


MITS Academy — Python Programming Course

Module 3: Functions — Advanced


3.1 *args, **kwargs and Function Annotations
*args allows a function to accept any number of positional arguments as a tuple. **kwargs
allows any number of keyword arguments as a dictionary. These are essential when building
flexible APIs and decorators. The * and ** operators also unpack sequences and dictionaries
when calling functions.
Function annotations (type hints) document expected argument types and return type. They are
not enforced at runtime but help IDEs provide better autocomplete, catch bugs with static
analysis tools like mypy, and improve code readability. The typing module provides complex
type hints.
# *args — variable positional arguments
def sum_all(*args):
return sum(args)

print(sum_all(1, 2, 3)) # 6
print(sum_all(10, 20, 30, 40)) # 100

# **kwargs — variable keyword arguments


def create_profile(name, **kwargs):
profile = {"name": name}
[Link](kwargs)
return profile

print(create_profile("Alice", age=25, city="Mumbai", role="student"))


# {'name': 'Alice', 'age': 25, 'city': 'Mumbai', 'role': 'student'}

# Combining all types


def mixed(pos1, pos2, *args, keyword_only, **kwargs):
print(f"pos: {pos1}, {pos2}")
print(f"extra args: {args}")
print(f"keyword_only: {keyword_only}")
print(f"kwargs: {kwargs}")

mixed(1, 2, 3, 4, keyword_only="required", x=10, y=20)

# Type annotations
from typing import List, Dict, Optional, Tuple

def calculate_stats(numbers: List[float]) -> Dict[str, float]:


return {
"mean": sum(numbers) / len(numbers),
"min": min(numbers),
"max": max(numbers)
}

def greet(name: str, greeting: str = "Hello") -> str:


return f"{greeting}, {name}!"

print(greet("Bob")) # Hello, Bob!


print(greet("Carol", "Hi")) # Hi, Carol!

Page 5 | MITS Academy | [Link]


MITS Academy — Python Programming Course

3.2 Decorators
A decorator is a function that takes another function as input and returns a modified version of it.
Decorators use the @ syntax as syntactic sugar. They are used to add functionality (logging,
timing, authentication checks, caching) to functions without modifying the original code. This
follows the Open/Closed Principle.
The [Link] decorator preserves the original function's name and docstring when using
wrappers. Without it, all decorated functions would show the wrapper function's metadata.
import time
import functools

# Basic decorator
def timer(func):
@[Link](func) # Preserve original function metadata
def wrapper(*args, **kwargs):
start = [Link]()
result = func(*args, **kwargs)
end = [Link]()
print(f"{func.__name__} took {end - start:.4f}s")
return result
return wrapper

@timer
def slow_function(n):
return sum(range(n))

result = slow_function(1000000) # slow_function took 0.0312s

# Decorator with arguments


def repeat(times):
def decorator(func):
@[Link](func)
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

@repeat(3)
def greet(name):
print(f"Hello, {name}!")

greet("Alice") # Prints 3 times

# Stacking decorators
def log(func):
@[Link](func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args} {kwargs}")
return func(*args, **kwargs)
return wrapper

@timer
@log
def add(a, b):

Page 6 | MITS Academy | [Link]


MITS Academy — Python Programming Course

return a + b

add(3, 5) # log runs first, then timer wraps both

3.3 Generators and Iterators


A generator function uses yield instead of return. When called, it returns a generator object
without executing the function body. Each call to next() runs the function until the next yield,
then pauses. Generators are memory-efficient for large sequences because they produce
values one at a time.
An iterator is any object implementing __iter__() and __next__(). Generators are iterators, but
not all iterators are generators. The yield from expression delegates to another generator,
enabling generator composition.
# Generator function
def fibonacci(n):
a, b = 0, 1
count = 0
while count < n:
yield a # Pause and return value
a, b = b, a + b
count += 1

# Lazy evaluation — only generates what you need


fib = fibonacci(10)
print(next(fib)) # 0
print(next(fib)) # 1
print(list(fibonacci(10))) # [0,1,1,2,3,5,8,13,21,34]

# Generator for reading large files without loading all into memory
def read_large_file(filepath):
with open(filepath, "r") as f:
for line in f:
yield [Link]()

# Infinite generator
def counter(start=0):
n = start
while True: # Infinite, but safe because of yield
yield n
n += 1

ctr = counter(5)
print([next(ctr) for _ in range(5)]) # [5, 6, 7, 8, 9]

# yield from — generator delegation


def chain(*iterables):
for it in iterables:
yield from it

result = list(chain([1,2,3], [4,5,6], [7,8,9]))


print(result) # [1, 2, 3, 4, 5, 6, 7, 8, 9]

Page 7 | MITS Academy | [Link]


MITS Academy — Python Programming Course

Module 4: Object-Oriented Programming — Advanced


4.1 Magic (Dunder) Methods
Magic methods (also called dunder methods — double underscore) allow you to define how
objects behave with Python's built-in operations. __str__ defines the human-readable string
representation. __repr__ defines the developer-facing representation. __len__, __getitem__,
__contains__, and __iter__ make custom objects work with len(), [], in, and for loops.
Operator overloading uses magic methods to define behavior for +, -, *, ==, <, etc. This allows
your custom classes to work seamlessly with Python's operators. For example, you can define +
on a Vector class to add vectors mathematically.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
return f"Vector({self.x}, {self.y})"

def __str__(self):
return f"({self.x}, {self.y})"

def __add__(self, other): # v1 + v2


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

def __mul__(self, scalar): # v * 3


return Vector(self.x * scalar, self.y * scalar)

def __rmul__(self, scalar): # 3 * v


return self.__mul__(scalar)

def __eq__(self, other): # v1 == v2


return self.x == other.x and self.y == other.y

def __abs__(self): # abs(v)


return (self.x**2 + self.y**2) ** 0.5

def __len__(self): # len(v)


return 2 # 2D vector has 2 components

v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # (4, 6)
print(v1 * 2) # (6, 8)
print(3 * v1) # (9, 12)
print(abs(v1)) # 5.0 (Pythagorean theorem: sqrt(9+16))
print(v1 == Vector(3,4)) # True

4.2 Class Methods, Static Methods and Properties


Python has three types of methods in a class: instance methods (operate on a specific object,
take self), class methods (operate on the class itself, take cls, use @classmethod), and static
methods (no access to instance or class, use @staticmethod — just utility functions logically
grouped in the class).

Page 8 | MITS Academy | [Link]


MITS Academy — Python Programming Course

The @property decorator creates getter methods that are accessed like attributes. It provides
controlled access to private attributes with validation. You can also define setters and deleters
for full property control.
class Temperature:
def __init__(self, celsius):
self._celsius = celsius # Protected (convention)

@property
def celsius(self):
return self._celsius

@[Link]
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero!")
self._celsius = value

@property
def fahrenheit(self):
return self._celsius * 9/5 + 32

@classmethod
def from_fahrenheit(cls, f): # Alternative constructor
return cls((f - 32) * 5/9)

@staticmethod
def is_valid(celsius):
return celsius >= -273.15

# Usage
t = Temperature(100)
print([Link]) # 100
print([Link]) # 212.0
[Link] = 0 # Uses setter
print([Link]) # 32.0

t2 = Temperature.from_fahrenheit(98.6) # Class method


print(f"{[Link]:.2f}") # 37.00

print(Temperature.is_valid(-300)) # False (Static method)

4.3 Abstract Classes and Interfaces


The abc (Abstract Base Class) module lets you define abstract classes — classes that cannot
be instantiated and define a contract that subclasses must fulfill. Abstract methods must be
overridden in any concrete subclass. This enforces a consistent interface across related
classes.
Abstract classes in Python can have both abstract methods (must be overridden) and concrete
methods (optional to override). This is more flexible than Java interfaces, which (before Java 8)
could only have abstract methods.
from abc import ABC, abstractmethod
import math

class Shape(ABC):
def __init__(self, color="white"):

Page 9 | MITS Academy | [Link]


MITS Academy — Python Programming Course

[Link] = color

@abstractmethod
def area(self) -> float:
"""Must be implemented by subclasses"""
pass

@abstractmethod
def perimeter(self) -> float:
pass

def describe(self):
"""Concrete method — available to all subclasses"""
return (f"{self.__class__.__name__}: "
f"area={[Link]():.2f}, "
f"perimeter={[Link]():.2f}, "
f"color={[Link]}")

class Circle(Shape):
def __init__(self, radius, color="white"):
super().__init__(color)
[Link] = radius

def area(self):
return [Link] * [Link] ** 2

def perimeter(self):
return 2 * [Link] * [Link]

class Rectangle(Shape):
def __init__(self, width, height, color="white"):
super().__init__(color)
[Link] = width
[Link] = height

def area(self):
return [Link] * [Link]

def perimeter(self):
return 2 * ([Link] + [Link])

shapes = [Circle(5, "red"), Rectangle(4, 6, "blue")]


for s in shapes:
print([Link]())

Module 5: Modules, Packages and Virtual


Environments
5.1 Creating and Importing Modules
A module is a Python file containing reusable code — functions, classes, and variables.
Python's module system allows you to organize large programs into smaller, manageable files.
The import statement loads a module. You can import specific names with from...import or
import everything with *.

Page 10 | MITS Academy | [Link]


MITS Academy — Python Programming Course

A package is a directory containing multiple modules and an __init__.py file. Packages allow
you to organize related modules hierarchically. When you install third-party libraries with pip,
they become importable packages.
# math_utils.py — a custom module
def is_prime(n):
"""Return True if n is prime."""
if n < 2: return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0: return False
return True

def factors(n):
"""Return list of all factors of n."""
return [i for i in range(1, n+1) if n % i == 0]

PI = 3.14159265

# [Link] — import the module


import math_utils # Import whole module
from math_utils import is_prime, PI # Import specific names
from math_utils import factors as f # Import with alias

print(math_utils.is_prime(17)) # True
print(is_prime(17)) # True (direct import)
print(PI) # 3.14159265
print(f(12)) # [1, 2, 3, 4, 6, 12]

# __name__ == "__main__" guard


# Code below only runs when the file is executed directly,
# NOT when imported as a module
if __name__ == "__main__":
print("Primes up to 50:", [n for n in range(2,51) if is_prime(n)])

5.2 Virtual Environments and pip


A virtual environment is an isolated Python environment for a project. It keeps project
dependencies separate — different projects can use different versions of the same library
without conflicts. This is essential professional practice.
pip is Python's package installer. A [Link] file lists all project dependencies with
version numbers. This allows anyone to recreate the exact same environment. Tools like pipenv
and poetry provide more advanced dependency management.
# Virtual environment commands (run in terminal)
# python -m venv venv Create virtual environment
# venv\Scripts\activate Activate on Windows
# source venv/bin/activate Activate on Mac/Linux
# pip install requests pandas Install packages
# pip freeze > [Link] Save dependencies
# pip install -r [Link] Restore from file
# deactivate Exit virtual environment

# Using installed packages


import requests # pip install requests

def get_public_ip():
"""Fetch current public IP address."""

Page 11 | MITS Academy | [Link]


MITS Academy — Python Programming Course

try:
response = [Link]("[Link] timeout=5)
response.raise_for_status() # Raise if HTTP error
data = [Link]()
return data["ip"]
except [Link]:
return "No internet connection"
except [Link]:
return "Request timed out"
except [Link] as e:
return f"HTTP Error: {e}"

# [Link] example:
# requests==2.31.0
# pandas==2.1.0
# numpy==1.26.0
# matplotlib==3.8.0

Module 6: File Handling — Advanced


6.1 Working with CSV and JSON
CSV (Comma-Separated Values) and JSON (JavaScript Object Notation) are the two most
common data file formats. Python's csv module provides DictReader and DictWriter to work with
CSV files as dictionaries. The json module serializes Python objects to JSON strings and
deserializes JSON back to Python objects.
JSON is widely used for API responses and configuration files. Python's [Link]() converts
Python objects to JSON string, and [Link]() parses JSON string to Python objects.
[Link]() writes to a file, [Link]() reads from a file.
import csv
import json
from pathlib import Path

# CSV — Write
students = [
{"name": "Alice", "marks": 92, "grade": "A"},
{"name": "Bob", "marks": 78, "grade": "B"},
{"name": "Carol", "marks": 85, "grade": "B"},
]

with open("[Link]", "w", newline="") as f:


writer = [Link](f, fieldnames=["name","marks","grade"])
[Link]()
[Link](students)

# CSV — Read
with open("[Link]") as f:
reader = [Link](f)
for row in reader:
print(f"{row['name']}: {row['marks']}%")

# JSON — Write
config = {
"app_name": "MITS App",
"version": "2.0",

Page 12 | MITS Academy | [Link]


MITS Academy — Python Programming Course

"database": {"host": "localhost", "port": 5432},


"features": ["auth", "dashboard", "reports"]
}

with open("[Link]", "w") as f:


[Link](config, f, indent=4) # indent for pretty print

# JSON — Read
with open("[Link]") as f:
loaded = [Link](f)
print(loaded["app_name"]) # MITS App
print(loaded["database"]["host"]) # localhost

# JSON to/from string (for APIs)


json_str = [Link](students, indent=2)
data = [Link](json_str)
print(type(data)) # <class 'list'>

Module 7: Error Handling and Context Managers


7.1 Advanced Exception Handling
Custom exceptions allow you to create domain-specific error types that carry meaningful error
information. A custom exception class inherits from Exception or a more specific built-in
exception. The exception hierarchy lets you catch specific errors before more general ones.
The else clause of a try block runs only if NO exception was raised. This is different from putting
code after the except block, which always runs. Use else for code that should only run on
success.
# Custom exception hierarchy
class AppError(Exception):
"""Base exception for our application"""
def __init__(self, message, error_code=None):
super().__init__(message)
self.error_code = error_code

class ValidationError(AppError):
"""Raised when input validation fails"""
pass

class DatabaseError(AppError):
"""Raised for database-related errors"""
pass

def register_student(name, age, marks):


if not name or not isinstance(name, str):
raise ValidationError("Name must be a non-empty string", 400)
if not (0 <= age <= 100):
raise ValidationError(f"Invalid age: {age}", 400)
if not (0 <= marks <= 100):
raise ValidationError(f"Invalid marks: {marks}", 400)
return {"name": name, "age": age, "marks": marks, "status": "registered"}

try:
student = register_student("Alice", 25, 92)
except ValidationError as e:

Page 13 | MITS Academy | [Link]


MITS Academy — Python Programming Course

print(f"Validation failed [{e.error_code}]: {e}")


except DatabaseError as e:
print(f"Database error [{e.error_code}]: {e}")
except AppError as e:
print(f"App error: {e}") # Catches any remaining AppError
else:
print(f"Success: {student}") # Only runs if no exception
finally:
print("Cleanup complete") # Always runs

7.2 Context Managers — with Statement


The with statement is used for resource management — automatically acquiring and releasing
resources (files, network connections, locks) even when exceptions occur. Under the hood, with
calls __enter__() before the block and __exit__() after.
You can create your own context managers in two ways: by implementing __enter__ and
__exit__ in a class, or by using the @contextmanager decorator from contextlib with a generator
function.
from contextlib import contextmanager
import time

# Class-based context manager


class Timer:
def __enter__(self):
[Link] = [Link]()
return self # returned as 'as' variable

def __exit__(self, exc_type, exc_val, exc_tb):


[Link] = [Link]() - [Link]
print(f"Elapsed: {[Link]:.4f}s")
return False # False = don't suppress exceptions

with Timer() as t:
result = sum(range(1_000_000))
print(f"Sum: {result}")

# Generator-based context manager (simpler)


@contextmanager
def managed_resource(name):
print(f"Acquiring {name}")
resource = {"name": name, "active": True}
try:
yield resource # Pause and give control to 'with' block
finally:
resource["active"] = False
print(f"Releasing {name}") # Always runs

with managed_resource("Database Connection") as conn:


print(f"Using: {conn['name']}") # Code inside 'with' block
# Acquiring Database Connection
# Using: Database Connection
# Releasing Database Connection

Page 14 | MITS Academy | [Link]


MITS Academy — Python Programming Course

Module 8: Regular Expressions


8.1 Pattern Matching with re module
Regular expressions (regex) are powerful patterns for matching, searching, and manipulating
text. Python's re module provides full regex support. Common patterns: \d (digit), \w (word
character), \s (whitespace), . (any character), + (one or more), * (zero or more), ? (zero or one),
^ (start), $ (end).
[Link]() finds the first match anywhere in the string. [Link]() matches only at the
beginning. [Link]() returns all matches as a list. [Link]() replaces matches. Compiled patterns
([Link]()) are more efficient when the same pattern is used multiple times.
import re

# Basic patterns
text = "Call us at +91-9876543210 or email info@[Link]"

# Find phone number


phone_pattern = r"\+?\d[\d\-]{9,13}"
phone = [Link](phone_pattern, text)
if phone:
print("Phone:", [Link]()) # +91-9876543210

# Find email
email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
email = [Link](email_pattern, text)
if email:
print("Email:", [Link]()) # info@[Link]

# Validate formats
def is_valid_email(email):
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool([Link](pattern, email))

def is_valid_phone(phone):
pattern = r"^[6-9]\d{9}$" # Indian mobile: starts with 6-9, 10 digits
return bool([Link](pattern, phone))

print(is_valid_email("alice@[Link]")) # True
print(is_valid_email("not-an-email")) # False
print(is_valid_phone("9876543210")) # True

# Groups — extract parts


date_text = "Event on 2025-08-15 and 2025-09-20"
dates = [Link](r"(\d{4})-(\d{2})-(\d{2})", date_text)
for year, month, day in dates:
print(f"Year:{year} Month:{month} Day:{day}")

# Substitution
clean = [Link](r"\s+", " ", "too many spaces")
print(clean) # "too many spaces"

Page 15 | MITS Academy | [Link]


MITS Academy — Python Programming Course

Module 9: Functional Programming


9.1 map, filter, reduce and itertools
Functional programming treats computation as the evaluation of mathematical functions. Python
supports functional programming through higher-order functions — functions that take other
functions as arguments or return them. map(), filter(), and [Link]() are classic
functional tools.
The itertools module provides efficient iterators for complex looping patterns. It is extremely
useful for generating combinations, permutations, infinite sequences, and grouping data. These
tools process data lazily (one item at a time) making them memory-efficient.
from functools import reduce
import itertools

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# map — apply function to every element


squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, ..., 100]

# filter — keep elements where function returns True


evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]

# reduce — combine all elements into one value


total = reduce(lambda acc, x: acc + x, numbers)
print(total) # 55

# List comprehension is often more Pythonic:


squared_lc = [x**2 for x in numbers]
evens_lc = [x for x in numbers if x % 2 == 0]

# itertools examples
# chain — combine multiple iterables
chained = list([Link]([1,2,3], [4,5], [6]))
print(chained) # [1, 2, 3, 4, 5, 6]

# combinations and permutations


teams = ["A", "B", "C", "D"]
matches = list([Link](teams, 2))
print(f"Possible matches: {len(matches)} — {matches[:3]}")
# [('A', 'B'), ('A', 'C'), ('A', 'D')...]

# groupby — group consecutive equal elements


data = [("Alice","Science"),("Bob","Science"),("Carol","Arts"),
("David","Arts")]
for subject, students in [Link](data, key=lambda x: x[1]):
names = [s[0] for s in students]
print(f"{subject}: {names}")

Page 16 | MITS Academy | [Link]


MITS Academy — Python Programming Course

Module 10: Concurrency and Performance


10.1 Multithreading and Multiprocessing
Python's GIL (Global Interpreter Lock) prevents true parallel execution of Python threads.
However, threading is still useful for I/O-bound tasks (file reading, network requests) where
threads spend time waiting. For CPU-bound tasks (heavy computation), use multiprocessing to
bypass the GIL.
The [Link] module provides a high-level interface for both threading and
multiprocessing. ThreadPoolExecutor manages a pool of threads. ProcessPoolExecutor
manages a pool of processes. Both support map() for parallel processing of iterables.
import threading
import time
from [Link] import ThreadPoolExecutor, ProcessPoolExecutor

# Threading — good for I/O-bound tasks


def download_file(filename):
[Link](1) # Simulates network delay
print(f"Downloaded: {filename}")
return f"{filename}_data"

files = ["[Link]", "[Link]", "[Link]", "video.mp4"]

# Sequential — 4 seconds total


start = [Link]()
for f in files:
download_file(f)
print(f"Sequential: {[Link]()-start:.1f}s") # ~4.0s

# Concurrent threads — ~1 second total


start = [Link]()
with ThreadPoolExecutor(max_workers=4) as executor:
results = list([Link](download_file, files))
print(f"Concurrent: {[Link]()-start:.1f}s") # ~1.0s

# CPU-bound task — use multiprocessing


def is_prime(n):
if n < 2: return False
for i in range(2, int(n**0.5)+1):
if n % i == 0: return False
return True

numbers = list(range(10000, 10100))

# Parallel processing on multiple CPU cores


with ProcessPoolExecutor() as executor:
results = list([Link](is_prime, numbers))
primes = [n for n, p in zip(numbers, results) if p]
print(f"Primes found: {len(primes)}")

Page 17 | MITS Academy | [Link]


MITS Academy — Python Programming Course

Assignments
Assignment 1: Intermediate Python
• Write a decorator @retry(times=3, delay=1) that retries a function if it raises an exception.
• Implement a generic memoize decorator using functools that caches function results.
• Write a generator that produces all prime numbers up to n using the Sieve of
Eratosthenes.
• Create a custom context manager for a simple database transaction (begin,
commit/rollback).
• Write a program using regex to extract all URLs from a block of HTML text.

Assignment 2: OOP and Functional Programming


• Implement a Stack and Queue using Python's [Link] with full dunder method
support (__len__, __contains__, __repr__).
• Create a Shape hierarchy (Circle, Rectangle, Triangle) with @property for area and
perimeter. Override __lt__ to compare shapes by area.
• Write a functional data pipeline: load CSV → filter → transform → sort → export to JSON
using only list comprehensions, map, and filter.
• Build a simple event system: EventEmitter class with on(event, handler) and emit(event,
data) methods.
• Implement a LRU Cache (Least Recently Used) using an OrderedDict.

Projects
Project 1: Task Management CLI Application
Build a feature-rich command-line task manager:
• Task class with: id, title, description, priority (High/Medium/Low), status (Todo/In
Progress/Done), due_date, tags
• TaskManager class: add, update, delete, search (by title or tag), filter (by status or priority)
• Persist data to a JSON file; load on startup, save on every change
• Sort tasks by priority or due date
• Use @property for due_date with validation, custom exceptions for invalid operations
• CLI menu with numbered options and proper input validation

Project 2: Web Scraper and Data Analyzer


Build a web scraping and analysis tool:
• Use requests and BeautifulSoup to scrape a public website (e.g., [Link])
• Extract: book titles, prices, ratings, and availability

Page 18 | MITS Academy | [Link]


MITS Academy — Python Programming Course

• Store scraped data in a CSV file


• Analyze: average price per rating, most common rating, price distribution
• Use generators to process data lazily for memory efficiency
• Implement retry logic with a decorator for failed requests
• Export a summary report as a formatted text file

Page 19 | MITS Academy | [Link]

You might also like