Programming Language
A programming language is a formal set of instructions that can be used to produce various kinds of output. It enables humans to communicate logic to a computer in a structured, unambiguous way.
Programming languages are broadly classified into:
- Low-level languages: Machine language (binary 0s and 1s) and Assembly language. Very fast, hardware-specific.
- High-level languages: Languages like Python, Java, C++ — closer to human language, portable, and easier to learn.
- Scripting languages: Interpreted languages used to automate tasks (e.g., Python, Bash, JavaScript).
A programming language is defined by its syntax (rules of structure) and semantics (meaning of the statements). The computer does not understand high-level code directly — it is translated by an interpreter or compiler.
List 5 programming languages and classify each as compiled, interpreted, or hybrid.
History and Origin of Python Language
Python was conceived in the late 1980s by Guido van Rossum at Centrum Wiskunde & Informatica (CWI), Netherlands, as a successor to the ABC programming language.
| Version | Year | Key Milestone |
|---|---|---|
| Python 0.9.0 | 1991 | First published release |
| Python 1.0 | 1994 | lambda, map, filter added |
| Python 2.0 | 2000 | List comprehensions, garbage collection |
| Python 3.0 | 2008 | Major redesign, not backward compatible |
| Python 3.10+ | 2021+ | Match statements, performance improvements |
The name "Python" comes from the British comedy show "Monty Python's Flying Circus" — Guido van Rossum was a fan while writing the language.
Research the key differences between Python 2 and Python 3. Why is Python 2 no longer supported?
Features of Python
- Simple & Easy to Learn: Python's clean, readable syntax closely resembles English.
- Interpreted: Code is executed line-by-line at runtime — no compilation step needed.
- Dynamically Typed: Variable types are determined at runtime, not declared explicitly.
- Object-Oriented: Supports classes, inheritance, encapsulation, and polymorphism.
- Platform Independent: Runs on Windows, macOS, Linux, and Unix without changes.
- Extensive Standard Library: Ships with libraries for math, file I/O, networking, and more.
- Open Source & Free: Python is freely available and actively community-driven.
- Extensible: Can integrate C/C++ code for performance-critical sections.
- Embeddable: Python can be embedded within C/C++ programs.
- High-level Language: Memory management is handled automatically.
Explain why "dynamically typed" is both an advantage and a potential source of bugs. Give an example.
Limitations of Python
Despite its popularity, Python has several notable limitations:
- Slow Execution Speed: Being interpreted, Python is significantly slower than compiled languages like C or C++.
- High Memory Consumption: Dynamic typing and garbage collection consume more memory than statically typed languages.
- Global Interpreter Lock (GIL): Python threads cannot run in true parallel on multi-core CPUs due to the GIL, limiting multi-threading performance.
- Weak in Mobile Development: Python has limited native support for Android and iOS app development.
- Runtime Errors: Dynamic typing means many errors only appear at runtime, not at compile time.
- Not Ideal for Low-level Programming: Direct hardware manipulation, device drivers, and kernel code cannot be written in Python.
Research what the Global Interpreter Lock (GIL) is and how Python developers work around it using multiprocessing.
Major Applications of Python
Python is one of the most versatile languages in the world. Its major application areas include:
- Web Development: Frameworks like Django and Flask power millions of websites.
- Data Science & Analytics: Libraries like Pandas, NumPy, and Matplotlib are industry standards.
- Machine Learning & AI: TensorFlow, PyTorch, and scikit-learn are all Python-based.
- Automation & Scripting: Automating repetitive system tasks, file operations, and workflows.
- Cybersecurity: Writing penetration testing tools, exploit scripts, and network scanners.
- Game Development: Pygame library allows 2D game creation.
- Web Scraping: BeautifulSoup and Scrapy extract data from websites.
- Scientific Computing: Used in physics, biology, and engineering simulations (SciPy).
- Finance & Trading: Algorithmic trading bots and financial modelling.
- Embedded Systems & IoT: MicroPython runs on microcontrollers like Raspberry Pi.
Pick one application domain and research one real-world product or company that uses Python in that domain.
Getting and Installing Python
Python can be downloaded for free from the official website: https://www.python.org/downloads/
Steps to install Python on Windows:
- Go to
python.organd download the latest installer (.exe). - Run the installer.
- ✅ Check the box "Add Python to PATH" before clicking Install.
- Click "Install Now".
- Verify installation by opening Command Prompt and typing:
python --version # Expected: Python 3.x.x pip --version # Expected: pip xx.x from ...
pip is Python's package manager, installed alongside Python. It lets you install third-party libraries.
Install Python on your system. Verify it is working by running python --version and pip --version in your terminal.
Setting up Path and Environment Variables
The PATH environment variable tells your operating system where to find executable programs. When Python is added to PATH, you can run python from any terminal directory.
Manually adding Python to PATH on Windows:
- Search for "Environment Variables" in the Start Menu.
- Click "Edit the system environment variables".
- Click "Environment Variables..." button.
- Under "System variables", select
Pathand click "Edit". - Click "New" and add the path to Python (e.g.,
C:\Python312\andC:\Python312\Scripts\). - Click OK and restart your terminal.
where python # Should output: C:\Python312\python.exe echo %PATH% # Lists all registered paths
The PYTHONPATH variable is a special environment variable that tells Python where to look for modules and packages beyond the default directories.
Open your system's environment variables and verify Python is listed in PATH. Try running python from two different directories in your terminal.
Running Python
There are three main ways to run Python code:
1. Interactive Mode (REPL — Read Evaluate Print Loop):
python
>>> 2 + 3
5
>>> print("Hello")
Hello
>>> exit()
2. Script Mode: Write code in a .py file and run it:
python myprogram.py
3. IDE / Code Editor: Tools like VS Code, PyCharm, IDLE, or Jupyter Notebook provide a full development environment with syntax highlighting, debugging, and auto-complete.
Run Python in interactive mode and compute: (15 + 3) * 2 / 9. Then create a script file that does the same calculation and prints the result.
First Python Program
The tradition in programming is to write a "Hello, World!" program as your very first code. In Python, this is remarkably simple:
print("Hello, World!")
Run it with:
python hello.py # Output: Hello, World!
Let's extend this to a slightly more meaningful first program:
name = input("Enter your name: ") print(f"Welcome to Python, {name}!")
Write a Python program that asks for your name and age, then prints a message saying "Hello [name], you will be [age+1] next year!"
Python Interactive Help Feature
Python has a powerful built-in help system accessible directly from the interpreter. The help() function retrieves documentation for any object, function, module, or keyword.
>>> help() # Enter interactive help mode
>>> help(print) # Get docs for print function
>>> help(str) # Get docs for string class
>>> help("for") # Get docs for the 'for' keyword
Other useful built-in inspection tools:
dir(obj)— lists all attributes and methods of an object.type(obj)— returns the data type of a variable.id(obj)— returns the memory address of an object.isinstance(obj, type)— checks if an object is an instance of a class.
>>> dir("hello") ['capitalize', 'casefold', 'center', ...] >>> type(3.14) <class 'float'>
Use help(input) and dir(list) in the Python shell and document 5 interesting things you discover about each.
Python Differences from Other Languages
| Feature | Python | C / C++ | Java |
|---|---|---|---|
| Code Blocks | Indentation | Curly braces { } | Curly braces { } |
| Typing | Dynamic | Static | Static |
| Execution | Interpreted | Compiled | Compiled to bytecode |
| Memory Mgmt | Automatic (GC) | Manual | Automatic (GC) |
| Semicolons | Not required | Required | Required |
| Variable Declaration | No keyword needed | int x = 5; | int x = 5; |
| Hello World Lines | 1 line | 5+ lines | 5+ lines |
Write "Hello, World!" in Python, C, and Java. Compare the amount of boilerplate code required in each.
Keywords
Keywords are reserved words in Python that have a fixed meaning and cannot be used as identifiers (variable names, function names, etc.).
Python 3 has 35 keywords. You can view them all with:
import keyword print(keyword.kwlist) print(len(keyword.kwlist)) # 35
| Category | Keywords |
|---|---|
| Boolean | True, False, None |
| Logical | and, or, not |
| Control Flow | if, elif, else, for, while, break, continue, pass, return |
| Functions/Classes | def, class, lambda, yield |
| Exceptions | try, except, finally, raise, with, as |
| Import | import, from, as |
| Scope | global, nonlocal, del |
| Identity/Membership | is, in |
Try to use a keyword as a variable name (e.g., for = 5). Observe the error. Then list 5 keywords and write a sentence explaining what each does.
Identifiers
An identifier is a user-defined name given to a variable, function, class, module, or any other object. Rules for valid identifiers:
- Must start with a letter (a–z, A–Z) or underscore (
_). - Cannot start with a digit.
- Can contain letters, digits (0–9), and underscores.
- Cannot be a Python keyword.
- Case-sensitive:
name,Name, andNAMEare three different identifiers. - No special characters allowed (
@,#,$, etc.).
# Valid identifiers student_name = "Alice" _count = 10 MyClass2 = True # Invalid identifiers # 2count = 5 → SyntaxError: starts with digit # my-var = 3 → SyntaxError: hyphen not allowed # class = "CS" → SyntaxError: keyword
Naming conventions:
snake_case— for variables and functions (Python standard)PascalCase— for class namesUPPER_CASE— for constants_single_leading_underscore— internal use__double_leading_underscore— name mangling in classes
Write 5 valid and 5 invalid identifiers. Explain why each invalid one fails.
Python Statement
A statement is an instruction that the Python interpreter can execute. Unlike many languages, Python statements do not require a semicolon at the end.
Types of statements:
- Simple Statement: A single logical line of code. E.g.,
x = 5,print("Hi"). - Compound Statement: Contains a header and a body (e.g.,
if,for,def,class). - Multi-line Statements: A statement that spans multiple lines using line continuation.
# Explicit line continuation with backslash total = 10 + 20 + \ 30 + 40 # Implicit continuation inside brackets result = (10 + 20 + 30 + 40) # Multiple statements on one line (not recommended) a = 1; b = 2; c = 3
Write a multi-line statement calculating the sum of 10 numbers using both the backslash continuation and parenthesis methods.
Indentation
Unlike other languages that use curly braces { }, Python uses indentation (whitespace) to define code blocks. This is not optional — it is part of Python's syntax.
if True: print("This is inside the if block") print("Still inside the if block") print("This is outside — not indented")
Rules:
- Standard indentation is 4 spaces (PEP 8 guideline).
- Tabs and spaces must not be mixed — Python 3 raises a
TabError. - All lines within the same block must have the same indentation level.
- An
IndentationErroris raised for incorrect indentation.
if True: print("Error!") # IndentationError — not indented
Write a nested if-else inside a for loop demonstrating 3 levels of indentation correctly.
Documentation
Documentation makes code readable and maintainable. Python supports two forms of documentation:
1. Single-line Comments: Use # — the interpreter ignores everything after it on that line.
2. Docstrings (Documentation Strings): Multi-line strings using triple quotes ("""...""" or '''...'''). They document modules, classes, and functions and can be accessed via __doc__.
# This is a single-line comment def add(a, b): """ Add two numbers and return the result. Parameters: a (int): First number b (int): Second number Returns: int: Sum of a and b """ return a + b print(add.__doc__) # Access docstring
Write a function multiply(x, y) with a full docstring describing parameters and return value. Access and print its __doc__.
Variables
A variable is a named reference to a memory location that stores data. In Python, variables are created when you assign a value — no explicit declaration is needed.
name = "Alice" # str age = 20 # int height = 5.6 # float is_student = True # bool print(type(name)) # <class 'str'> print(id(age)) # Memory address
Key Points:
- Variables are labels pointing to objects in memory, not containers.
- A variable can be reassigned to a completely different type at any time.
- Python uses reference counting to manage memory for variables.
- You can delete a variable using
del variable_name.
Create variables of types int, float, str, bool, and None. Print their types using type() and their memory addresses using id().
Multiple Assignment
Python allows assigning values to multiple variables in a single statement:
# Assign different values to multiple variables a, b, c = 10, 20.5, "Python" print(a, b, c) # 10 20.5 Python # Assign the same value to multiple variables x = y = z = 0 print(x, y, z) # 0 0 0 # Swap variables without a temp variable x, y = 5, 10 x, y = y, x print(x, y) # 10 5 # Unpack a list into variables values = [1, 2, 3] p, q, r = values print(p, q, r) # 1 2 3
Use multiple assignment to swap three variables a, b, c in a cyclic manner (a→b, b→c, c→a) without a temp variable.
Understanding Data Type
A data type classifies data and determines what operations can be performed on it. Python has the following built-in data types:
| Category | Type | Example |
|---|---|---|
| Text | str | "Hello" |
| Numeric | int, float, complex | 42, 3.14, 2+3j |
| Sequence | list, tuple, range | [1,2], (1,2) |
| Mapping | dict | {"key": "val"} |
| Set | set, frozenset | {1, 2, 3} |
| Boolean | bool | True, False |
| Binary | bytes, bytearray | b"hello" |
| None | NoneType | None |
x = 42 print(type(x)) # <class 'int'> print(isinstance(x, int)) # True
Create one variable of each built-in type. Use type() and isinstance() to verify each one.
Data Type Conversion
Python supports two types of data conversion:
1. Implicit Conversion (Type Coercion): Python automatically converts one type to another without any user involvement.
result = 5 + 2.5 # int + float → float print(result, type(result)) # 7.5 <class 'float'>
2. Explicit Conversion (Type Casting): You manually convert data types using built-in functions.
x = "42" y = int(x) # str → int z = float(y) # int → float s = str(z) # float → str b = bool(0) # int → bool → False lst = list((1, 2, 3)) # tuple → list print(y, z, s, b, lst)
Write a program that takes a string number as input, converts it to int, performs arithmetic, converts back to string, and prints with formatting.
Python Input and Output Functions
Output — print():
print("Hello") # Basic output print("Hello", "World", sep="-") # Hello-World print("Line1", end=" | ") # No newline at end print("Line2") name = "Alice"; score = 98.5 print(f"Student: {name}, Score: {score:.2f}") # f-string print("Name: %s, Score: %.1f" % (name, score)) # % formatting
Input — input(): Always returns a string. Must be cast if you need a number.
name = input("Enter your name: ") age = int(input("Enter your age: ")) print(f"Hello {name}, you are {age} years old.")
Write a program that inputs two numbers and prints their sum, difference, product, and quotient using f-strings with 2 decimal places.
Import Command
The import command loads external modules or libraries into your program. Python has several import forms:
# Import entire module import math print(math.sqrt(16)) # 4.0 # Import specific function/attribute from math import pi, ceil print(pi) # 3.14159... # Import with alias import datetime as dt print(dt.date.today()) # Import all (not recommended) from math import * print(factorial(5)) # 120
Import the random module and use it to generate 5 random integers between 1 and 100. Print them in a formatted list.
Operators in Python
An operator is a symbol that performs an operation on one or more operands. Python has the following operator categories:
| Type | Operators | Example |
|---|---|---|
| Arithmetic | + - * / // % ** | 10 // 3 = 3 |
| Comparison | == != > < >= <= | 5 == 5 → True |
| Logical | and or not | True and False → False |
| Assignment | = += -= *= /= //= **= | x += 5 |
| Bitwise | & | ^ ~ << >> | 5 & 3 = 1 |
| Identity | is is not | x is None |
| Membership | in not in | 3 in [1,2,3] |
a, b = 10, 3 print(a + b, a - b, a * b) # 13 7 30 print(a / b, a // b, a % b) # 3.33 3 1 print(a ** b) # 1000 print(5 & 3, 5 | 3, 5 ^ 3) # 1 7 6
Write a program that accepts two integers and prints the result of every arithmetic and bitwise operator applied to them.
Expressions
An expression is a combination of values, variables, operators, and function calls that Python evaluates to produce a single value.
x = 5; y = 3 # Arithmetic expression result = (x ** 2) + (2 * x * y) + (y ** 2) print(result) # 64 # Boolean expression is_valid = (x > 0) and (y < 10) print(is_valid) # True # Conditional (ternary) expression label = "positive" if x > 0 else "non-positive" print(label) # positive # String expression greeting = "Hello" + " " + "World" print(greeting) # Hello World
Write a program that evaluates the quadratic formula for given a, b, c values using a single expression. Handle the case where the discriminant is negative.
Precedence
Operator precedence determines which operator is evaluated first in an expression with multiple operators. Higher precedence operators are evaluated before lower ones.
| Priority | Operators | Description |
|---|---|---|
| 1 (Highest) | () | Parentheses |
| 2 | ** | Exponentiation |
| 3 | ~, +, - | Unary operators |
| 4 | *, /, //, % | Multiplication, Division |
| 5 | +, - | Addition, Subtraction |
| 6 | <<, >> | Bitwise Shift |
| 7 | & | Bitwise AND |
| 8 | ^ | Bitwise XOR |
| 9 | | | Bitwise OR |
| 10 | ==, !=, >, <... | Comparisons |
| 11 | not | Logical NOT |
| 12 | and | Logical AND |
| 13 (Lowest) | or | Logical OR |
result = 2 + 3 * 4 # 14 (not 20) result2 = (2 + 3) * 4 # 20 result3 = 2 ** 3 ** 2 # 512 (right-to-left) print(result, result2, result3)
Predict the output of: 3 + 5 * 2 - 4 / 2 ** 2 without running it. Then verify.
Associativity of Operators
When two operators have the same precedence, associativity determines the direction of evaluation:
- Left-to-Right (Left associative): Most operators:
+,-,*,/,//,% - Right-to-Left (Right associative): Exponentiation
**, unary operators, assignment=
# Left associativity: evaluated left to right result = 10 - 4 - 2 # (10 - 4) - 2 = 4 print(result) # Right associativity: evaluated right to left result2 = 2 ** 3 ** 2 # 2 ** (3 ** 2) = 2 ** 9 = 512 print(result2) # Division is left associative result3 = 100 / 10 / 2 # (100 / 10) / 2 = 5.0 print(result3)
Evaluate 4 ** 3 ** 2 and (4 ** 3) ** 2. Show the difference and explain which one uses right associativity.
Non Associative Operators
Non-associative operators are operators that cannot be chained in the usual left-to-right or right-to-left manner. In Python, comparison operators are the primary example.
Python uses chained comparisons for comparison operators instead of standard associativity:
# Chained comparison — Python reads it as: # (1 < 5) and (5 < 10) → True print(1 < 5 < 10) # True # Not: (1 < 5) < 10 → (True) < 10 → 1 < 10 → True (C-style) # Python is smarter: it chains them properly print(1 < 5 > 3) # True (1<5 and 5>3) print(1 == 1 == 1) # True print(5 < 5) # False — not non-associative, just False
Write expressions using chained comparisons to check if a number lies within a range (e.g., between 10 and 100 inclusive).
Decision Making Statements
Decision making allows a program to choose different paths of execution based on conditions. Python uses if, elif, and else:
marks = 75 if marks >= 90: print("Grade: A") elif marks >= 75: print("Grade: B") elif marks >= 60: print("Grade: C") else: print("Grade: F") # Nested if x = 10 if x > 0: if x % 2 == 0: print("Positive even number") # One-liner (ternary) status = "pass" if marks >= 50 else "fail" print(status)
Write a program that takes a year as input and checks whether it is a leap year using nested if statements.
Python Loops
Loops execute a block of code repeatedly. Python has two types of loops:
1. for Loop: Iterates over a sequence (list, tuple, string, range).
for i in range(1, 6): print(i, end=" ") # 1 2 3 4 5 fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(fruit)
2. while Loop: Repeats as long as a condition remains True.
count = 1 while count <= 5: print(count, end=" ") count += 1 # 1 2 3 4 5
Print the multiplication table for a number entered by the user using both for and while loops.
Python Control Statements
Control statements alter the normal sequential flow of loops:
break— exits the loop immediately.continue— skips the current iteration and moves to the next.pass— does nothing; acts as a placeholder.
for i in range(10): if i == 3: continue # skip 3 if i == 7: break # stop at 7 print(i, end=" ") # 0 1 2 4 5 6 # pass — empty block placeholder for x in range(5): pass # will be implemented later
Write a loop that prints numbers 1 to 20, skips all multiples of 3, and breaks when it hits a multiple of 7.
Numbers
Python supports three numeric types:
- int: Whole numbers of unlimited precision. E.g.,
42,-7,100000000 - float: Real numbers with decimal points (64-bit IEEE 754). E.g.,
3.14,-0.001,2.5e10 - complex: Numbers with real and imaginary parts. E.g.,
3+4j
a = 100 # int b = 3.14159 # float c = 3 + 4j # complex print(c.real, c.imag) # 3.0 4.0 print(abs(c)) # magnitude = 5.0 import math print(math.floor(3.9)) # 3 print(math.ceil(3.1)) # 4 print(round(3.567, 2)) # 3.57
Create a complex number and compute its magnitude, conjugate, and check if two complex numbers are equal.
Lists
A list is an ordered, mutable collection that can hold items of different types. Lists allow duplicate elements.
fruits = ["apple", "banana", "cherry"] # Indexing & Slicing print(fruits[0]) # apple print(fruits[-1]) # cherry print(fruits[0:2]) # ['apple', 'banana'] # Modifying fruits.append("mango") fruits.insert(1, "grape") fruits.remove("banana") popped = fruits.pop() # Useful methods nums = [3, 1, 4, 1, 5, 9] nums.sort() nums.reverse() print(nums.count(1)) # 2 print(nums.index(5)) # position of 5
Create a list of 10 numbers. Sort, reverse, find max/min, and use list comprehension to filter only even numbers.
Tuples
A tuple is an ordered, immutable collection. Once created, elements cannot be added, removed, or changed. Tuples are faster than lists and are used for fixed data.
coords = (10, 20) rgb = (255, 128, 0) print(coords[0]) # 10 print(rgb[1:3]) # (128, 0) # Unpacking x, y = coords print(x, y) # 10 20 # Single element tuple — trailing comma required single = (42,) print(type(single)) # <class 'tuple'> # Tuple methods data = (1, 2, 2, 3) print(data.count(2)) # 2 print(data.index(3)) # 3
Create a tuple of student records (name, age, grade). Unpack and print each record. Try to modify a value and observe the error.
Sets
A set is an unordered, mutable collection of unique elements. Duplicates are automatically removed. Sets support mathematical set operations.
a = {1, 2, 3, 4, 2, 1}
print(a) # {1, 2, 3, 4} — duplicates removed
a.add(5)
a.discard(2) # safe remove (no error if missing)
a.remove(3) # raises KeyError if missing
# Set operations
b = {3, 4, 5, 6}
print(a | b) # Union
print(a & b) # Intersection
print(a - b) # Difference
print(a ^ b) # Symmetric Difference
print(a.issubset(b)) # Subset check
Given two lists of student names, use sets to find students who appear in both, only in the first, only in the second, and in either but not both.
Dictionary
A dictionary is an ordered (Python 3.7+), mutable collection of key-value pairs. Keys must be unique and immutable.
student = {
"name": "Alice",
"age": 21,
"grade": "A"
}
# Access
print(student["name"]) # Alice
print(student.get("age")) # 21 (safe access)
# Modify
student["age"] = 22
student["city"] = "Delhi" # add new key
del student["grade"] # remove key
# Check key existence
print("name" in student) # True
Create a dictionary representing a product (name, price, stock). Update the price, add a "discount" key, and delete "stock". Print the final dictionary.
Functions & Methods of Dictionary
| Method | Description |
|---|---|
keys() | Returns all keys as a view object |
values() | Returns all values as a view object |
items() | Returns key-value pairs as (key, value) tuples |
get(key, default) | Returns value or default if key missing |
update(dict2) | Merges another dictionary into this one |
pop(key) | Removes and returns the specified key's value |
popitem() | Removes and returns the last inserted pair |
clear() | Removes all items |
copy() | Returns a shallow copy |
setdefault(key, val) | Returns value if key exists, else inserts with val |
fromkeys(seq, val) | Creates dict from sequence of keys with same value |
d = {"a": 1, "b": 2, "c": 3}
for key, val in d.items():
print(f"{key} → {val}")
d.update({"d": 4, "e": 5})
removed = d.pop("a")
empty = dict.fromkeys(["x", "y"], 0)
print(empty) # {'x': 0, 'y': 0}
Create a word frequency counter: given a sentence, use a dictionary and setdefault() or get() to count how many times each word appears.
Strings
A string is an immutable sequence of Unicode characters. Strings support a rich set of operations and methods.
s = "Hello, Python World!" # Indexing & Slicing print(s[0]) # H print(s[-6:]) # World! print(s[::2]) # Hlo yhno ol! print(s[::-1]) # Reversed string # Case methods print(s.upper()) print(s.lower()) print(s.title()) print(s.swapcase()) # Search methods print(s.find("Python")) # 7 print(s.count("l")) # 3 print(s.startswith("Hello")) # True # Manipulation print(s.replace("Python", "Code")) print(s.split(",")) print(" | ".join(["a", "b", "c"])) print(" hello ".strip()) print(s.center(30, "*"))
Write a program that takes a sentence, reverses each word individually (not the sentence), and prints the result in title case.
Functions
A function is a named, reusable block of code that performs a specific task. Functions are defined using the def keyword.
# Basic function with default parameter def greet(name="Guest"): """Greets the user by name.""" print(f"Hello, {name}!") greet() # Hello, Guest! greet("Alice") # Hello, Alice! # Function with return value def add(a, b): return a + b result = add(10, 5) print(result) # 15 # Function with *args (variable arguments) def total(*nums): return sum(nums) print(total(1, 2, 3, 4)) # 10 # Function with **kwargs def profile(**info): for k, v in info.items(): print(f"{k}: {v}") profile(name="Bob", age=25)
Write a function stats(*nums) that accepts any number of integers and returns a tuple of (min, max, sum, average).
Advantages of Functions
- Code Reusability: Write once, use many times anywhere in the program.
- Modularity: Break complex problems into smaller, manageable sub-problems.
- Readability: Named functions make code self-documenting and easier to understand.
- Easier Debugging: Test and fix individual functions independently.
- Reduced Redundancy: Avoid writing duplicate logic in multiple places.
- Abstraction: Hide implementation details — caller only needs to know what a function does, not how.
- Maintainability: Change logic in one place and it updates everywhere it's called.
- Testability: Functions can be unit tested in isolation.
def is_prime(n): if n < 2: return False for i in range(2, int(n ** 0.5) + 1): if n % i == 0: return False return True # Reused in multiple contexts: primes = [x for x in range(100) if is_prime(x)] print(primes)
Write 3 separate functions: one to validate an email, one to validate a phone number, and one that calls both and prints a report. Demonstrate modularity.
Built-in Functions
Python ships with many built-in functions available without importing anything:
| Function | Description | Example |
|---|---|---|
print() | Output to console | print("Hi") |
input() | Read from stdin | input("Enter: ") |
len() | Length of object | len([1,2,3]) → 3 |
type() | Return type | type(3.14) → float |
int(),float(),str() | Type conversion | int("42") → 42 |
range() | Generate integer sequence | range(1,6) |
abs() | Absolute value | abs(-7) → 7 |
max(),min() | Max/Min value | max(3,1,5) → 5 |
sum() | Sum of iterable | sum([1,2,3]) → 6 |
sorted() | Returns sorted list | sorted([3,1,2]) |
enumerate() | Index + value pairs | enumerate(["a","b"]) |
zip() | Pairs from iterables | zip([1,2],[3,4]) |
map() | Apply function to iterable | map(str, [1,2,3]) |
filter() | Filter iterable by function | filter(is_even, lst) |
Use enumerate(), zip(), map(), and filter() each in a program working on a list of 10 numbers.
User Defined Functions
A user-defined function (UDF) is created by the programmer using the def keyword to solve a specific problem.
# Function with positional arguments def rectangle_area(length, width): return length * width # Function with keyword arguments def describe(name, age, city="Unknown"): return f"{name}, Age {age}, from {city}" print(rectangle_area(5, 3)) print(describe("Alice", 25, city="Delhi")) # Function returning multiple values def min_max(lst): return min(lst), max(lst) lo, hi = min_max([4, 2, 9, 1]) print(lo, hi) # 1 9
Write a UDF bmi(weight_kg, height_m) that returns the BMI value and category (Underweight / Normal / Overweight / Obese).
Anonymous Functions
An anonymous function (also called a lambda function) is a small, nameless function defined with the lambda keyword. Useful for short, one-expression functions.
Syntax: lambda arguments: expression
# Simple lambda square = lambda x: x ** 2 print(square(5)) # 25 # Lambda with multiple args add = lambda a, b: a + b print(add(3, 4)) # 7 # Lambda with sorted() — sort by second element pairs = [(1, 'b'), (3, 'a'), (2, 'c')] pairs.sort(key=lambda p: p[1]) print(pairs) # [(3, 'a'), (1, 'b'), (2, 'c')] # Lambda with map() and filter() nums = [1, 2, 3, 4, 5] squared = list(map(lambda x: x**2, nums)) evens = list(filter(lambda x: x%2==0, nums)) print(squared, evens)
Use a lambda with sorted() to sort a list of dictionaries by a key (e.g., sort students by their GPA descending).
Pass by Value Vs. Pass by Reference
Python uses Pass by Object Reference (also called "pass by assignment"). The behavior depends on whether the object is mutable or immutable:
- Immutable types (int, str, float, tuple): Behave like pass-by-value — changes inside the function do not affect the original.
- Mutable types (list, dict, set): Behave like pass-by-reference — changes inside the function affect the original object.
# Immutable — original unchanged def change_num(x): x = 999 n = 10 change_num(n) print(n) # 10 — unchanged # Mutable — original IS changed def append_item(lst): lst.append(99) my_list = [1, 2, 3] append_item(my_list) print(my_list) # [1, 2, 3, 99] — changed! # To protect a list, pass a copy: append_item(my_list.copy())
Demonstrate the difference between passing a list and a tuple to a function that tries to modify them. Explain what happens and why.
Recursion
Recursion is when a function calls itself to solve a problem. Every recursive function must have:
- Base Case: Condition that stops the recursion.
- Recursive Case: The function calling itself with a simpler input.
# Factorial using recursion def factorial(n): if n == 0 or n == 1: # Base case return 1 return n * factorial(n - 1) # Recursive call print(factorial(5)) # 120 # Fibonacci using recursion def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2) print([fib(i) for i in range(10)]) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Write a recursive function to compute the sum of digits of a number. E.g., sum_digits(1234) returns 10.
Scope and Lifetime of Variables
The scope of a variable defines where in the program it can be accessed. Python uses the LEGB rule:
- L — Local: Inside the current function.
- E — Enclosing: Inside any enclosing functions (closures).
- G — Global: At the top level of the module.
- B — Built-in: Python's built-in namespace (e.g.,
print,len).
x = "global" def outer(): x = "enclosing" def inner(): x = "local" print(x) # local inner() print(x) # enclosing outer() print(x) # global # global keyword counter = 0 def increment(): global counter counter += 1 increment() print(counter) # 1
The lifetime of a local variable is the duration of the function call — it is created when the function is called and destroyed when it returns.
Demonstrate all 4 LEGB scopes in a single program with nested functions. Show how global and nonlocal keywords change which scope is modified.
Module Definition
A module is a file containing Python definitions (functions, classes, variables) and statements. Any .py file is a module. Modules help organize code into logical, reusable units.
# This file IS the module PI = 3.14159 def circle_area(r): return PI * r * r def square_area(s): return s * s # This block only runs when executed directly if __name__ == "__main__": print("Running mathutils directly") print(circle_area(5))
The special variable __name__ equals "__main__" when the file is run directly, but equals the module name when imported.
Create a module greetings.py with functions for formal and informal greetings. Import and use it in a separate script.
Need of Modules
Modules solve critical software engineering problems:
- Code Organization: Split large programs into manageable files.
- Reusability: Write a function once, import it across multiple programs.
- Namespace Management: Each module has its own namespace, preventing name conflicts.
- Collaboration: Teams can work on different modules simultaneously.
- Separation of Concerns: Keep database logic, UI logic, and business logic in separate modules.
- Maintainability: Update one module without affecting others.
- Testing: Test modules independently.
Without modules, a large program would be a single massive file — impossible to maintain.
Explain how you would organize a student management system into 3–4 separate modules. Name each module and list what it would contain.
Creating a Module
Creating a module is as simple as saving Python code in a .py file:
"""Unit Converter Module""" KM_PER_MILE = 1.60934 KG_PER_POUND = 0.453592 def miles_to_km(miles): return miles * KM_PER_MILE def pounds_to_kg(pounds): return pounds * KG_PER_POUND def celsius_to_fahrenheit(c): return (c * 9/5) + 32
import converter print(converter.miles_to_km(10)) # 16.0934 print(converter.celsius_to_fahrenheit(100)) # 212.0
Create a module shapes.py with functions for area and perimeter of circle, rectangle, and triangle. Use it in a main program.
Importing Module
Python provides multiple ways to import modules:
# 1. Import entire module import math print(math.pi) # 2. Import specific names from math import sqrt, factorial print(sqrt(25)) # 5.0 print(factorial(5)) # 120 # 3. Import with alias import numpy as np # common convention import pandas as pd # common convention # 4. Import all (generally discouraged) from math import * # 5. Import from a package from os.path import join, exists
Import the datetime module using all 4 styles shown above. Print today's date and time using each method.
Path Searching of a Module
When you run import module_name, Python searches for the module in this order:
- Built-in Modules: Standard library modules compiled into the interpreter (e.g.,
sys,os). - Current Directory: The folder where the script is running.
- PYTHONPATH: A list of directories in the environment variable.
- Installation-Dependent Default: Standard library and site-packages.
import sys # View the complete module search path for path in sys.path: print(path) # Add a custom directory to the path sys.path.append("/my/custom/modules") # Check where a module is loaded from import math print(math.__file__) # Full path to math module
Print your sys.path. Then create a module in a subdirectory, add that directory to sys.path, and import the module successfully.
Module Reloading
By default, Python imports a module only once per session. Subsequent import statements for the same module use the cached version from sys.modules. To force a re-import (e.g., after modifying the module file), use importlib.reload():
import importlib import mymodule # ... (you edit mymodule.py here) ... importlib.reload(mymodule) # Force fresh load print(mymodule.some_function())
You can also check cached modules:
import sys print("math" in sys.modules) # True after import math print(sys.modules.keys())
Create a simple module with a variable. Import it, print the variable. Modify the module file. Reload it and print again to see the updated value.
Standard Modules
Python's Standard Library contains hundreds of pre-built modules ready to use:
| Module | Purpose | Example |
|---|---|---|
math | Mathematical functions | math.sqrt(16) |
os | Operating system interface | os.getcwd() |
sys | System-specific parameters | sys.argv |
random | Random number generation | random.randint(1, 10) |
datetime | Date and time handling | datetime.date.today() |
json | JSON encoding/decoding | json.dumps(data) |
re | Regular expressions | re.findall(pattern, text) |
collections | Specialized containers | Counter(list) |
itertools | Iterator building blocks | itertools.chain() |
functools | Higher-order functions | functools.reduce() |
Use random to simulate rolling two dice 1000 times. Use collections.Counter to count how many times each sum appears.
Python Packages
A package is a directory containing multiple modules, organized hierarchically. It must contain a special file __init__.py (can be empty).
mypackage/
__init__.py ← makes it a package
shapes.py ← module 1
converter.py ← module 2
utils/
__init__.py
validators.py ← sub-package module
from mypackage import shapes from mypackage.utils import validators shapes.circle_area(5) validators.is_email("[email protected]")
Create a package called school with sub-modules students.py and teachers.py. Each should have an add() and list_all() function. Import and test from a main script.
Exceptions
An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. Unlike syntax errors (detected at parse time), exceptions occur at runtime.
When Python encounters an error at runtime, it creates an exception object. If unhandled, the program crashes and displays a traceback.
# This will raise a ZeroDivisionError result = 10 / 0 # Traceback output: # Traceback (most recent call last): # File "exception_demo.py", line 2, in module # ZeroDivisionError: division by zero
All exceptions in Python are derived from the base class BaseException. The main hierarchy is:
BaseExceptionSystemExit,KeyboardInterrupt,GeneratorExitException← most exceptions inherit from thisArithmeticError,LookupError,ValueError, etc.
Deliberately trigger 5 different exceptions (ZeroDivisionError, TypeError, IndexError, KeyError, NameError) and read each traceback carefully.
Built-in Exceptions
Python has many built-in exception types:
| Exception | Raised When |
|---|---|
ZeroDivisionError | Division or modulo by zero |
TypeError | Operation on incompatible types |
ValueError | Correct type but invalid value |
IndexError | Sequence index out of range |
KeyError | Dictionary key not found |
AttributeError | Attribute reference/assignment failure |
NameError | Local or global name not found |
FileNotFoundError | File/directory does not exist |
ImportError | Module import fails |
MemoryError | Operation ran out of memory |
RecursionError | Maximum recursion depth exceeded |
StopIteration | next() called on exhausted iterator |
OverflowError | Arithmetic result too large |
RuntimeError | Generic error not covered by others |
Write a program that intentionally triggers 5 different built-in exceptions. Catch each one separately and print a helpful message explaining what went wrong.
Exception Handling
Python handles exceptions using try-except-else-finally blocks:
try: num = int(input("Enter a number: ")) result = 100 / num except ZeroDivisionError: print("Cannot divide by zero!") except ValueError: print("Please enter a valid integer!") except Exception as e: print(f"Unexpected error: {e}") else: # Runs ONLY if no exception occurred print(f"Result: {result}") finally: # ALWAYS runs print("Program completed.")
You can also raise exceptions manually:
def set_age(age): if age < 0: raise ValueError("Age cannot be negative") return age
Write a safe division function that handles ZeroDivisionError, TypeError, and catches any other unexpected exception with a generic handler. Use finally to log every call.
User Defined Exceptions in Python
You can create custom exceptions by subclassing the built-in Exception class. This allows you to define meaningful, application-specific errors.
# Define custom exceptions class InsufficientFundsError(Exception): """Raised when bank balance is insufficient.""" def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__( f"Cannot withdraw {amount}. Balance: {balance}" ) class InvalidAgeError(Exception): pass # Use custom exception def withdraw(balance, amount): if amount > balance: raise InsufficientFundsError(balance, amount) return balance - amount try: withdraw(500, 800) except InsufficientFundsError as e: print(f"Error: {e}")
Create custom exceptions NegativeBalanceError, InvalidPINError, and DailyLimitExceededError for a banking system. Demonstrate raising and catching each one.
Operations on Files
File management in Python starts with the open() function. Syntax: open(filename, mode, encoding)
| Mode | Description |
|---|---|
'r' | Read — default. File must exist. |
'w' | Write — creates or overwrites file. |
'a' | Append — adds to end of file. |
'x' | Create — fails if file exists. |
'rb' | Read binary. |
'wb' | Write binary. |
'r+' | Read and write. |
File attributes:
f = open("data.txt", "w", encoding="utf-8") print(f.name) # data.txt print(f.mode) # w print(f.closed) # False f.close() print(f.closed) # True
Open a file in write mode, write 5 lines of text, close it, then reopen in read mode and display its attributes (name, mode, encoding).
read() & write() Methods
# WRITING with open("notes.txt", "w") as f: f.write("Python is great!\n") f.write("File I/O is easy.\n") f.writelines(["Line 3\n", "Line 4\n"]) # READING — entire file with open("notes.txt", "r") as f: content = f.read() # entire file as string print(content) # READING — line by line with open("notes.txt") as f: line1 = f.readline() # reads one line lines = f.readlines() # reads all lines into list print(line1) print(lines)
with statements when working with files. It automatically closes the file even if an exception occurs — preventing resource leaks.Write a program that reads a text file and counts the number of lines, words, and characters in it. Use both read() and readlines().
tell() & seek() Methods
These methods control the file pointer (read/write head position):
tell()— returns the current byte position of the file pointer.seek(offset, whence)— moves the pointer.whence: 0=start, 1=current, 2=end.
with open("notes.txt", "r") as f: print(f.tell()) # 0 — at start data = f.read(10) # read 10 bytes print(f.tell()) # 10 f.seek(0) # go back to start print(f.tell()) # 0 f.seek(5) # jump to byte 5 word = f.read(6) # read 6 bytes from pos 5 print(word) f.seek(0, 2) # jump to end of file print(f.tell()) # file size in bytes
Write to a file, then use seek() and tell() to read only the 3rd and 4th words from the file without reading the whole content at once.
Renaming & Deleting Files in Python
Use the os module to rename, delete, and manage files on the filesystem:
import os # Check if file exists before operating if os.path.exists("old.txt"): os.rename("old.txt", "new.txt") # rename print("Renamed successfully") if os.path.exists("temp.txt"): os.remove("temp.txt") # delete file print("Deleted successfully") # Get file information info = os.stat("new.txt") print(f"Size: {info.st_size} bytes") # Using shutil for more operations import shutil shutil.copy("new.txt", "backup.txt") # copy shutil.move("new.txt", "archive/") # move
Write a script that creates 5 files named file1.txt through file5.txt, writes data to each, renames them to doc1.txt etc., then deletes doc3.txt.
Directories in Python
Use the os module to work with directories:
import os print(os.getcwd()) # Current working directory os.chdir("C:/Users") # Change directory os.mkdir("new_folder") # Create directory os.makedirs("a/b/c") # Create nested dirs print(os.listdir(".")) # List directory contents os.rmdir("new_folder") # Remove empty directory import shutil shutil.rmtree("a") # Remove directory and all contents # Walk directory tree for root, dirs, files in os.walk("."): for file in files: print(os.path.join(root, file))
Write a program that creates a folder structure project/src/, project/docs/, creates files inside each, then uses os.walk() to list all files in the entire project tree.
The Concept of OOP in Python
Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around objects and classes, rather than functions and logic alone.
The four pillars of OOP:
- Encapsulation: Bundling data and methods that operate on that data within one unit (class). Restricts direct access to some components.
- Abstraction: Hiding complex implementation details and showing only essential features.
- Inheritance: A class (child) can inherit attributes and methods from another class (parent).
- Polymorphism: Different classes can implement the same method differently.
Python is a fully object-oriented language — everything in Python is an object (integers, strings, functions, modules).
Describe a real-world entity (e.g., a car) in OOP terms: identify its attributes (data) and methods (behaviors). Draw the class diagram.
Designing Classes
A class is a blueprint/template defining attributes (data) and methods (behaviours). Defined using the class keyword:
class BankAccount: """Represents a bank account.""" bank_name = "Code Tutorium Bank" # Class attribute def __init__(self, owner, balance=0): self.owner = owner # Instance attribute self.__balance = balance # Private attribute def deposit(self, amount): if amount > 0: self.__balance += amount return self.__balance def get_balance(self): return self.__balance def __str__(self): # String representation return f"{self.owner}'s account: ₹{self.__balance}"
Design a class Library with attributes for book list and methods to add, remove, search, and display books. Include a private attribute for book count.
Creating Objects
An object is an instance of a class — a specific entity created from the class blueprint. Creating an object is called instantiation.
class Student: def __init__(self, name, roll, grade): self.name = name self.roll = roll self.grade = grade def display(self): print(f"Roll {self.roll}: {self.name} — Grade {self.grade}") # Create objects (instantiation) s1 = Student("Alice", 101, "A") s2 = Student("Bob", 102, "B") s3 = Student("Carol", 103, "A") # Each object is independent s1.display() s2.display() print(s1 is s2) # False — different objects print(type(s1)) # <class '__main__.Student'>
Create a Car class and instantiate 3 different car objects. Store them in a list and iterate over them calling a display_info() method on each.
Accessing Attributes
Attributes are accessed using the dot notation (object.attribute). Python provides built-in functions for attribute access:
class Person: def __init__(self, name, age): self.name = name self.age = age p = Person("Alice", 30) # Dot notation print(p.name) # Alice print(p.age) # 30 # Built-in attribute functions print(getattr(p, "name")) # Alice print(getattr(p, "salary", 0)) # 0 (default if missing) print(hasattr(p, "age")) # True print(hasattr(p, "email")) # False # Access class attribute via object print(p.__class__.__name__) # Person print(p.__dict__) # {'name': 'Alice', 'age': 30}
Create an object and use getattr(), hasattr(), and __dict__ to inspect its attributes dynamically without knowing the attribute names in advance.
Editing Class Attributes
Attributes can be dynamically modified, added, or deleted at runtime:
class Employee: company = "TechCorp" # Class attribute def __init__(self, name): self.name = name e1 = Employee("Alice") e2 = Employee("Bob") # Modify instance attribute e1.name = "Alicia" print(e1.name) # Alicia print(e2.name) # Bob — unchanged # Modify class attribute — affects ALL instances Employee.company = "NewCorp" print(e1.company) # NewCorp print(e2.company) # NewCorp # Add new attribute dynamically setattr(e1, "salary", 50000) print(e1.salary) # 50000 # Delete attribute delattr(e1, "salary") del e2.name # Alternative
Create a Config class with class-level settings. Modify class attributes to update global config and instance attributes for per-object overrides. Show the difference.
Built-in Class Attributes
Every Python class automatically has these built-in attributes:
| Attribute | Description |
|---|---|
__name__ | Name of the class as a string |
__doc__ | Class documentation string (docstring) |
__dict__ | Dictionary of class or instance namespace |
__module__ | Module name where the class is defined |
__bases__ | Tuple of base (parent) classes |
__mro__ | Method Resolution Order tuple |
class Animal: """Base class for all animals.""" species = "Unknown" class Dog(Animal): """Represents a dog.""" def bark(self): pass print(Dog.__name__) # Dog print(Dog.__doc__) # Represents a dog. print(Dog.__bases__) # (<class 'Animal'>,) print(Dog.__mro__) # [Dog, Animal, object] print(Dog.__module__) # __main__ print(Dog.__dict__) # {bark: ..., __doc__: ...}
Create an inheritance chain (Animal → Mammal → Dog). Print __mro__, __bases__, and __dict__ for each class and explain the output.
Garbage Collection
Garbage Collection (GC) is Python's automatic memory management system that frees memory occupied by objects that are no longer referenced.
Python uses two mechanisms:
- Reference Counting: Every object tracks how many references point to it. When count reaches 0, memory is freed immediately.
- Cyclic GC: Handles circular references (A references B, B references A) that reference counting alone cannot detect. The
gcmodule manages this.
import gc import sys x = [1, 2, 3] print(sys.getrefcount(x)) # 2 (x + getrefcount arg) y = x # ref count increases print(sys.getrefcount(x)) # 3 del y # ref count decreases del x # ref count → 0, memory freed # Manual GC operations gc.enable() gc.disable() gc.collect() # Force garbage collection print(gc.get_count()) # (gen0, gen1, gen2) counts
Create two objects that reference each other (circular reference). Use the gc module to detect and collect them. Print the reference counts before and after collection.
Destroying Objects
Objects are destroyed when their reference count drops to zero. You can define a destructor using the __del__() special method, which is called just before the object is removed from memory.
class DatabaseConnection: def __init__(self, db_name): self.db_name = db_name print(f"Connection to '{db_name}' opened.") def query(self, sql): print(f"Running: {sql}") def __del__(self): print(f"Connection to '{self.db_name}' closed.") # Object created conn = DatabaseConnection("students_db") conn.query("SELECT * FROM students") # Explicit deletion — __del__ is called del conn # Objects also destroyed at program end
Important Notes:
__del__()is not guaranteed to be called at a specific time.- The preferred way to release resources is using the
withstatement (context managers). - Avoid complex logic in
__del__()as it may cause unpredictable behavior.
Create a FileHandler class with __init__ (opens file), write(), and __del__ (closes file). Use it to write data to a file and observe when the destructor is called.