Python Java C++ HTML CSS Bootstrap JavaScript jQuery AngularJS React Node.js TypeScript Django NumPy Pandas Matplotlib Seaborn Machine Learning Deep Learning Decipher XML

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.

Python is widely used for web backends, data analysis, machine learning, automation, DevOps scripts, and scientific computing. It trades raw execution speed for developer speed, but performance‑critical parts can be optimized via compiled extensions (C/C++/Cython), vectorized libraries (NumPy/Pandas), or multiprocessing.

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
In production, pick a version that is actively supported by the Python community and your dependency stack. Lock your runtime version in deployment (e.g., Docker or CI) to avoid surprises.

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)
These patterns show Python’s emphasis on readability: minimal syntax, expressive loops, and high‑level constructs like comprehensions and generators.

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 (count and Count are 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_, or can_ (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)
If you need a name that conflicts with a keyword, append an underscore (e.g., 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.

Operators help make programs expressive and concise. Understanding operator behavior is essential for writing correct logic.

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)
Tip: Use == 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 is instead 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
Operators are heavily used in decision making, data processing, and performance-critical applications.

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.

Python automatically determines the data type based on the value assigned.

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)
Booleans are commonly used in conditions and loops.

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))
None is used to represent absence of value.

🔟 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

MutableImmutable
listint
dictfloat
settuple
bytearraystr

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()
Understanding data types is the foundation of mastering Python. Every advanced concept builds on this knowledge.

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.

Most beginner bugs come from forgetting that 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.

Lists allow duplicate values, maintain insertion order, and can store multiple data types.

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)
List comprehensions provide clean, readable, and faster code.

🔟 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

ListTuple
MutableImmutable
SlowerFaster
More memoryLess memory
Dynamic dataFixed 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
Lists are the backbone of Python programming. Mastering them is essential for writing clean, efficient, and professional code.

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]
Here b is not a copy; it is another reference to the same object.

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

FeatureShallow CopyDeep Copy
Copy levelOnly top-level objectEntire object hierarchy
Nested objectsSharedFully copied
Memory usageLessMore
SpeedFasterSlower
Changes affect originalYes (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.

Once a tuple is created, its values cannot be changed. This immutability makes tuples safe and reliable.

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)
Tuples can contain mutable objects like lists, but the tuple itself cannot be reassigned.

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))
Tuples have only two built-in methods: 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

TupleList
ImmutableMutable
FasterSlower
Less memoryMore memory
Safe for fixed dataGood 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))
Python does not support true tuple comprehensions. Parentheses create a generator expression.

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
Tuples are essential for performance, safety, and clean code in professional Python development.

Sets in Python

A set is an unordered, mutable collection of unique elements. Sets are optimized for fast membership testing and mathematical set operations.

Sets do not allow duplicate values and do not maintain insertion order. Elements must be hashable (immutable types).

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)
Use 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 frozenset when immutability is needed
Sets are extremely powerful when working with data filtering, comparisons, analytics, and algorithm optimization.

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.

Dictionary keys must be hashable (immutable types like str, int, tuple). Values can be of any data type.

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"))
Use 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 defaultdict for counters and grouping
  • Keep dictionaries flat when possible
Dictionaries are the backbone of JSON, APIs, configuration files, caching systems, and most real-world Python applications.

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.

Python uses Unicode by default, making it suitable for internationalization and multilingual applications.

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
Mastery of strings is critical for web development, data processing, automation, and security-related tasks.

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.

Control flow is the backbone of decision-making logic in software systems.

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-case for structured logic
Industry Insight: Conditional control flow is used in authentication, validations, automation systems, business rules, AI decision trees, and APIs.

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.

Python provides two primary loops: for and while.

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
Infinite loops are commonly used in servers, games, and real-time systems.

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 for loops over while when possible
  • Use enumerate() instead of manual counters
  • Use comprehensions for readability
  • Avoid deeply nested loops
Industry Usage: Loops are heavily used in data analysis, backend APIs, automation scripts, AI training loops, and system monitoring.

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 function
  • parameters → inputs to function
  • return → 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)
Important: 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
Industry Tip: Functions are the foundation of clean code, APIs, frameworks, data pipelines, and automation scripts.

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 *
Best Practice: Avoid 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:

  1. Current directory
  2. PYTHONPATH
  3. Standard library
  4. 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
Industry Tip: Clean module & package structure is critical for Django, Flask, FastAPI, Data Science pipelines, and large enterprise applications.

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())
Best Practice: Prefer composition over inheritance unless a clear "is-a" relationship exists.

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)
Exceptions help detect errors safely without stopping the entire application.

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 finally for cleanup (files, DB, network)
  • Log exceptions in production
  • Don’t suppress errors silently
Industry Tip: Exception handling is critical in APIs, payment systems, authentication, and database operations.

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.

Files are heavily used in logging, configuration, databases, reports, backups, and data pipelines.

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 pathlib for modern code
Industry Tip: File handling is used in backups, logging, ETL pipelines, data science workflows, and configuration management.

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.

Decorators follow the principle of Open–Closed Design: open for extension, closed for modification.

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()
The @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__)
Always use 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
Decorators are widely used in frameworks like Django, Flask, FastAPI, logging systems, authentication, caching, and performance monitoring.

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.

Generators are based on the iterator protocol and use the yield keyword instead of return.

1️⃣ What is a Generator?

A generator is a function that:

  • Uses yield instead of return
  • 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))
Generator expressions are best when working with large datasets.

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 return instead of yield
  • 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
Generators are heavily used in Python internals, data science, web frameworks, streaming systems, and asynchronous programming.

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.

Regex is heavily used in data validation, log analysis, web scraping, NLP, compilers, and cybersecurity.

1️⃣ Importing the re Module

import re

2️⃣ Basic Regex Functions

  • re.match() — match from beginning
  • re.search() — search anywhere
  • re.findall() — return all matches
  • re.finditer() — iterator of matches
  • re.sub() — replace text
  • re.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 case
  • re.M → multiline
  • re.S → dot matches newline
  • re.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
Regex is powerful but dangerous when overused. Use it when pattern matching is required — otherwise prefer normal string methods.