Introduction
Python is a high-level, interpreted, general-purpose language designed for clarity and developer productivity. Its syntax is compact and readable, which lowers maintenance cost and makes collaboration easier in large codebases.
Python is interpreted and dynamically typed. Code is executed by an interpreter, and variables can change type at runtime. This accelerates prototyping and iteration, but robust testing and linting are important for long-term reliability.
Python supports multiple paradigms: procedural programming for scripts, object‑oriented programming for structured systems, and functional programming for composable data flows. This flexibility makes Python suitable for quick automation as well as production‑grade services.
Why Python Is Popular
- Readable syntax and consistent community conventions
- Massive standard library and third‑party ecosystem (pip)
- Excellent tooling for data science, AI/ML, web, and automation
- Cross‑platform support (Windows, macOS, Linux)
How Python Runs Your Code
Python source code is compiled into bytecode and executed by the Python Virtual Machine (PVM). This model keeps development fast and portable, while still allowing native extensions for speed when needed.
Common Use Cases
- Data pipelines, analytics, and visualization
- Machine learning and AI model development
- REST APIs and backend services
- Automation scripts and tooling
- Scientific and numerical computing
Key design points: clear syntax, batteries‑included standard library, dynamic typing, and easy integration with C/C++.
Applications
Python is used across industries because it balances developer speed with a rich ecosystem. Below are major application areas, typical tools, and real‑world use cases.
- Web Development — Django, Flask, FastAPI for REST APIs, admin panels, and full‑stack web apps.
- Data Science & Analytics — NumPy, pandas, Matplotlib/Seaborn for data cleaning, reporting, and visualization.
- Machine Learning & AI — scikit‑learn, TensorFlow, PyTorch for model training, evaluation, and deployment.
- Automation & Scripting — file processing, ETL pipelines, web scraping, and repetitive task automation.
- DevOps & Cloud — infrastructure automation, CI/CD scripts, and cloud SDKs (AWS, Azure, GCP).
- Desktop Applications — PyQt, Tkinter, Kivy for cross‑platform GUI tools.
- Embedded / IoT — MicroPython and CircuitPython for microcontrollers and sensors.
- Finance & Quant — time‑series analysis, backtesting, and algorithmic trading prototypes.
- Cybersecurity — automation of scans, log analysis, and custom security tooling.
Why Teams Choose Python
- Faster delivery due to concise syntax and strong libraries
- Easy integration with databases, APIs, and legacy systems
- Scales from prototypes to production with the right architecture
Features
Python combines simplicity with powerful features that scale from quick scripts to large systems. Below are the most important features and what they mean in practice.
- Readable, minimal syntax — clean indentation‑based structure improves clarity and collaboration.
- Dynamic typing — variables can hold any type; great for rapid development, but benefits from type hints in large projects.
- Duck typing — focus on behavior rather than explicit types, enabling flexible and composable code.
- Automatic memory management — garbage collection handles allocation/deallocation, reducing memory leaks.
- Multi‑paradigm support — procedural, object‑oriented, and functional styles in the same codebase.
- Extensive standard library — “batteries included” modules for networking, files, data formats, and more.
- Massive ecosystem — PyPI provides libraries for web, data, AI/ML, automation, and scientific computing.
- Interoperability — integrate with C/C++/Java through extensions, FFI, and bindings for performance.
- Cross‑platform — runs on Windows, macOS, and Linux with consistent behavior.
- Interactive workflows — REPL and Jupyter notebooks enable quick exploration and data analysis.
Developer Productivity Benefits
- Faster prototyping and iteration
- Concise code with high readability
- Rich testing, debugging, and packaging tools
Versions
Python 2 reached end‑of‑life in 2020 and should not be used for new development. Modern projects should use Python 3. If possible, target the latest stable release for better performance, security fixes, and language features.
Why Version Choice Matters
- Newer versions bring performance improvements (often significant)
- Typing enhancements improve maintainability and tooling
- Async features and standard library upgrades expand capabilities
- Security and bug fixes are only shipped to supported versions
Major Python 3 Milestones
- 3.7+ — data classes, contextvars, improved async support
- 3.8 — walrus operator (:=), faster startup
- 3.9 — dictionary merge (|), type hinting improvements
- 3.10 — structural pattern matching (match/case)
- 3.11+ — major performance gains and better error messages
Check Your Python Version
# check version import sys print(sys.version)
Basic Examples / Quick refresher
A quick refresher of core Python patterns: variables, control flow, comprehensions, functions, and generators. These snippets are short but represent common real‑world idioms.
1️⃣ Hello World + Variables
# hello world msg = "Hello, world!" print(msg)
2️⃣ Conditionals + Loops
nums = [1, 2, 3, 4, 5]
for n in nums:
if n % 2 == 0:
print(n, "is even")
else:
print(n, "is odd")
3️⃣ List Comprehension + Generator
nums = [1, 2, 3, 4, 5] squares = [x * x for x in nums] g = (x * x for x in nums) # generator print(squares) print(list(g))
4️⃣ Functions + Default Arguments
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Ava"))
print(greet("Ava", "Welcome"))
5️⃣ Small Script Example — Fibonacci Generator
Iterator + generator for efficient sequences.
def fib_gen(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
for x in fib_gen(10):
print(x)
Identifiers & Naming Conventions
Identifiers are the names you give to variables, functions, classes, and modules. Clear, consistent naming improves readability and reduces bugs in large codebases.
Rules for Valid Identifiers
- Must start with a letter (A–Z, a–z) or underscore (
_) - Can contain letters, digits, and underscores
- Case‑sensitive (
countandCountare different) - Cannot be a Python keyword (e.g.,
class,for,def)
PEP 8 Naming Conventions
- snake_case — functions, variables, method names
- PascalCase — class names
- UPPER_SNAKE_CASE — constants and configuration values
- _leading_underscore — internal/private use by convention
- __double_leading — name mangling for class internals
Examples
# variables and functions
total_price = 1200
def calculate_tax(amount):
return amount * 0.18
# class name
class PaymentProcessor:
pass
# constant
MAX_RETRIES = 3
Best Practices
- Prefer descriptive names over short ones
- Avoid single‑letter names except for short loops or math
- Use consistent prefixes/suffixes for related variables
- Name booleans with
is_,has_, orcan_(e.g.,is_active)
Good names are more valuable than comments.
Keywords (Reserved Words)
Keywords are reserved words built into the Python language. You cannot use them as identifiers (variable, function, or class names) because they define the syntax of the language.
Why Keywords Matter
- They represent core syntax like control flow (
if,for,while) - They define structure for functions, classes, and exceptions
- They ensure the interpreter can parse code unambiguously
Examples of Keywords
if, else, elif, for, while, break, continue, def, class, return, try, except, finally, raise, with, as, import, from, lambda, yield, True, False, None
Get the Full List at Runtime
Python’s keyword list can change between versions, so you can query it dynamically.
import keyword print(keyword.kwlist)
class_ or from_).
Operators in Python
Operators are special symbols used to perform operations on values and variables. Python provides a rich set of operators that support mathematical calculations, comparisons, logical decisions, and bit-level operations.
1️⃣ Arithmetic Operators
Used for mathematical calculations.
a = 10 b = 3 print(a + b) # Addition print(a - b) # Subtraction print(a * b) # Multiplication print(a / b) # Division (float) print(a // b) # Floor division print(a % b) # Modulus print(a ** b) # Exponentiation
2️⃣ Comparison (Relational) Operators
Used to compare two values and return a Boolean result.
x = 5 y = 10 print(x == y) # Equal print(x != y) # Not equal print(x > y) print(x < y) print(x >= y) print(x <= y)
3️⃣ Assignment Operators
Used to assign and update values.
x = 10 x += 5 # x = x + 5 x -= 3 x *= 2 x /= 4 x %= 3 print(x)
4️⃣ Logical Operators
Used to combine conditional statements.
a = True b = False print(a and b) # AND print(a or b) # OR print(not a) # NOT
5️⃣ Bitwise Operators
Operate on binary representations of integers. Mostly used in low-level programming, networking, and encryption.
a = 5 # 0101 b = 3 # 0011 print(a & b) # AND print(a | b) # OR print(a ^ b) # XOR print(~a) # NOT print(a << 1) # Left shift print(a >> 1) # Right shift
6️⃣ Membership Operators
Used to test whether a value exists in a sequence.
nums = [1, 2, 3, 4] print(3 in nums) print(10 not in nums)
7️⃣ Identity Operators
Identity operators compare memory locations, not values.
a = [1, 2, 3] b = [1, 2, 3] c = a print(a is b) # False print(a is c) # True print(a is not b)
== for value comparison and
is only when checking object identity (e.g. None).
8️⃣ Operator Precedence
Determines the order in which operations are evaluated.
result = 10 + 5 * 2 ** 2 print(result) # 30
Order: () → ** → *, /, //, % → +, - → comparisons → logical
9️⃣ Ternary Operator (Conditional Expression)
A short form of if-else.
age = 18 status = "Adult" if age >= 18 else "Minor" print(status)
🔟 Chained Comparisons
Python allows multiple comparisons in a single statement.
x = 15 print(10 < x < 20)
1️⃣1️⃣ Operator Overloading
Python allows redefining operators for user-defined objects using magic methods.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2
print(p3.x, p3.y)
1️⃣2️⃣ Real-World Example
salary = 50000
bonus = 5000
total_pay = salary + bonus if bonus > 0 else salary
print("Total Pay:", total_pay)
1️⃣3️⃣ Common Mistakes
- Using
isinstead of== - Confusing
/and// - Ignoring operator precedence
- Overusing bitwise operators unnecessarily
1️⃣4️⃣ Best Practices
- Use parentheses to improve readability
- Prefer readable logic over compact expressions
- Use ternary operator only for simple conditions
- Avoid operator overloading unless truly needed
Python Data Types
Data types define the type of value a variable can hold. Python is a dynamically typed language, meaning you don’t need to declare data types explicitly.
1️⃣ Built-in Data Types Overview
Text Type → str Numeric Types → int, float, complex Sequence Types → list, tuple, range Mapping Type → dict Set Types → set, frozenset Boolean Type → bool Binary Types → bytes, bytearray, memoryview None Type → NoneType
2️⃣ Numeric Data Types
🔹 int (Integer)
a = 10 b = -25 c = 100000 print(type(a))
🔹 float (Decimal Numbers)
x = 3.14 y = -0.5 print(type(x))
🔹 complex (Real + Imaginary)
z = 2 + 3j print(z.real) print(z.imag)
3️⃣ Boolean Data Type
a = True b = False print(a and b) print(a or b) print(not a)
4️⃣ String Data Type (str)
name = "Python" msg = 'Learning Python' print(name[0]) print(msg.upper()) print(msg.lower())
5️⃣ Sequence Data Types
🔹 List
lst = [1, 2, 3, "AI", 4.5] lst.append(100) print(lst)
🔹 Tuple
tup = (10, 20, 30) print(tup[1])
🔹 Range
r = range(1, 6) print(list(r))
6️⃣ Mapping Data Type (Dictionary)
student = {
"name": "Rahul",
"age": 20,
"course": "Python"
}
print(student["name"])
print(student.keys())
print(student.values())
7️⃣ Set Data Types
🔹 set
nums = {1, 2, 3, 3, 4}
nums.add(10)
print(nums)
🔹 frozenset (Immutable Set)
fs = frozenset([1, 2, 3]) print(fs)
8️⃣ Binary Data Types
🔹 bytes
b = bytes([65, 66, 67]) print(b)
🔹 bytearray
ba = bytearray([65, 66, 67]) ba[0] = 90 print(ba)
🔹 memoryview
data = bytes([1, 2, 3, 4]) mv = memoryview(data) print(mv[0])
9️⃣ None Data Type
x = None print(type(x))
🔟 Type Conversion (Type Casting)
Explicit conversions: int(), float(), str(), bool(), list(), tuple(), set(), dict(). Beware of data loss on conversion.
print(int(3.9)) # 3
print(float('2.5')) # 2.5
print(bool('')) # False
x = int("10")
y = float("3.5")
z = str(100)
print(x, y, z)
1️⃣1️⃣ Checking Data Type
a = 10 print(type(a)) print(isinstance(a, int))
1️⃣2️⃣ Mutable vs Immutable Data Types
| Mutable | Immutable |
|---|---|
| list | int |
| dict | float |
| set | tuple |
| bytearray | str |
1️⃣3️⃣ Real-World Use Cases
- int/float: Calculations, finance apps
- str: User input, messages
- list: Product lists, tasks
- dict: API responses, JSON data
- set: Unique records
1️⃣4️⃣ Common Mistakes
- Confusing list and tuple mutability
- Forgetting type conversion during input()
- Using mutable objects as dictionary keys
1️⃣5️⃣ Best Practices
- Use appropriate data type for performance
- Prefer immutable types for safety
- Validate data types using isinstance()
Input & Output Statements
Input and output are how a program interacts with the user.
In Python, input() reads user data, and print() displays results.
This section covers common patterns, formatting, and best practices.
input() always returns a string.
Use type conversion when you need numbers.
1️⃣ The print() Function
print() outputs text or values to the console.
It supports multiple values and custom separators.
name = "Ava"
age = 22
print("Name:", name, "Age:", age)
2️⃣ The input() Function
input() reads a line of text from the user and returns it as a string.
name = input("Enter your name: ")
print("Hello", name)
3️⃣ Converting Input to Numbers
Convert to int or float for calculations.
age = int(input("Enter age: "))
price = float(input("Enter price: "))
print("Next year age:", age + 1)
print("Price with tax:", price * 1.18)
4️⃣ Output Formatting with f-Strings
f-strings are the cleanest way to build formatted output.
name = "Ava"
score = 93.456
print(f"Student: {name}, Score: {score:.2f}")
5️⃣ sep and end Parameters
Control how values are separated and how lines end.
print("A", "B", "C", sep=" | ")
print("Loading", end=" ... ")
print("Done!")
6️⃣ Reading Multiple Inputs
Use split() to read multiple values in one line.
name, city = input("Enter name and city: ").split()
print(name, "lives in", city)
7️⃣ Reading a List of Numbers
nums = list(map(int, input("Enter numbers: ").split()))
print("Sum:", sum(nums))
8️⃣ Output Alignment (Tabular Print)
items = [("Pen", 10), ("Notebook", 50), ("Marker", 25)]
for name, price in items:
print(f"{name:<10} | {price:>5}")
9️⃣ Handling Invalid Input (Basic)
try:
age = int(input("Enter age: "))
print("Age:", age)
except ValueError:
print("Please enter a valid number.")
🔟 Real-World Example — Simple Bill
name = input("Customer name: ")
qty = int(input("Quantity: "))
price = float(input("Price per item: "))
total = qty * price
print(f"Customer: {name}")
print(f"Total Bill: ₹{total:.2f}")
Lists in Python
A list is an ordered, mutable collection used to store multiple items in a single variable. Lists are one of the most commonly used data structures in Python due to their flexibility.
1️⃣ Creating Lists
# Empty list lst1 = [] # List with elements lst2 = [1, 2, 3, "python", 4.5] # Using list() constructor lst3 = list((10, 20, 30)) print(type(lst2))
2️⃣ List Data Types
lst = [1, "AI", True, 3.14, [10, 20], (1, 2)] print(lst)
3️⃣ Accessing List Elements
lst = ["python", "java", "c++", "js"] print(lst[0]) print(lst[-1]) print(lst[1:3])
4️⃣ Modifying List Elements
lst = [10, 20, 30] lst[0] = 100 print(lst)
5️⃣ Adding Elements
lst = [1, 2] lst.append(3) lst.extend([4, 5]) lst.insert(1, 100) print(lst)
6️⃣ Removing Elements
lst = [1, 2, 3, 4, 5] lst.remove(3) lst.pop() lst.pop(1) del lst[0] lst.clear() print(lst)
7️⃣ List Methods (IMPORTANT)
lst = [3, 1, 4, 2, 2] lst.sort() lst.reverse() print(lst) print(lst.count(2)) print(lst.index(4))
8️⃣ Iterating Through Lists
lst = ["a", "b", "c"]
for item in lst:
print(item)
9️⃣ List Comprehensions
# Square numbers squares = [x*x for x in range(1, 6)] print(squares) # Conditional even = [x for x in range(10) if x % 2 == 0] print(even)
🔟 Nested Lists
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(matrix[1][2])
1️⃣1️⃣ Copying Lists
a = [1, 2, 3] b = a.copy() c = a[:] a.append(4) print(a) print(b) print(c)
1️⃣2️⃣ List vs Tuple
| List | Tuple |
|---|---|
| Mutable | Immutable |
| Slower | Faster |
| More memory | Less memory |
| Dynamic data | Fixed data |
1️⃣3️⃣ Using Lists as Stack & Queue
# Stack (LIFO) stack = [] stack.append(10) stack.append(20) stack.pop() # Queue (FIFO) from collections import deque queue = deque([1, 2, 3]) queue.append(4) queue.popleft()
1️⃣4️⃣ Sorting with Custom Logic
data = [(1, 3), (2, 1), (4, 2)] data.sort(key=lambda x: x[1]) print(data)
1️⃣5️⃣ Removing Duplicates
lst = [1, 2, 2, 3, 4, 4] unique = list(set(lst)) print(unique)
1️⃣6️⃣ Real-World Use Cases
- Storing user data
- Task management systems
- Shopping carts
- Data analysis pipelines
- Dynamic UI elements
1️⃣7️⃣ Common Mistakes
- Using shallow copy unintentionally
- Modifying list while iterating
- Confusing append() with extend()
1️⃣8️⃣ Best Practices
- Use list comprehensions where possible
- Avoid deep nesting
- Use tuple for fixed data
- Use deque for queue operations
Shallow Copy vs Deep Copy in Python
When working with lists, dictionaries, or other complex objects in Python, copying objects is a common task. Python provides two ways to copy objects: Shallow Copy and Deep Copy. Understanding the difference matters because changes in one object may or may not affect the other.
1️⃣ What is Copy in Python?
A copy means creating a new object that contains the same data as the original object.
a = [1, 2, 3] b = a b.append(4) print(a) # [1, 2, 3, 4] print(b) # [1, 2, 3, 4]
2️⃣ Shallow Copy in Python
A Shallow Copy creates a new outer object, but nested objects are shared between copies.
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
print("Original:", original)
print("Shallow:", shallow)
shallow[0][0] = 100
print("Original:", original)
print("Shallow:", shallow)
Another Way to Create Shallow Copy
# Using list slicing a = [1, 2, 3] b = a[:] b.append(4) print(a) # [1, 2, 3] print(b) # [1, 2, 3, 4] # Using .copy() a = [1, 2, 3] b = a.copy()
3️⃣ Deep Copy in Python
A Deep Copy creates a completely independent copy of the object and all its nested objects.
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
print("Original:", original)
print("Deep:", deep)
deep[0][0] = 100
print("Original:", original)
print("Deep:", deep)
4️⃣ Memory Representation
Shallow Copy
Original List
|
v
[ [1,2], [3,4] ]
Shallow Copy
|
v
[ [1,2], [3,4] ]
Both point to the SAME inner lists
Deep Copy
Original
[ [1,2], [3,4] ]
Deep Copy
[ [1,2], [3,4] ]
All inner lists are NEW objects
5️⃣ Difference Between Shallow Copy and Deep Copy
| Feature | Shallow Copy | Deep Copy |
|---|---|---|
| Copy level | Only top-level object | Entire object hierarchy |
| Nested objects | Shared | Fully copied |
| Memory usage | Less | More |
| Speed | Faster | Slower |
| Changes affect original | Yes (nested objects) | No |
6️⃣ Practical Example
import copy
student = {
"name": "Ali",
"marks": [80, 90, 85]
}
shallow_copy = copy.copy(student)
deep_copy = copy.deepcopy(student)
shallow_copy["marks"][0] = 50
print(student)
print(shallow_copy)
print(deep_copy)
7️⃣ When to Use Shallow Copy
- Objects do not contain nested mutable objects
- You want better performance
- Nested objects do not need separate copies
8️⃣ When to Use Deep Copy
- Objects contain nested lists, dictionaries, or custom objects
- You want fully independent objects
9️⃣ Summary
- Shallow Copy copies only the outer object and shares inner objects.
- Deep Copy copies both outer and inner objects.
- Use
copy.copy()for shallow copy. - Use
copy.deepcopy()for deep copy.
import copy shallow = copy.copy(obj) deep = copy.deepcopy(obj)
Tuples in Python
A tuple is an ordered, immutable collection used to store multiple items in a single variable. Tuples are faster and more memory-efficient than lists and are commonly used for fixed data.
1️⃣ Creating Tuples
# Empty tuple t1 = () # Single element tuple (comma is mandatory) t2 = (5,) # Multiple elements t3 = (1, 2, 3, "python", 4.5) # Without parentheses (tuple packing) t4 = 10, 20, 30 print(type(t2))
2️⃣ Tuple Data Types
t = (1, "AI", 3.14, True, (1, 2), [3, 4]) print(t)
3️⃣ Accessing Tuple Elements
t = ("python", "java", "c++", "js")
print(t[0])
print(t[-1])
print(t[1:3])
4️⃣ Tuple Unpacking
t = (10, 20, 30) a, b, c = t print(a, b, c) # Using * x, *y = (1, 2, 3, 4) print(x) print(y)
5️⃣ Tuple Immutability
t = (1, 2, 3) # t[0] = 10 ❌ Not allowed # But mutable elements inside tuple CAN change t2 = (1, [2, 3]) t2[1].append(4) print(t2)
6️⃣ Tuple Operations
a = (1, 2) b = (3, 4) print(a + b) # Concatenation print(a * 3) # Repetition
7️⃣ Tuple Methods
t = (1, 2, 3, 2, 4) print(t.count(2)) print(t.index(3))
count() and index()
8️⃣ Iterating Through Tuples
t = ("a", "b", "c")
for item in t:
print(item)
9️⃣ Membership Testing
t = ("python", "java")
print("python" in t)
print("c++" not in t)
🔟 Tuple vs List
| Tuple | List |
|---|---|
| Immutable | Mutable |
| Faster | Slower |
| Less memory | More memory |
| Safe for fixed data | Good for dynamic data |
1️⃣1️⃣ Nested Tuples
t = ((1, 2), (3, 4), (5, 6)) print(t[1][0])
1️⃣2️⃣ Tuple Comprehension (Generator)
gen = (x*x for x in range(5)) print(tuple(gen))
1️⃣3️⃣ Real-World Use Cases
- Returning multiple values from a function
- Coordinates (x, y)
- Database records
- Configuration values
- Dictionary keys
1️⃣4️⃣ Example — Multiple Return Values
def calculate(a, b):
return a+b, a-b, a*b
result = calculate(10, 5)
print(result)
1️⃣5️⃣ Why Tuples are Faster?
Tuples use less memory and do not require dynamic resizing. This makes them faster than lists in read-only operations.
1️⃣6️⃣ Common Mistakes
- Forgetting comma in single-element tuple
- Trying to modify tuple elements
- Confusing tuple with list syntax
1️⃣7️⃣ Best Practices
- Use tuples for fixed data
- Use lists for dynamic data
- Prefer tuples as dictionary keys
Sets in Python
A set is an unordered, mutable collection of unique elements. Sets are optimized for fast membership testing and mathematical set operations.
1️⃣ Creating Sets
# Empty set
s1 = set()
# Using curly braces
s2 = {1, 2, 3, 4}
# Remove duplicates automatically
nums = {1, 2, 2, 3, 4, 4}
print(nums)
{} creates an empty dictionary, not a set.
Always use set() for empty sets.
2️⃣ Set Data Types Allowed
# Valid
s = {1, "python", 3.14, (1, 2)}
# Invalid (will raise error)
# s = {[1, 2, 3]}
3️⃣ Adding Elements
s = {1, 2, 3}
s.add(4)
s.update([5, 6, 7])
print(s)
4️⃣ Removing Elements
s = {1, 2, 3, 4}
s.remove(3) # Error if not found
s.discard(5) # No error
s.pop() # Removes random element
s.clear() # Removes all elements
print(s)
5️⃣ Membership Testing
langs = {"python", "java", "c++"}
print("python" in langs)
print("go" not in langs)
6️⃣ Set Operations (MOST IMPORTANT)
Union ( | )
a = {1, 2, 3}
b = {3, 4, 5}
print(a | b)
print(a.union(b))
Intersection ( & )
print(a & b) print(a.intersection(b))
Difference ( - )
print(a - b) print(b - a)
Symmetric Difference ( ^ )
print(a ^ b) print(a.symmetric_difference(b))
7️⃣ Subset, Superset & Disjoint
a = {1, 2}
b = {1, 2, 3, 4}
print(a.issubset(b))
print(b.issuperset(a))
print(a.isdisjoint({5, 6}))
8️⃣ Set Comprehensions
# Square of numbers
squares = {x*x for x in range(1, 6)}
print(squares)
# Conditional
even = {x for x in range(10) if x % 2 == 0}
print(even)
9️⃣ Frozen Sets (Immutable Sets)
fs = frozenset([1, 2, 3]) # fs.add(4) ❌ Not allowed print(fs)
frozenset when you need a set as a dictionary key
or when immutability is required.
🔟 Copying Sets
a = {1, 2, 3}
b = a.copy()
a.add(4)
print(a)
print(b)
1️⃣1️⃣ Removing Duplicates Using Sets
nums = [1, 2, 2, 3, 4, 4, 5] unique_nums = list(set(nums)) print(unique_nums)
1️⃣2️⃣ Real-World Use Cases
- Removing duplicate data
- Fast lookup (membership checking)
- Finding common items between datasets
- Permission and role management
- Tag systems
1️⃣3️⃣ Example — Common Students
python_students = {"Aman", "Riya", "John"}
java_students = {"John", "Riya", "Sara"}
common = python_students & java_students
print(common)
1️⃣4️⃣ Performance Advantage
Set lookup operations (in) are O(1) on average,
making them much faster than lists for large datasets.
1️⃣5️⃣ Common Mistakes
- Expecting order preservation
- Using mutable objects as set elements
- Confusing set and dictionary syntax
1️⃣6️⃣ Best Practices
- Use sets for uniqueness and fast lookup
- Prefer set operations instead of loops
- Use
frozensetwhen immutability is needed
Dictionaries in Python
A dictionary is a mutable, unordered (insertion-ordered since Python 3.7+) collection of key–value pairs. Dictionaries are optimized for fast lookup, insertion, and deletion.
1️⃣ Creating Dictionaries
# Empty dictionary
d1 = {}
# Using literals
student = {
"name": "Alice",
"age": 22,
"course": "Python"
}
# Using dict()
d2 = dict(a=1, b=2, c=3)
print(student)
print(d2)
2️⃣ Accessing Values
print(student["name"]) # Direct access
print(student.get("age")) # Safe access
print(student.get("grade", "Not Assigned"))
get() instead of [] when keys may be missing
to avoid KeyError.
3️⃣ Adding & Updating Items
student["age"] = 23 # Update student["city"] = "Delhi" # Add new key print(student)
4️⃣ Removing Items
# pop
student.pop("city")
# popitem (removes last inserted item)
student.popitem()
# del keyword
del student["age"]
print(student)
5️⃣ Dictionary Methods
data = {"a": 1, "b": 2, "c": 3}
print(data.keys())
print(data.values())
print(data.items())
data.clear()
6️⃣ Looping Through Dictionaries
marks = {"Math": 90, "Science": 85, "English": 88}
# Keys
for subject in marks:
print(subject)
# Values
for score in marks.values():
print(score)
# Key-Value pairs
for subject, score in marks.items():
print(subject, "=>", score)
7️⃣ Dictionary Comprehensions
# Squares of numbers
squares = {x: x*x for x in range(1, 6)}
print(squares)
# Conditional comprehension
even_squares = {x: x*x for x in range(10) if x % 2 == 0}
print(even_squares)
8️⃣ Nested Dictionaries
employees = {
"emp1": {"name": "John", "salary": 50000},
"emp2": {"name": "Sara", "salary": 60000}
}
print(employees["emp1"]["name"])
9️⃣ Copying Dictionaries
import copy
original = {"a": [1, 2], "b": 3}
shallow = original.copy()
deep = copy.deepcopy(original)
original["a"].append(99)
print(shallow) # affected
print(deep) # not affected
🔟 Default Dictionary (defaultdict)
Automatically assigns default values for missing keys.
from collections import defaultdict dd = defaultdict(int) dd["a"] += 1 dd["b"] += 2 print(dict(dd))
1️⃣1️⃣ Counter (Frequency Dictionary)
from collections import Counter text = "python programming" freq = Counter(text) print(freq)
1️⃣2️⃣ Merging Dictionaries
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
# Python 3.9+
merged = d1 | d2
print(merged)
1️⃣3️⃣ Dictionary as Switch Case
def add(a, b): return a + b
def sub(a, b): return a - b
operations = {
"add": add,
"sub": sub
}
print(operations["add"](10, 5))
1️⃣4️⃣ Real-World Example
# Student database
students = {
101: {"name": "Aman", "marks": 85},
102: {"name": "Riya", "marks": 92}
}
for roll, info in students.items():
if info["marks"] > 90:
print(info["name"], "is a topper")
1️⃣5️⃣ Common Mistakes
- Using mutable types (list, set) as keys
- Using
[]without checking key existence - Confusing shallow vs deep copy
1️⃣6️⃣ Best Practices
- Use meaningful keys
- Prefer
get()for safe access - Use
defaultdictfor counters and grouping - Keep dictionaries flat when possible
Strings in Python
Strings are sequences of Unicode characters used to represent text. Python strings are immutable, meaning once created, their contents cannot be changed.
1️⃣ Creating Strings
# Single, double, triple quotes s1 = 'Hello' s2 = "World" s3 = '''Multi-line string example''' print(s1, s2) print(s3)
2️⃣ String Immutability
Strings cannot be modified in place. Any operation creates a new string.
s = "Python" # s[0] = 'J' ❌ Error s = "J" + s[1:] print(s)
3️⃣ Indexing & Slicing
text = "Python Programming" print(text[0]) # First character print(text[-1]) # Last character print(text[0:6]) # Python print(text[7:]) # Programming print(text[::-1]) # Reverse
4️⃣ Common String Methods
s = " hello Python "
print(s.lower())
print(s.upper())
print(s.strip())
print(s.replace("Python", "World"))
print(s.count("o"))
print(s.startswith(" he"))
print(s.endswith("on "))
5️⃣ Searching in Strings
text = "Learn Python Programming"
print(text.find("Python")) # index or -1
print(text.index("Python")) # raises error if not found
print("Learn" in text)
6️⃣ String Splitting & Joining
data = "apple,banana,orange"
fruits = data.split(",")
print(fruits)
joined = " | ".join(fruits)
print(joined)
7️⃣ String Formatting
✔ Old Style (%)
name = "Alice"
age = 25
print("Name: %s, Age: %d" % (name, age))
✔ str.format()
print("Name: {}, Age: {}".format(name, age))
print("Age: {a}, Name: {n}".format(n=name, a=age))
✔ f-Strings (Recommended)
print(f"Name: {name}, Age: {age}")
pi = 3.14159
print(f"Pi rounded: {pi:.2f}")
8️⃣ Escape Characters
print("Line1\nLine2")
print("Tabbed\tText")
print("Quote: \"Python\"")
print("Backslash: \\")
9️⃣ Raw Strings
Useful for regular expressions and file paths.
path = r"C:\new_folder\test" print(path)
🔟 Encoding & Decoding
text = "Python"
encoded = text.encode("utf-8")
print(encoded)
decoded = encoded.decode("utf-8")
print(decoded)
1️⃣1️⃣ Regular Expressions (re)
Regular expressions allow pattern matching and text extraction.
import re
email = "contact@technacode.com"
pattern = r"[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}"
match = re.search(pattern, email)
if match:
print("Valid Email:", match.group())
1️⃣2️⃣ String Iteration
for ch in "Python":
print(ch)
1️⃣3️⃣ Checking String Types
s = "Python123" print(s.isalpha()) print(s.isdigit()) print(s.isalnum()) print(s.isspace())
1️⃣4️⃣ Performance Tips
- Use
join()instead of concatenation in loops - Prefer f-strings for formatting
- Avoid repeated string concatenation
# Efficient concatenation words = ["Python", "is", "fast"] result = " ".join(words) print(result)
1️⃣5️⃣ Real-World Example
username = " AdminUser "
clean_name = username.strip().lower()
if clean_name == "adminuser":
print("Access Granted")
else:
print("Access Denied")
1️⃣6️⃣ Common Mistakes
- Trying to modify strings directly
- Using
+repeatedly in loops - Ignoring Unicode/encoding issues
- Overusing regex where simple methods suffice
1️⃣7️⃣ Best Practices
- Use f-strings for clarity and speed
- Normalize input strings (strip, lower)
- Use regex only when necessary
- Be encoding-aware when handling files
Conditional Control Flow Statements
Conditional control flow statements allow a program to decide which block of code should execute based on specific conditions.
1️⃣ What is Control Flow?
Control flow defines the order in which statements are executed in a program. Python executes code line by line by default, but control flow statements change this order based on conditions.
2️⃣ Boolean Conditions
All conditional statements rely on Boolean values:
True or False.
x = 10 print(x > 5) # True print(x == 5) # False
3️⃣ if Statement
The if statement executes a block of code only when the condition is true.
age = 20
if age >= 18:
print("Adult")
4️⃣ if–else Statement
The else block executes when the if condition fails.
temperature = 15
if temperature > 25:
print("Hot day")
else:
print("Cool day")
5️⃣ if–elif–else Ladder
Used when multiple conditions need to be checked sequentially.
score = 78
if score >= 90:
grade = "A"
elif score >= 75:
grade = "B"
elif score >= 60:
grade = "C"
else:
grade = "D"
print("Grade:", grade)
6️⃣ Nested Conditional Statements
An if statement inside another if is called nested if.
username = "admin"
password = "root123"
if username == "admin":
if password == "root123":
print("Access granted")
else:
print("Wrong password")
else:
print("Unknown user")
7️⃣ Comparison Operators
==Equal!=Not equal>,<>=,<=
8️⃣ Logical Operators
Combine multiple conditions using logical operators.
x = 15
if x > 10 and x < 20:
print("Between 10 and 20")
if x < 5 or x > 10:
print("Outside range")
if not x == 0:
print("Non-zero value")
9️⃣ Membership & Identity Conditions
colors = ["red", "blue", "green"]
if "red" in colors:
print("Red exists")
a = 10
b = 10
if a is b:
print("Same object")
🔟 Ternary Conditional Expression
A short one-line conditional statement.
result = "Pass" if score >= 40 else "Fail" print(result)
1️⃣1️⃣ match–case Statement (Python 3.10+)
An alternative to long if–elif ladders for structured decision making.
day = 4
match day:
case 1:
print("Monday")
case 2:
print("Tuesday")
case 3:
print("Wednesday")
case 4:
print("Thursday")
case _:
print("Invalid day")
1️⃣2️⃣ Truthy & Falsy Values
Python treats some values as False even if they are not boolean.
if []:
print("Won't run")
if "":
print("Won't run")
if 1:
print("Will run")
1️⃣3️⃣ Short-Circuit Evaluation
x = 0
if x != 0 and 10/x > 2:
print("Safe")
1️⃣4️⃣ Real-World Example
balance = 5000
withdraw = 3000
if withdraw <= balance:
balance -= withdraw
print("Transaction successful")
else:
print("Insufficient balance")
1️⃣5️⃣ Common Mistakes
- Incorrect indentation
- Using
=instead of== - Overusing nested conditions
- Wrong logical grouping
1️⃣6️⃣ Best Practices
- Keep conditions readable
- Use descriptive variable names
- Avoid deep nesting
- Prefer
match-casefor structured logic
Loops in Python
Loops allow us to execute a block of code repeatedly until a certain condition is met. They are essential for automation, data processing, and logic building.
1️⃣ Why Do We Need Loops?
Without loops, repetitive tasks would require writing the same code again and again, making programs lengthy and error-prone.
# Without loop
print("Hello")
print("Hello")
print("Hello")
# With loop
for i in range(3):
print("Hello")
2️⃣ for Loop
The for loop is used to iterate over a sequence such as list, tuple,
string, dictionary, or range.
for i in range(5):
print(i)
3️⃣ Iterating Over Different Data Types
# List
for item in [10, 20, 30]:
print(item)
# String
for ch in "Python":
print(ch)
# Tuple
for x in (1, 2, 3):
print(x)
4️⃣ for Loop with range()
range(start, stop, step) generates a sequence of numbers.
for i in range(1, 10, 2):
print(i)
5️⃣ while Loop
The while loop runs as long as the condition remains true.
count = 1
while count <= 5:
print(count)
count += 1
6️⃣ Infinite Loop (Use with Caution)
while True:
print("Running...")
break
7️⃣ break Statement
The break statement terminates the loop immediately.
for i in range(10):
if i == 5:
break
print(i)
8️⃣ continue Statement
The continue statement skips the current iteration.
for i in range(6):
if i == 3:
continue
print(i)
9️⃣ pass Statement
The pass statement does nothing and is used as a placeholder.
for i in range(3):
pass
🔟 else with Loops
The else block executes only if the loop completes normally
(without break).
for i in range(5):
print(i)
else:
print("Loop completed")
1️⃣1️⃣ Nested Loops
A loop inside another loop is called a nested loop.
for i in range(1, 4):
for j in range(1, 4):
print(i, j)
1️⃣2️⃣ Looping with enumerate()
Used to get index and value together.
fruits = ["apple", "banana", "mango"]
for index, fruit in enumerate(fruits, start=1):
print(index, fruit)
1️⃣3️⃣ Looping with zip()
Iterates over multiple sequences simultaneously.
names = ["A", "B", "C"]
scores = [80, 90, 85]
for n, s in zip(names, scores):
print(n, s)
1️⃣4️⃣ List Comprehension (Loop Shortcut)
A concise way to create lists using loops.
squares = [x*x for x in range(5)] print(squares)
1️⃣5️⃣ Generator Expressions
Similar to list comprehension but memory-efficient.
gen = (x*x for x in range(5))
for value in gen:
print(value)
1️⃣6️⃣ Real-World Example
transactions = [100, -50, 200, -30]
balance = 0
for t in transactions:
balance += t
print("Final Balance:", balance)
1️⃣7️⃣ Common Loop Mistakes
- Infinite loops due to missing condition update
- Modifying a list while iterating over it
- Using wrong indentation
- Overusing nested loops
1️⃣8️⃣ Best Practices
- Prefer
forloops overwhilewhen possible - Use
enumerate()instead of manual counters - Use comprehensions for readability
- Avoid deeply nested loops
Functions
Reusable blocks of code defined using def. Supports parameters, return values, default arguments, keyword arguments, and lambda functions.
def greet(name):
return f"Hello {name}"
1️⃣ What is a Function?
A function is a reusable block of code that performs a specific task. Functions help reduce code repetition, improve readability, and make programs easier to maintain.
# basic function
def add(a, b):
return a + b
print(add(10, 20))
2️⃣ Why Use Functions?
- Code reusability
- Better readability
- Easier debugging
- Modular programming
- Cleaner and scalable code
3️⃣ Function Syntax Explained
def function_name(parameters):
# function body
return value
def→ keyword to define functionparameters→ inputs to functionreturn→ sends result back
4️⃣ Function Parameters & Arguments
🔹 Positional Arguments
def multiply(a, b):
return a * b
print(multiply(3, 4))
🔹 Keyword Arguments
print(multiply(b=5, a=2))
🔹 Default Arguments
def greet(name="User"):
return f"Hello {name}"
print(greet())
print(greet("Nafees"))
🔹 Variable Length Arguments (*args)
def total(*numbers):
return sum(numbers)
print(total(1, 2, 3, 4))
🔹 Keyword Variable Arguments (**kwargs)
def profile(**info):
for key, value in info.items():
print(key, ":", value)
profile(name="Ali", age=22, city="Delhi")
5️⃣ Return vs Print
def square(x):
return x * x
result = square(5)
print(result)
return sends value back to caller,
while print only displays output.
6️⃣ Lambda (Anonymous) Functions
Lambda functions are small, one-line functions without a name.
square = lambda x: x * x print(square(6))
# lambda with map numbers = [1, 2, 3, 4] result = list(map(lambda x: x * 2, numbers)) print(result)
7️⃣ Function Scope (Local & Global)
x = 10 # global
def show():
x = 5 # local
print(x)
show()
print(x)
🔹 global Keyword
count = 0
def increment():
global count
count += 1
increment()
print(count)
8️⃣ Nested Functions
def outer():
def inner():
return "Inner function"
return inner()
print(outer())
9️⃣ Functions as Arguments
def shout(text):
return text.upper()
def whisper(text):
return text.lower()
def speak(func):
print(func("Hello World"))
speak(shout)
speak(whisper)
🔟 Recursive Functions
A recursive function calls itself. It must have a base condition.
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
print(factorial(5))
1️⃣1️⃣ Docstrings & Help
def add(a, b):
"""Returns sum of two numbers"""
return a + b
print(help(add))
1️⃣2️⃣ Real-World Example
def calculate_bill(amount, tax=0.18):
return amount + (amount * tax)
print(calculate_bill(1000))
print(calculate_bill(1000, 0.05))
1️⃣3️⃣ Best Practices
- Use meaningful function names
- Keep functions small
- One function = one responsibility
- Use docstrings
- Avoid global variables
Modules & Packages
Organize code into modules (.py) and packages (folders with __init__.py). Use virtual environments (`venv` / `pipenv` / `poetry`).
# import styles import math from collections import deque from mypkg.module import func
1️⃣ What is a Module?
A module is a single Python file (.py) containing variables,
functions, and classes. Modules help split large programs into smaller, manageable files.
# math_module.py
def add(a, b):
return a + b
def sub(a, b):
return a - b
# main.py import math_module print(math_module.add(10, 5))
2️⃣ Why Use Modules?
- Code reusability
- Better organization
- Easier debugging
- Team collaboration
- Improved maintainability
3️⃣ Different Ways to Import Modules
# import full module import math print(math.sqrt(25)) # import specific items from math import sqrt, pi print(sqrt(16), pi) # aliasing import numpy as np # import everything (not recommended) from math import *
from module import * as it pollutes the namespace.
4️⃣ Built-in vs User-Defined Modules
- Built-in: math, sys, os, json, datetime
- Third-party: numpy, pandas, requests
- User-defined: your own .py files
import os print(os.getcwd()) import sys print(sys.path)
5️⃣ What is a Package?
A package is a collection of modules organized inside a directory.
It usually contains an __init__.py file.
project/ │ ├── main.py ├── mypkg/ │ ├── __init__.py │ ├── math_ops.py │ └── string_ops.py
# math_ops.py
def multiply(a, b):
return a * b
# main.py
from mypkg.math_ops import multiply
print(multiply(3, 4))
6️⃣ __init__.py Explained
__init__.py tells Python that the directory is a package.
It can also initialize package-level variables.
# mypkg/__init__.py from .math_ops import multiply __all__ = ['multiply']
7️⃣ Absolute vs Relative Imports
# absolute import (recommended) from mypkg.math_ops import multiply # relative import (inside package) from .math_ops import multiply
8️⃣ Python Module Search Path
Python searches modules in this order:
- Current directory
- PYTHONPATH
- Standard library
- site-packages
import sys print(sys.path)
9️⃣ Virtual Environments (VERY IMPORTANT)
Virtual environments isolate dependencies for each project.
# create venv python -m venv venv # activate (Windows) venv\Scripts\activate # activate (Mac/Linux) source venv/bin/activate
🔟 Installing Third-Party Packages
# install packages pip install requests numpy pandas # freeze dependencies pip freeze > requirements.txt # install from file pip install -r requirements.txt
1️⃣1️⃣ Real-World Example — Utility Package
# utils/logger.py
def log(msg):
print("[LOG]", msg)
# utils/math_utils.py
def square(x):
return x * x
# main.py
from utils.logger import log
from utils.math_utils import square
log(square(5))
1️⃣2️⃣ Best Practices
- One module = one responsibility
- Use meaningful module names
- Use virtual environments always
- Group related modules into packages
- Avoid circular imports
Object-Oriented Programming (OOP)
Classes, instances, inheritance, method resolution order (MRO), magic methods (dunder methods), properties, dataclasses.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError
class Dog(Animal):
def speak(self):
return 'Woof'
d = Dog('Rex')
print(d.name, d.speak())
Dataclass (convenience for data containers)
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p = Point(1.0, 2.0)
print(p)
1️⃣ Class & Object (Concept)
A class is a blueprint, and an object is a real-world entity created from that blueprint. Example: Car is a class, your car is an object.
class Car:
pass
c1 = Car()
c2 = Car()
2️⃣ Constructor — __init__()
The constructor initializes data when an object is created.
self refers to the current object.
class Student:
def __init__(self, name, marks):
self.name = name
self.marks = marks
s = Student("Aman", 92)
print(s.name, s.marks)
3️⃣ Instance vs Class Variables
class College:
college_name = "ABC College" # class variable
def __init__(self, student):
self.student = student # instance variable
a = College("Rahul")
b = College("Neha")
print(a.college_name, b.college_name)
4️⃣ Types of Methods
- Instance Method — uses object data
- Class Method — uses class data
- Static Method — utility method
class Demo:
def instance_method(self):
print("Instance method")
@classmethod
def class_method(cls):
print("Class method")
@staticmethod
def static_method():
print("Static method")
5️⃣ Inheritance (Code Reusability)
class Parent:
def show(self):
print("Parent class")
class Child(Parent):
pass
c = Child()
c.show()
6️⃣ super() Keyword
class A:
def __init__(self):
print("A constructor")
class B(A):
def __init__(self):
super().__init__()
print("B constructor")
7️⃣ Polymorphism
Same method name, different behavior.
class Dog:
def sound(self):
return "Bark"
class Cat:
def sound(self):
return "Meow"
def make_sound(obj):
print(obj.sound())
make_sound(Dog())
make_sound(Cat())
8️⃣ Encapsulation (Data Hiding)
class Account:
def __init__(self):
self.name = "Public"
self._balance = 5000 # protected
self.__pin = 1234 # private
9️⃣ Abstraction
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def area(self):
return 4 * 4
🔟 Magic / Dunder Methods
class Book:
def __init__(self, pages):
self.pages = pages
def __str__(self):
return f"Book with {self.pages} pages"
b = Book(200)
print(b)
💼 Real-World Example — Bank Account
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def deposit(self, amount):
self.__balance += amount
def get_balance(self):
return self.__balance
acc = BankAccount(1000)
acc.deposit(500)
print(acc.get_balance())
Exception Handling
Use try/except/else/finally. Catch specific exceptions. Create custom exceptions by inheriting from Exception.
try:
val = int('x')
except ValueError as e:
print('Conversion failed:', e)
else:
print('Success')
finally:
print('Cleanup')
1️⃣ What is an Exception?
An exception is a runtime error that interrupts the normal flow of a program. If not handled, the program crashes.
# Example: ZeroDivisionError print(10 / 0)
2️⃣ Why Exception Handling is Important?
- Prevents program crash
- Improves user experience
- Helps debugging
- Used heavily in real-world applications
3️⃣ Common Built-in Exceptions
# NameError
print(x)
# TypeError
print(5 + "5")
# IndexError
lst = [1,2]
print(lst[5])
# KeyError
d = {"a": 1}
print(d["b"])
4️⃣ Multiple Except Blocks
Handle different errors differently.
try:
a = int(input("Enter number: "))
b = int(input("Enter number: "))
print(a / b)
except ZeroDivisionError:
print("Cannot divide by zero")
except ValueError:
print("Invalid input")
except Exception as e:
print("Unknown error:", e)
5️⃣ try–else–finally Explained
- try → risky code
- except → handles error
- else → runs if no error
- finally → always runs
try:
print("Try block")
except:
print("Error occurred")
else:
print("No error occurred")
finally:
print("Always executed")
6️⃣ Raising Exceptions (raise keyword)
Use raise to trigger an exception manually.
age = -5
if age < 0:
raise ValueError("Age cannot be negative")
7️⃣ Custom Exceptions
Create your own exception class for business logic.
class InsufficientBalanceError(Exception):
pass
balance = 1000
withdraw = 2000
if withdraw > balance:
raise InsufficientBalanceError("Not enough balance")
8️⃣ Exception Handling in Functions
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return "Division by zero not allowed"
print(divide(10, 2))
print(divide(10, 0))
9️⃣ Real-World Example — Login System
def login(username, password):
if username != "admin":
raise Exception("Invalid username")
if password != "1234":
raise Exception("Invalid password")
return "Login successful"
try:
print(login("admin", "1234"))
except Exception as e:
print("Login failed:", e)
🔟 Best Practices
- Catch specific exceptions, not generic ones
- Never use empty
except:unless necessary - Use
finallyfor cleanup (files, DB, network) - Log exceptions in production
- Don’t suppress errors silently
File Handling
Use context managers to reliably open/close files. For large files, iterate line-by-line.
# writing and reading
with open('sample.txt', 'w', encoding='utf-8') as f:
f.write('Hello\n')
with open('sample.txt', 'r', encoding='utf-8') as f:
for line in f:
print(line.strip())
1️⃣ What is File Handling?
File handling allows a program to store data permanently on disk, retrieve it later, and manipulate it. Unlike variables, file data is not lost when the program ends.
2️⃣ File Modes Explained
'r'– Read (default)'w'– Write (overwrite)'a'– Append'x'– Create (error if exists)'b'– Binary mode't'– Text mode'+'– Read & Write
# append mode
with open('log.txt', 'a') as f:
f.write('New log entry\n')
3️⃣ Reading Files (Multiple Ways)
# read entire file
with open('data.txt') as f:
content = f.read()
print(content)
# read line by line
with open('data.txt') as f:
for line in f:
print(line.strip())
# read lines as list
with open('data.txt') as f:
lines = f.readlines()
4️⃣ Writing Files Safely
Always use with to ensure files are closed automatically.
lines = ['Apple\n', 'Banana\n', 'Mango\n']
with open('fruits.txt', 'w') as f:
f.writelines(lines)
5️⃣ File Pointer & seek()
The file pointer tracks the current position.
with open('sample.txt', 'r') as f:
print(f.read(5)) # read first 5 chars
f.seek(0) # move pointer to beginning
print(f.read())
6️⃣ Handling Large Files (Memory Efficient)
# process large file line-by-line
with open('bigfile.txt') as f:
for line in f:
process(line) # avoids loading entire file
7️⃣ Binary Files (Images, PDFs)
# copy an image
with open('photo.jpg', 'rb') as src:
with open('copy.jpg', 'wb') as dst:
dst.write(src.read())
8️⃣ File Handling with try–except
try:
f = open('missing.txt')
except FileNotFoundError:
print("File not found")
finally:
print("Done")
9️⃣ Working with CSV Files
import csv
with open('data.csv', newline='') as f:
reader = csv.reader(f)
for row in reader:
print(row)
# writing csv
with open('out.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['Name', 'Age'])
writer.writerow(['Alice', 25])
🔟 Working with JSON Files
import json
data = {'name': 'John', 'age': 30}
# write json
with open('data.json', 'w') as f:
json.dump(data, f, indent=4)
# read json
with open('data.json') as f:
obj = json.load(f)
print(obj)
1️⃣1️⃣ File & Directory Operations (os, pathlib)
import os
print(os.getcwd())
os.mkdir('test_dir')
from pathlib import Path
p = Path('file.txt')
print(p.exists(), p.stat().st_size)
1️⃣2️⃣ Real-World Example — Logging System
from datetime import datetime
def log(msg):
with open('app.log', 'a') as f:
f.write(f"{datetime.now()} - {msg}\n")
log("Application started")
log("User logged in")
1️⃣3️⃣ Best Practices
- Always use
with - Handle exceptions properly
- Use binary mode for non-text files
- Never hardcode file paths (use config)
- Prefer
pathlibfor modern code
Decorators in Python
Decorators are a powerful Python feature used to modify or extend the behavior of functions or classes without changing their actual code.
1️⃣ What is a Decorator?
A decorator is a function that:
- Takes another function as input
- Adds extra functionality
- Returns a new function
# Function without decorator
def greet():
print("Hello!")
greet()
2️⃣ Functions are First-Class Objects
In Python, functions can be:
- Assigned to variables
- Passed as arguments
- Returned from other functions
def say_hello():
print("Hello Python")
x = say_hello
x()
3️⃣ Basic Decorator (Manual Way)
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
def hello():
print("Hello World")
hello = my_decorator(hello)
hello()
4️⃣ Decorator Using @ Syntax
def my_decorator(func):
def wrapper():
print("Before execution")
func()
print("After execution")
return wrapper
@my_decorator
def greet():
print("Welcome to Python")
greet()
@decorator_name syntax is just a shortcut.
5️⃣ Decorators with Arguments
Most real-world functions take arguments.
Decorators must handle them using *args and **kwargs.
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Arguments:", args, kwargs)
return func(*args, **kwargs)
return wrapper
@my_decorator
def add(a, b):
return a + b
print(add(5, 3))
6️⃣ Returning Values from Decorators
def smart_divide(func):
def wrapper(a, b):
if b == 0:
print("Cannot divide by zero")
return None
return func(a, b)
return wrapper
@smart_divide
def divide(a, b):
return a / b
print(divide(10, 2))
print(divide(10, 0))
7️⃣ Decorators with Parameters
Sometimes decorators themselves need arguments. This requires three layers of functions.
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi()
8️⃣ Using functools.wraps (VERY IMPORTANT)
Without wraps, function metadata is lost.
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def demo():
"""This is a demo function"""
pass
print(demo.__name__)
print(demo.__doc__)
functools.wraps in production code.
9️⃣ Multiple Decorators
def decor1(func):
def wrapper():
print("Decor1")
func()
return wrapper
def decor2(func):
def wrapper():
print("Decor2")
func()
return wrapper
@decor1
@decor2
def show():
print("Hello")
show()
🔟 Class-Based Decorators
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self):
print("Before call")
self.func()
print("After call")
@MyDecorator
def hello():
print("Hello from class decorator")
hello()
1️⃣1️⃣ Real-World Decorator Examples
🔹 Timing Function Execution
import time
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print("Execution Time:", end - start)
return result
return wrapper
@timer
def long_task():
sum(range(1000000))
long_task()
🔹 Authorization Check
def login_required(func):
def wrapper(user):
if user != "admin":
print("Access Denied")
return
return func(user)
return wrapper
@login_required
def dashboard(user):
print("Welcome to dashboard")
dashboard("admin")
dashboard("guest")
1️⃣2️⃣ Built-in Python Decorators
@staticmethod@classmethod@property
class Student:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
s = Student("Amit")
print(s.name)
1️⃣3️⃣ Common Mistakes
- Forgetting to return the wrapper
- Not handling arguments properly
- Not using functools.wraps
- Calling function inside decorator instead of returning it
1️⃣4️⃣ Best Practices
- Keep decorators small and reusable
- Use wraps for metadata
- Avoid overusing decorators
- Document decorator behavior clearly
Generators in Python
Generators are a special type of function used to produce values one at a time instead of returning all values at once. They are extremely memory-efficient and are widely used in data processing, streaming, and large-scale applications.
yield keyword instead of return.
1️⃣ What is a Generator?
A generator is a function that:
- Uses
yieldinstead ofreturn - Remembers its state between executions
- Produces values lazily (on demand)
# Normal function
def square_list(n):
result = []
for i in range(n):
result.append(i * i)
return result
print(square_list(5))
# Generator function
def square_gen(n):
for i in range(n):
yield i * i
print(list(square_gen(5)))
2️⃣ yield vs return
return ends the function completely,
while yield pauses execution and saves state.
def demo():
print("Start")
yield 1
print("Middle")
yield 2
print("End")
g = demo()
print(next(g))
print(next(g))
# next(g) would raise StopIteration
3️⃣ Generator Object & next()
Calling a generator function does NOT execute it immediately. It returns a generator object.
def count_up(n):
i = 1
while i <= n:
yield i
i += 1
gen = count_up(3)
print(next(gen))
print(next(gen))
print(next(gen))
4️⃣ Generator with for Loop
Most commonly, generators are consumed using a for loop.
def even_numbers(n):
for i in range(n):
if i % 2 == 0:
yield i
for num in even_numbers(10):
print(num)
5️⃣ Generator Expressions
Similar to list comprehensions, but use () instead of [].
# List comprehension (stores all values) squares_list = [x*x for x in range(5)] # Generator expression (lazy) squares_gen = (x*x for x in range(5)) print(squares_gen) print(list(squares_gen))
6️⃣ Memory Efficiency (IMPORTANT)
Generators do NOT store all values in memory.
import sys lst = [i for i in range(100000)] gen = (i for i in range(100000)) print(sys.getsizeof(lst)) print(sys.getsizeof(gen))
7️⃣ Infinite Generators
Generators can be infinite — use carefully.
def infinite_counter():
i = 1
while True:
yield i
i += 1
counter = infinite_counter()
print(next(counter))
print(next(counter))
8️⃣ Generator with try / finally
Generators can handle cleanup logic using try and finally.
def resource_manager():
print("Open resource")
try:
yield "Using resource"
finally:
print("Close resource")
gen = resource_manager()
print(next(gen))
gen.close()
9️⃣ send() Method in Generators
Generators can receive values using send().
def echo():
while True:
value = yield
print("Received:", value)
g = echo()
next(g) # start generator
g.send("Hello")
g.send("Python")
🔟 yield from (Delegating Generators)
Used to yield values from another generator.
def sub_gen():
yield 1
yield 2
def main_gen():
yield from sub_gen()
yield 3
print(list(main_gen()))
1️⃣1️⃣ Generators vs Iterators
- All generators are iterators
- Not all iterators are generators
- Generators are simpler to write
1️⃣2️⃣ Real-World Generator Examples
🔹 Reading Large Files Line-by-Line
def read_large_file(file):
with open(file) as f:
for line in f:
yield line.strip()
🔹 Streaming Data Processing
def pipeline(data):
for item in data:
yield item * 2
data = range(5)
for value in pipeline(data):
print(value)
🔹 Fibonacci Generator
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(5):
print(next(fib))
1️⃣3️⃣ Common Mistakes
- Using
returninstead ofyield - Trying to reuse exhausted generators
- Forgetting generators run only once
- Using generators when random access is needed
1️⃣4️⃣ Best Practices
- Use generators for large or infinite data
- Prefer generator expressions for readability
- Keep generator logic simple
- Combine generators for pipelines
Regular Expressions (Regex)
Regular Expressions (Regex) are powerful patterns used
to search, match, extract, replace, and validate text.
In Python, regex functionality is provided by the built-in
re module.
1️⃣ Importing the re Module
import re
2️⃣ Basic Regex Functions
re.match()— match from beginningre.search()— search anywherere.findall()— return all matchesre.finditer()— iterator of matchesre.sub()— replace textre.split()— split string
text = "Python is powerful"
print(re.search("power", text))
print(re.findall("o", text))
3️⃣ Metacharacters (Core of Regex)
Special characters that define patterns:
.→ any character^→ start of string$→ end of string*→ 0 or more+→ 1 or more?→ 0 or 1{n,m}→ repetition
text = "aaaaab"
print(re.search("a+b", text))
4️⃣ Character Classes
Match a set or range of characters.
text = "abc123"
print(re.findall("[a-z]", text))
print(re.findall("[0-9]", text))
print(re.findall("[a-zA-Z0-9]", text))
5️⃣ Predefined Character Sets
\d→ digit\D→ non-digit\w→ word character\W→ non-word\s→ whitespace\S→ non-whitespace
text = "User_123"
print(re.findall("\w", text))
6️⃣ Anchors (^ and $)
print(re.match("^Hello", "Hello World"))
print(re.search("World$", "Hello World"))
7️⃣ Grouping & Capturing
Parentheses () are used to capture groups.
text = "My email is user@example.com"
match = re.search("([a-z0-9._]+)@([a-z]+)\.([a-z]+)", text)
print(match.group(0))
print(match.group(1))
print(match.groups())
8️⃣ Non-Capturing Groups
re.search("(?:Mr|Mrs|Ms)\\.\\s[A-Z][a-z]+", "Mr. Smith")
9️⃣ Quantifiers (Greedy vs Lazy)
Regex is greedy by default. Use ? for lazy matching.
text = "content " print(re.search("<.*>", text).group()) print(re.search("<.*?>", text).group())
🔟 Alternation (OR)
print(re.findall("cat|dog", "cat dog tiger cat"))
1️⃣1️⃣ Lookahead & Lookbehind (Advanced)
Positive Lookahead
re.search("Python(?= Developer)", "Python Developer")
Negative Lookahead
re.search("Python(?! Developer)", "Python Engineer")
Lookbehind
re.search("(?<=₹)\d+", "Price ₹500")
1️⃣2️⃣ re.sub() — Replacing Text
text = "My number is 9876543210"
masked = re.sub("\d", "*", text)
print(masked)
1️⃣3️⃣ re.split()
text = "one,two;three four"
print(re.split("[,; ]", text))
1️⃣4️⃣ Flags / Modifiers
re.I→ ignore casere.M→ multilinere.S→ dot matches newlinere.X→ verbose regex
re.search("python", "Python", re.I)
1️⃣5️⃣ Compiled Regular Expressions
Compiling regex improves performance when reused.
pattern = re.compile(r"\d+")
print(pattern.findall("ID 123 and 456"))
1️⃣6️⃣ Common Validation Examples
📧 Email Validation
email_pattern = r"^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$"
print(bool(re.match(email_pattern, "user@example.com")))
📱 Mobile Number
phone_pattern = r"^[6-9]\d{9}$"
print(bool(re.match(phone_pattern, "9876543210")))
🔐 Password Validation
password_pattern = r"^(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&]).{8,}$"
1️⃣7️⃣ Regex for File Parsing
log = "ERROR 2024-01-01 Server failed"
match = re.search(r"\d{4}-\d{2}-\d{2}", log)
print(match.group())
1️⃣8️⃣ Common Mistakes
- Forgetting raw strings (
r"") - Using greedy patterns accidentally
- Overusing regex instead of simple string methods
- Complex unreadable patterns
1️⃣9️⃣ Best Practices
- Use raw strings for regex
- Keep patterns readable
- Comment complex expressions
- Prefer compiled regex for reuse
- Test patterns with sample data