Python is a high-level, interpreted, general-purpose programming language. It is widely used for web development, data science, artificial intelligence, automation, and more.

Key Features of Python:

  • Easy to read and write with simple syntax.
  • Interpreted language (no compilation needed).
  • Object-oriented and supports functional programming.
  • Extensive standard library and third-party modules.
  • Cross-platform compatibility.
  • Dynamic typing and memory management.

Example: Hello World in Python

# Print "Hello, World!" to the console
print("Hello, World!")  # Output: Hello, World!

Python Installation & Running Programs

You can install Python from python.org. Run programs using:

  • python filename.py — Run script in terminal/command prompt
  • python -i filename.py — Run interactively
  • IDEs: VSCode, PyCharm, Jupyter Notebook

Python Modules and pip

Modules allow code reuse. Use import module_name to include modules.

pip is Python’s package manager to install external libraries:

# Install a library
pip install numpy

# Import and use it
import numpy as np
arr = np.array([1, 2, 3])
print(arr)  # Output: [1 2 3]

Exercises

Exercise 1: Print your name

# Print your name
print("Nisa")  # Output: Nisa

Exercise 2: Add two numbers

# Add two numbers
a = 5  # first number
b = 7  # second number
sum = a + b  # calculate sum
print("Sum:", sum)  # Output: Sum: 12

Python Basics forms the foundation of programming. In this topic, we cover keywords, identifiers, variables, data types, type conversion, input/output, comments, and include examples and exercises.

1. Keywords

Keywords are predefined reserved words in Python that cannot be used as variable names.

import keyword
print(keyword.kwlist)
# Example output: ['False', 'None', 'True', 'and', 'as', ...]
    

2. Identifiers

Identifiers are names you give to variables, functions, classes, etc. Rules:

  • Can contain letters, digits, and underscores
  • Cannot start with a digit
  • Case-sensitive

3. Variables

Variables store values in memory and are referenced by identifiers. Python is dynamically typed.

name = "Nisa"
age = 25
height = 5.4

print(name)    # Output: Nisa
print(age)     # Output: 25
print(height)  # Output: 5.4
    

4. Data Types

Numeric types: int, float, complex

x = 10       # int
y = 3.14     # float
z = 2 + 5j   # complex
print(type(x), type(y), type(z))
# Output:   
    

Sequence types: str (text), list, tuple, range

text = "Hello"             # str
fruits = ["apple","banana"] # list
coords = (10,20)           # tuple
print(text[0])  # H
fruits[1] = "mango"        # list can change
    

Mapping type: dict

person = {"name":"Nisa","age":25}
print(person["name"])      # Nisa
    

Set types: set, frozenset

A = {1,2,2,3}  # duplicates removed -> {1,2,3}
B = set([2,4])
print(A & B)  # intersection
print(A | B)  # union
    

Boolean & None: bool, NoneType

flag = True
x = None
print(type(flag))  # 
print(type(x))     # 
    

5. Type Conversion (Casting)

s = "15"
print(int(s) + 5)        # 20
print(float("3.14") + 1) # 4.14
# int("3.14") would raise ValueError
    

6. Input & Output

name = input("Enter your name: ")
age = int(input("Enter your age: "))
print("Hello,", name, "Age:", age)
    

7. Comments

# This is a single-line comment
'''
This is a 
multi-line comment
'''
    

Exercises (Solutions Included)

Exercise 1 — Identify Types

a = 42
b = 3.14
c = "Python"
d = [1,2,3]
e = (4,5,6)
f = {"name":"John","age":30}
g = {1,2,2,3}
h = True
i = None
# Print type of each variable
      
Solution
variables = [a,b,c,d,e,f,g,h,i]
for v in variables:
    print(v, "->", type(v))
        

Exercise 2 — Create dict & set

# Create dict for a book and set of numbers
book = {"title":"Alchemist","author":"Paulo Coelho","year":1988}
print(book)
numbers = {1,2,2,3,4}
print(numbers)  # duplicates removed
      

Exercise 3 — Even or Odd Check

n = int(input("Enter a number: "))
if n % 2 == 0:
    print("Even")
else:
    print("Odd")
      
Tips:
  • Use type(x) or isinstance(x, T) to check types
  • Empty containers ("", [], {}, set()) are falsy in boolean context
  • Convert strings carefully: int("3.14") fails, do float() first

Operators are symbols used to perform operations on values or variables. Python has several types of operators: arithmetic, comparison, logical, assignment, bitwise, membership, and identity.

1. Arithmetic Operators

Used to perform mathematical operations.

a = 10
b = 3

print(a + b)   # Addition -> 13
print(a - b)   # Subtraction -> 7
print(a * b)   # Multiplication -> 30
print(a / b)   # Division -> 3.3333...
print(a // b)  # Floor division -> 3
print(a % b)   # Modulus -> 1
print(a ** b)  # Exponent -> 1000
    

2. Comparison Operators

Compare two values and return True or False.

x = 5
y = 10

print(x == y)  # False
print(x != y)  # True
print(x > y)   # False
print(x < y)   # True
print(x >= y)  # False
print(x <= y)  # True
    

3. Logical Operators

Combine boolean values.

p = True
q = False

print(p and q)  # False
print(p or q)   # True
print(not p)    # False
    

4. Assignment Operators

Used to assign values to variables, optionally combined with arithmetic.

x = 5
x += 3  # x = x + 3 -> 8
x -= 2  # x = x - 2 -> 6
x *= 2  # x = x * 2 -> 12
x /= 4  # x = x / 4 -> 3.0
x %= 2  # x = x % 2 -> 1.0
    

5. Bitwise Operators

Operate on bits of integers.

a = 5   # 0b0101
b = 3   # 0b0011

print(a & b)   # AND -> 1 (0b0001)
print(a | b)   # OR -> 7 (0b0111)
print(a ^ b)   # XOR -> 6 (0b0110)
print(~a)      # NOT -> -6
print(a << 1)  # Left shift -> 10 (0b1010)
print(a >> 1)  # Right shift -> 2 (0b0010)
    

6. Membership Operators

Check if a value exists in a sequence.

fruits = ["apple", "banana", "mango"]
print("apple" in fruits)      # True
print("orange" not in fruits) # True
    

7. Identity Operators

Check if two variables refer to the same object in memory.

x = [1,2,3]
y = x
z = [1,2,3]

print(x is y)  # True
print(x is z)  # False
print(x == z)  # True (values equal)
    

Exercises (Solutions Included)

Exercise 1 — Arithmetic Practice

# Calculate and print:
# 1. Sum of 12 and 8
# 2. 15 raised to 3
# 3. Floor division of 17 by 4
      
Solution
print(12 + 8)   # 20
print(15 ** 3)  # 3375
print(17 // 4)  # 4
        

Exercise 2 — Logical & Comparison

# Check if 10 is greater than 5 AND less than 20
# Check if 15 is NOT equal to 15
      
Solution
print(10 > 5 and 10 < 20)  # True
print(not 15 != 15)         # True
        

Exercise 3 — Membership & Identity

# Check if 'Python' is in a list
# Compare two lists for identity and equality
      
Solution
languages = ["Python", "Java", "C++"]
print("Python" in languages)  # True

list1 = [1,2,3]
list2 = list1
list3 = [1,2,3]

print(list1 is list2)  # True
print(list1 is list3)  # False
print(list1 == list3)  # True
        

Strings are sequences of characters enclosed in quotes. Python supports single (' '), double (" "), and triple quotes (''' ''' or """ """). Strings are immutable, meaning once created, their content cannot be changed directly.

1. String Indexing

Each character in a string has an index, starting from 0. Negative indexing starts from -1 (last character).

text = "Python"
print(text[0])   # P
print(text[3])   # h
print(text[-1])  # n (last character)
print(text[-3])  # h
    

2. String Slicing

Extract a substring using text[start:end:step]. start is inclusive, end is exclusive.

text = "Python"

print(text[0:4])   # Pyth (0 to 3)
print(text[2:])    # thon (from index 2 to end)
print(text[:4])    # Pyth (start to index 3)
print(text[::2])   # Pto (every 2nd character)
print(text[::-1])  # nohtyP (reverse string)
    

3. Common String Methods

Python provides many built-in methods for strings.

s = "  Python Programming  "

print(len(s))           # 20
print(s.upper())        # '  PYTHON PROGRAMMING  '
print(s.lower())        # '  python programming  '
print(s.strip())        # 'Python Programming' (removes spaces)
print(s.replace("Python","Java"))  # '  Java Programming  '
print(s.split())        # ['Python', 'Programming']
print(s.startswith("  Py"))  # True
print(s.endswith("ing  "))    # True
print(s.count("o"))     # 2
print(s.find("Pro"))    # 7 (first index)
print(s.isalpha())      # False (spaces present)
print(s.isdigit())      # False

4. f-Strings & String Formatting

f-Strings (formatted string literals) make it easy to insert variables into strings.

name = "Nisa"
age = 25

print(f"My name is {name} and I am {age} years old.")

# Older formatting
print("My name is {} and I am {} years old.".format(name, age))
print("Name: %s, Age: %d" % (name, age))
    

5. String Concatenation & Repetition

s1 = "Hello"
s2 = "World"

print(s1 + " " + s2)  # Hello World
print(s1 * 3)         # HelloHelloHello
    

Exercises (Solutions Included)

Exercise 1 — Indexing & Slicing

# Extract 'thon' from "Python"
# Reverse the string "Python"
# Extract every 2nd character from "Python Programming"
      
Solution
text = "Python"
print(text[2:])       # 'thon'
print(text[::-1])     # 'nohtyP'

text2 = "Python Programming"
print(text2[::2])     # 'Pto rgamn'
        

Exercise 2 — String Methods

# Remove spaces from "   Hello World  "
# Replace "World" with "Python"
# Count occurrences of 'l'
      
Solution
s = "   Hello World  "
print(s.strip())                 # 'Hello World'
print(s.replace("World","Python"))  # '   Hello Python  '
print(s.count("l"))              # 3
        

Exercise 3 — f-Strings & Concatenation

# Create variables: name="Alice", age=30
# Print: "My name is Alice and I am 30 years old." using f-string
# Concatenate "Hello" and "Python" with a space
      
Solution
name = "Alice"
age = 30
print(f"My name is {name} and I am {age} years old.")  # f-string

s1 = "Hello"
s2 = "Python"
print(s1 + " " + s2)  # Concatenation -> 'Hello Python'
        

Lists are ordered, mutable collections of items. They can hold elements of any data type, including numbers, strings, other lists, or mixed types. Lists are defined using square brackets [].

1. Creating & Accessing Lists

# Creating lists
numbers = [1, 2, 3, 4, 5]
fruits = ["apple", "banana", "cherry"]
mixed = [1, "apple", 3.5, True]

# Accessing elements
print(numbers[0])     # 1
print(fruits[1])      # banana
print(mixed[-1])      # True (last element)

# Nested list
nested = [[1,2],[3,4]]
print(nested[0][1])   # 2

2. List Methods

Python lists provide many built-in methods for common operations.

fruits = ["apple", "banana", "cherry"]

fruits.append("orange")    # Add to end
fruits.insert(1, "kiwi")   # Insert at index 1
fruits.extend(["mango","grape"]) # Add multiple items
print(fruits)

fruits.remove("banana")    # Remove by value
popped = fruits.pop()      # Remove last item
print(popped)
print(fruits)

fruits.sort()              # Sort ascending (alphabetical)
fruits.reverse()           # Reverse order
print(fruits)

print(len(fruits))         # Number of items
print(fruits.count("apple")) # Count occurrences

3. List Slicing

numbers = [10, 20, 30, 40, 50]

print(numbers[1:4])   # [20, 30, 40] (index 1 to 3)
print(numbers[:3])    # [10, 20, 30] (start to index 2)
print(numbers[::2])   # [10, 30, 50] (every 2nd element)
print(numbers[::-1])  # [50, 40, 30, 20, 10] (reverse)

4. Nested Lists

Lists can contain other lists (nested), which can be accessed using multiple indices.

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

print(matrix[0][1])   # 2
print(matrix[2][0])   # 7

Exercises (Solutions Included)

Exercise 1 — Basic list operations

# Create a list of 5 colors
# Add one color at the end
# Insert a color at index 2
# Remove a color by name
# Print final list
Solution
colors = ["red", "blue", "green", "yellow", "pink"]
colors.append("orange")
colors.insert(2, "purple")
colors.remove("blue")
print(colors)  # ['red', 'purple', 'green', 'yellow', 'pink', 'orange']
        

Exercise 2 — List slicing & reverse

# Given numbers = [10,20,30,40,50]
# Extract [20,30,40]
# Reverse the list
# Get every second element
Solution
numbers = [10,20,30,40,50]

print(numbers[1:4])   # [20,30,40]
print(numbers[::-1])  # [50,40,30,20,10]
print(numbers[::2])   # [10,30,50]
        

Exercise 3 — Nested list access

# Access the element 6 in the nested list below
matrix = [
    [1,2,3],
    [4,5,6],
    [7,8,9]
]
Solution
print(matrix[1][2])  # 6
        
Tips:
  • Lists are mutable — you can change elements, unlike strings or tuples.
  • You can mix different data types in a list, but it’s recommended to keep them consistent for clarity.
  • Use len(list) to find the number of elements and list.count(item) to count occurrences.
  • Nested lists can be used to represent matrices or grids.

Tuples and Sets are two important collection types in Python. Tuples are ordered and immutable, while sets are unordered collections of unique elements.

1. Tuples

Tuples are similar to lists but immutable — once created, their elements cannot be changed. Use parentheses () to define a tuple.

# Creating tuples
empty_tuple = ()
numbers = (1, 2, 3, 4)
mixed = (1, "apple", 3.5, True)

# Accessing elements
print(numbers[0])      # 1
print(mixed[-1])       # True

# Nested tuple
nested = ((1,2), (3,4))
print(nested[1][0])    # 3

# Tuple unpacking
a, b, c, d = mixed
print(a, b, c, d)      # 1 apple 3.5 True

Tuple Methods

Tuples have limited methods because they are immutable:

numbers = (1, 2, 3, 2, 2)

print(numbers.count(2))  # 3 — counts occurrences
print(numbers.index(3))  # 2 — index of first occurrence

2. Sets

Sets are unordered collections of unique items. Use {} or set() to define a set.

# Creating sets
A = {1, 2, 3, 3}        # duplicates removed -> {1,2,3}
B = set([2, 4, 5])
print(A)                # {1, 2, 3}
print(B)                # {2, 4, 5}

# Adding and removing elements
A.add(4)
A.remove(1)
print(A)                # {2, 3, 4}

# Membership test
print(3 in A)           # True
print(10 in B)          # False

Set Operations

X = {1, 2, 3}
Y = {3, 4, 5}

print(X | Y)   # Union -> {1,2,3,4,5}
print(X & Y)   # Intersection -> {3}
print(X - Y)   # Difference -> {1,2}
print(X ^ Y)   # Symmetric difference -> {1,2,4,5}

Exercises (Solutions Included)

Exercise 1 — Tuple basics

# Create a tuple with 5 elements of your choice
# Access the first and last elements
# Unpack elements into variables
Solution
my_tuple = (10, "apple", 3.5, True, "banana")
print(my_tuple[0])    # 10
print(my_tuple[-1])   # banana

a, b, c, d, e = my_tuple
print(a, b, c, d, e)  # 10 apple 3.5 True banana

Exercise 2 — Set operations

# Create two sets: {1,2,3,4} and {3,4,5,6}
# Perform union, intersection, difference, symmetric difference
Solution
A = {1,2,3,4}
B = {3,4,5,6}

print(A | B)   # {1,2,3,4,5,6} — union
print(A & B)   # {3,4} — intersection
print(A - B)   # {1,2} — difference
print(A ^ B)   # {1,2,5,6} — symmetric difference

Exercise 3 — Set membership & add/remove

# Create a set of numbers {10, 20, 30}
# Add 40, remove 20, check if 10 exists
Solution
numbers = {10,20,30}
numbers.add(40)
numbers.remove(20)
print(10 in numbers)   # True
print(numbers)         # {10,30,40}
Tips:
  • Tuples are immutable — good for fixed collections or dictionary keys.
  • Sets automatically remove duplicates and allow fast membership tests.
  • Use set operations (union, intersection, difference) for mathematical tasks.
  • Remember: sets are unordered, so indexing is not allowed.

Dictionaries are Python’s built-in mapping type. They store data as key-value pairs, allowing fast lookups, insertion, and deletion by key. Dictionaries are unordered (Python ≥3.7 maintains insertion order), mutable, and keys must be immutable.

1. Creating Dictionaries

# Empty dictionary
my_dict = {}

# Dictionary with initial values
person = {"name": "Nisa", "age": 25, "city": "Kochi"}

print(person)
print(type(person))   # <class 'dict'>

2. Accessing Values

# Access by key
print(person["name"])     # Nisa
print(person.get("age"))  # 25

# Safe access with default
print(person.get("email", "Not available"))  # Not available

3. Adding & Updating Items

# Adding new key-value
person["email"] = "nisa@example.com"

# Updating existing key
person["age"] = 26

print(person)
# Output: {'name': 'Nisa', 'age': 26, 'city': 'Kochi', 'email': 'nisa@example.com'}

4. Removing Items

# Using del
del person["city"]

# Using pop (returns value)
email = person.pop("email")

# Clear all items
# person.clear()

print(person)  # {'name': 'Nisa', 'age': 26}

5. Dictionary Methods

person = {"name": "Nisa", "age": 25, "city": "Kochi"}

print(person.keys())    # dict_keys(['name', 'age', 'city'])
print(person.values())  # dict_values(['Nisa', 25, 'Kochi'])
print(person.items())   # dict_items([('name', 'Nisa'), ('age', 25), ('city', 'Kochi')])

# Iterating
for key, value in person.items():
    print(key, "->", value)

6. Nested Dictionaries

students = {
    "student1": {"name": "Alice", "age": 20},
    "student2": {"name": "Bob", "age": 22}
}

print(students["student1"]["name"])  # Alice

Exercises (Solutions Included)

Exercise 1 — Create & Access

# Create a dictionary for a book with keys: title, author, year
# Access and print each value
Solution
book = {"title": "The Alchemist", "author": "Paulo Coelho", "year": 1988}
print(book["title"])
print(book["author"])
print(book.get("year"))

Exercise 2 — Add & Update

# Add a new key 'price' with value 500
# Update 'year' to 1993
# Print final dictionary
Solution
book["price"] = 500
book["year"] = 1993
print(book)
# Output: {'title': 'The Alchemist', 'author': 'Paulo Coelho', 'year': 1993, 'price': 500}

Exercise 3 — Iterating & Nested Dict

# Create nested dictionary of 2 students with name and age
# Print each student's name and age
Solution
students = {
    "student1": {"name": "Alice", "age": 20},
    "student2": {"name": "Bob", "age": 22}
}

for s in students:
    print(students[s]["name"], "is", students[s]["age"], "years old")

# Output:
# Alice is 20 years old
# Bob is 22 years old
Tips:
  • Dictionaries are mutable and can contain any data type as value (including lists or dicts).
  • Keys must be immutable (string, number, tuple, etc.)
  • Use .get() for safe access without KeyError.
  • Nested dictionaries are useful for structured data (like JSON).

Conditional statements in Python allow your program to make decisions based on conditions. You can execute certain blocks of code only if a condition is True. The main statements are if, elif, and else.

1. The if Statement

Use if to execute a block when a condition is True.

x = 10
if x > 5:
    print("x is greater than 5")
# Output: x is greater than 5

2. The if-else Statement

Use else to execute a block when the condition is False.

x = 3
if x > 5:
    print("x is greater than 5")
else:
    print("x is 5 or less")
# Output: x is 5 or less

3. The if-elif-else Statement

Use elif to check multiple conditions sequentially.

marks = 85

if marks >= 90:
    grade = "A"
elif marks >= 75:
    grade = "B"
elif marks >= 60:
    grade = "C"
else:
    grade = "D"

print("Grade:", grade)
# Output: Grade: B

4. Nested Conditions

You can put an if inside another if to check additional conditions.

x = 15
if x > 0:
    if x % 2 == 0:
        print("Positive even number")
    else:
        print("Positive odd number")
# Output: Positive odd number

5. Conditional Expressions (Ternary Operator)

age = 18
status = "Adult" if age >= 18 else "Minor"
print(status)
# Output: Adult

6. Boolean Contexts in Conditions

Python treats certain values as False in conditions: 0, 0.0, "", [], {}, set(), None. Everything else is True.

items = []
if not items:
    print("List is empty")
# Output: List is empty

Exercises (Solutions Included)

Exercise 1 — Check Positive/Negative/Zero

# Ask user for a number and print whether it's positive, negative, or zero
Solution
n = int(input("Enter a number: "))
if n > 0:
    print("Positive")
elif n < 0:
    print("Negative")
else:
    print("Zero")

Exercise 2 — Grading System

# Ask user for marks and print grade
# A: 90+, B: 75-89, C: 60-74, D: below 60
Solution
marks = int(input("Enter marks: "))
if marks >= 90:
    grade = "A"
elif marks >= 75:
    grade = "B"
elif marks >= 60:
    grade = "C"
else:
    grade = "D"

print("Grade:", grade)

Exercise 3 — Nested Condition

# Ask user for a number and print:
# "Positive even", "Positive odd", "Negative", "Zero"
Solution
n = int(input("Enter a number: "))
if n > 0:
    if n % 2 == 0:
        print("Positive even")
    else:
        print("Positive odd")
elif n < 0:
    print("Negative")
else:
    print("Zero")
Tips:
  • Use elif instead of multiple if to avoid unnecessary checks.
  • Remember that indentation is critical — Python uses it to define code blocks.
  • Boolean expressions like n > 0 or not items control the flow.
  • Ternary expressions are useful for short conditional assignments.

Loops in Python allow you to repeatedly execute a block of code until a condition is met. Loops help avoid repetitive code and make programs more efficient.

1. The for Loop

The for loop iterates over a sequence (like a list, tuple, string, or range).

# Loop through a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)
# Output:
# apple
# banana
# cherry

# Loop using range
for i in range(5):
    print(i)
# Output: 0 1 2 3 4

2. The while Loop

The while loop executes as long as a condition is True.

count = 0
while count < 5:
    print(count)
    count += 1
# Output: 0 1 2 3 4

3. break and continue

break stops the loop completely. continue skips the current iteration and moves to the next.

# break example
for i in range(10):
    if i == 5:
        break
    print(i)
# Output: 0 1 2 3 4

# continue example
for i in range(5):
    if i == 2:
        continue
    print(i)
# Output: 0 1 3 4

4. Nested Loops

Loops inside loops are called nested loops. Useful for matrices, tables, and patterns.

# Print a 3x3 grid
for i in range(1, 4):
    for j in range(1, 4):
        print(f"({i},{j})", end=" ")
    print()
# Output:
# (1,1) (1,2) (1,3)
# (2,1) (2,2) (2,3)
# (3,1) (3,2) (3,3)

5. else with loops

Python allows else after loops, which executes when the loop ends naturally (not via break).

for i in range(3):
    print(i)
else:
    print("Loop finished without break")
# Output:
# 0
# 1
# 2
# Loop finished without break

Exercises (Solutions Included)

Exercise 1 — Sum of first n numbers

# Task: Ask user for a number n and print the sum of 1 to n
Solution
n = int(input("Enter a number: "))
sum = 0
for i in range(1, n+1):
    sum += i
print("Sum =", sum)

Exercise 2 — Print even numbers using while

# Task: Print all even numbers from 1 to 20 using while loop
Solution
i = 1
while i <= 20:
    if i % 2 == 0:
        print(i, end=" ")
    i += 1
# Output: 2 4 6 8 10 12 14 16 18 20

Exercise 3 — Multiplication Table (Nested Loop)

# Task: Print 1 to 5 multiplication table
Solution
for i in range(1, 6):
    for j in range(1, 6):
        print(f"{i*j}", end="\t")
    print()
# Output:
# 1   2   3   4   5
# 2   4   6   8   10
# 3   6   9   12  15
# 4   8   12  16  20
# 5   10  15  20  25
Tips:
  • Use range(start, end, step) to customize loops.
  • Remember: break stops the loop completely; continue skips current iteration.
  • Nested loops can generate patterns, tables, and matrices.
  • Using else with loops can help check if a loop completed without interruption.

Functions are reusable blocks of code that perform a specific task. They help in organizing code, reducing repetition, and improving readability.

1. Defining and Calling Functions

Use the def keyword to define a function and call it by its name.

# Define a function
def greet():
    print("Hello, welcome to Python!")

# Call the function
greet()
# Output: Hello, welcome to Python!

2. Function Arguments

Functions can take inputs called arguments.

a) Positional Arguments
def add(a, b):
    return a + b

print(add(5, 3))  # Output: 8
b) Keyword Arguments
def greet(name, msg):
    print(f"Hello {name}, {msg}")

greet(msg="Good Morning!", name="Nisa")
# Output: Hello Nisa, Good Morning!
c) Default Arguments
def greet(name="Guest"):
    print(f"Hello {name}!")

greet()        # Hello Guest!
greet("Nisa")  # Hello Nisa!
d) Arbitrary Arguments (*args & **kwargs)
def add_numbers(*nums):
    return sum(nums)

print(add_numbers(1,2,3,4))  # Output: 10

def print_info(**info):
    for key, value in info.items():
        print(key, ":", value)

print_info(name="Nisa", age=25)
# Output:
# name : Nisa
# age : 25

3. Return Statement

Use return to send a value back to the caller.

def square(x):
    return x**2

result = square(5)
print(result)  # Output: 25

4. Variable Scope

Variables defined inside a function are local, outside are global.

x = 10  # global

def demo():
    x = 5  # local
    print("Inside function:", x)

demo()               # Inside function: 5
print("Outside function:", x)  # Outside function: 10

Exercises (Solutions Included)

Exercise 1 — Calculator Function

# Task: Write a function to add, subtract, multiply, divide two numbers
Solution
def calculator(a, b):
    print("Addition:", a + b)
    print("Subtraction:", a - b)
    print("Multiplication:", a * b)
    print("Division:", a / b)

calculator(10, 5)
# Output:
# Addition: 15
# Subtraction: 5
# Multiplication: 50
# Division: 2.0

Exercise 2 — Factorial Function

# Task: Create a function to calculate factorial of a number
Solution
def factorial(n):
    result = 1
    for i in range(1, n+1):
        result *= i
    return result

print(factorial(5))  # Output: 120

Exercise 3 — Check Prime

# Task: Write a function to check if a number is prime
Solution
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

# Test the function
for num in [1,2,3,4,5,16,17]:
    print(num, "is prime?", is_prime(num))

# Output:
# 1 is prime? False
# 2 is prime? True
# 3 is prime? True
# 4 is prime? False
# 5 is prime? True
# 16 is prime? False
# 17 is prime? True
Tips:
  • Use functions to organize code and avoid repetition.
  • Default arguments help make functions flexible.
  • Use *args for multiple positional arguments and **kwargs for multiple keyword arguments.
  • Always use return if you want to use the result outside the function.

Modules are files containing Python code (functions, variables, classes) that you can reuse in other programs. Packages are collections of modules organized in directories with an __init__.py file.

1. Importing Modules

You can use import to access built-in or custom modules.

# Import the math module
import math

print(math.sqrt(16))    # Output: 4.0
print(math.pi)          # Output: 3.141592653589793

# Import specific function
from math import factorial
print(factorial(5))     # Output: 120

2. Creating Your Own Module

Create a Python file (e.g., my_module.py) and define functions/variables inside it.

# my_module.py
def greet(name):
    return f"Hello, {name}!"

PI = 3.14

Use it in another file:

# main.py
import my_module

print(my_module.greet("Nisa"))  # Output: Hello, Nisa!
print(my_module.PI)              # Output: 3.14

3. Using Built-in Modules

Python provides many built-in modules:

# Random numbers
import random
print(random.randint(1,10))  # Random integer between 1 and 10

# Date and Time
import datetime
now = datetime.datetime.now()
print("Current date and time:", now)

# OS operations
import os
print("Current working directory:", os.getcwd())

4. Installing and Using External Packages

Use pip to install packages from PyPI.

# Install requests package
# pip install requests

import requests
response = requests.get("https://api.github.com")
print(response.status_code)  # Output: 200

5. The __name__ Variable

Check if the module is run directly or imported:

# my_module.py
def hello():
    print("Hello!")

if __name__ == "__main__":
    print("This runs only when the file is executed directly")

Exercises (Solutions Included)

Exercise 1 — Create & Import a Custom Module

# Task: Create a module with a function that squares a number and a variable storing your name. Import and use them in another file.
Solution
# my_module.py
def square(n):
    return n**2

name = "Nisa"

# main.py
import my_module

print(my_module.square(6))  # Output: 36
print(my_module.name)       # Output: Nisa

Exercise 2 — Use Random Module

# Task: Generate a random number between 50 and 100 and pick a random item from a list
Solution
import random

num = random.randint(50,100)
print("Random number:", num)

fruits = ["apple", "banana", "mango"]
print("Random fruit:", random.choice(fruits))

Exercise 3 — Date & Time Module

# Task: Print current date and time, and also print just the year, month, day
Solution
import datetime

now = datetime.datetime.now()
print("Current date and time:", now)
print("Year:", now.year)
print("Month:", now.month)
print("Day:", now.day)
Tips:
  • Modules help organize code and reuse functionality.
  • Packages are directories containing modules and an __init__.py file.
  • Use built-in modules before installing external packages.
  • Check __name__ to see if a file is run directly or imported.

File handling allows Python programs to read from and write to files on your computer. This is essential for storing data, logs, or any persistent information.

1. Opening a File

Use the open() function with a filename and mode.

# Open a file for reading
f = open("example.txt", "r")

# Modes:
# 'r' -> read (default)
# 'w' -> write (creates or overwrites)
# 'a' -> append
# 'x' -> create new file, fails if exists
# 'b' -> binary mode
# 't' -> text mode (default)

2. Reading Files

Use read(), readline(), or readlines() to read content.

# Read entire file
with open("example.txt", "r") as f:
    content = f.read()
    print(content)

# Read line by line
with open("example.txt", "r") as f:
    for line in f:
        print(line.strip())  # remove newline characters

3. Writing to Files

Use write() to add content. Use 'w' mode to overwrite or 'a' mode to append.

# Write to a file
with open("example.txt", "w") as f:
    f.write("Hello, Python!\n")
    f.write("This is a tutorial.\n")

# Append to file
with open("example.txt", "a") as f:
    f.write("Appending a new line.\n")

4. File Handling Best Practice: Using with

The with statement automatically closes the file after use.

with open("example.txt", "r") as f:
    data = f.read()
# No need to call f.close()

5. Working with CSV Files

Use the csv module to handle CSV files easily.

import csv

# Writing CSV
with open("data.csv", "w", newline="") as file:
    writer = csv.writer(file)
    writer.writerow(["Name", "Age", "City"])
    writer.writerow(["Nisa", 25, "Kochi"])

# Reading CSV
with open("data.csv", "r") as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)

6. Common File Operations (OS module)

import os

# Check if file exists
print(os.path.exists("example.txt"))  # True or False

# Rename a file
# os.rename("example.txt", "new_example.txt")

# Delete a file
# os.remove("new_example.txt")

Exercises (Solutions Included)

Exercise 1 — Write & Read File

# Task: Write 3 lines of text to a file and then read & print them
Solution
lines = ["Python is fun.\n", "File handling is important.\n", "Always close files or use with.\n"]

# Writing to file
with open("exercise1.txt", "w") as f:
    f.writelines(lines)

# Reading from file
with open("exercise1.txt", "r") as f:
    content = f.read()
    print(content)

Exercise 2 — Append Data

# Task: Append your name to a file and print all lines
Solution
with open("exercise2.txt", "a") as f:
    f.write("Nisa\n")

with open("exercise2.txt", "r") as f:
    for line in f:
        print(line.strip())

Exercise 3 — CSV File Handling

# Task: Create CSV file with Name, Age; Read and print rows
Solution
import csv

# Write CSV
with open("exercise3.csv", "w", newline="") as file:
    writer = csv.writer(file)
    writer.writerow(["Name", "Age"])
    writer.writerow(["Nisa", 25])
    writer.writerow(["Ali", 30])

# Read CSV
with open("exercise3.csv", "r") as file:
    reader = csv.reader(file)
    for row in reader:
        print(row)
Tips:
  • Always use with to handle files safely.
  • Use 'r' for reading, 'w' for writing (overwrite), 'a' for appending.
  • Use modules like csv and os for easier file management.
  • Remember to strip newline characters when reading lines.

Exception Handling allows your Python programs to handle errors gracefully without crashing. Exceptions occur when the program encounters unexpected situations, such as division by zero, file not found, or invalid input.

1. Basics of Exceptions

Python raises built-in exceptions for common errors:

  • ZeroDivisionError – dividing by zero
  • ValueError – invalid value for a function (e.g., int("abc"))
  • FileNotFoundError – file does not exist
  • TypeError – operation on incompatible types
# Example of exception
a = 10
b = 0
# print(a / b)  # ZeroDivisionError: division by zero

2. try and except

Use try block to wrap code that may raise an exception, and except block to handle it.

try:
    a = int(input("Enter a number: "))
    b = int(input("Enter another number: "))
    result = a / b
    print("Result:", result)
except ZeroDivisionError:
    print("Cannot divide by zero!")
except ValueError:
    print("Invalid input! Please enter a number.")

3. Handling Multiple Exceptions

You can handle multiple exceptions in a single try block.

try:
    num = int(input("Enter a number: "))
    print(10 / num)
except (ValueError, ZeroDivisionError) as e:
    print("Error occurred:", e)

4. else and finally

  • else – runs if no exception occurs
  • finally – runs no matter what (useful for cleanup like closing files)

try:
    x = int(input("Enter a number: "))
    print("10 / x =", 10 / x)
except ZeroDivisionError:
    print("Cannot divide by zero!")
else:
    print("No errors occurred!")
finally:
    print("Execution finished.")

5. Raising Exceptions

You can raise exceptions manually using raise.

def check_age(age):
    if age < 0:
        raise ValueError("Age cannot be negative!")
    else:
        print("Valid age:", age)

check_age(25)  # Valid
# check_age(-5)  # ValueError: Age cannot be negative!

6. Custom Exceptions

You can define your own exception classes by inheriting from Exception.

class MyError(Exception):
    pass

try:
    raise MyError("This is a custom error!")
except MyError as e:
    print(e)

Exercises (Solutions Included)

Exercise 1 — Handle Division

# Task: Ask user for two numbers and divide them. Handle zero division and invalid input
Solution
try:
    a = int(input("Enter numerator: "))
    b = int(input("Enter denominator: "))
    print("Result:", a / b)
except ZeroDivisionError:
    print("Error: Cannot divide by zero!")
except ValueError:
    print("Error: Invalid input! Enter numbers only.")

Exercise 2 — File Handling with Exceptions

# Task: Try to open a file. Handle if file does not exist.
Solution
try:
    with open("nonexistent.txt", "r") as f:
        data = f.read()
        print(data)
except FileNotFoundError:
    print("File not found. Please check the filename.")

Exercise 3 — Raise Custom Exception

# Task: Raise an exception if input number is negative
Solution
num = int(input("Enter a number: "))
if num < 0:
    raise ValueError("Negative number not allowed!")
else:
    print("You entered:", num)
Tips:
  • Always handle exceptions to prevent program crashes.
  • Use finally to perform cleanup tasks.
  • Custom exceptions make your code more readable and maintainable.
  • Group multiple exceptions in a tuple when handling similar errors.

Object-Oriented Programming (OOP) is a programming paradigm that uses objects and classes to structure code. Python supports OOP concepts like classes, objects, inheritance, encapsulation, and polymorphism.

1. Classes and Objects

A class is a blueprint for creating objects. An object is an instance of a class.

# Define a class
class Person:
    def __init__(self, name, age):
        self.name = name  # attribute
        self.age = age

    def greet(self):
        print(f"Hello, my name is {self.name} and I am {self.age} years old.")

# Create objects
p1 = Person("Alice", 25)
p2 = Person("Bob", 30)

p1.greet()  # Hello, my name is Alice and I am 25 years old.
p2.greet()  # Hello, my name is Bob and I am 30 years old.

2. Attributes and Methods

Attributes store object data. Methods are functions inside a class that operate on its attributes.

class Circle:
    pi = 3.14159  # class attribute

    def __init__(self, radius):
        self.radius = radius  # instance attribute

    def area(self):
        return Circle.pi * self.radius ** 2

c = Circle(5)
print("Area:", c.area())  # Area: 78.53975

3. Inheritance

Inheritance allows a class (child/subclass) to reuse attributes and methods from another class (parent/superclass).

Single Inheritance
class Animal:
    def speak(self):
        print("Some sound")

class Dog(Animal):
    def bark(self):
        print("Woof!")

d = Dog()
d.speak()  # Some sound
d.bark()   # Woof!
Multiple Inheritance
class Flyer:
    def fly(self):
        print("Flying high")

class Swimmer:
    def swim(self):
        print("Swimming in water")

class Duck(Flyer, Swimmer):
    pass

d = Duck()
d.fly()   # Flying high
d.swim()  # Swimming in water
Multilevel Inheritance
class Grandparent:
    def skill(self):
        print("Gardening")

class Parent(Grandparent):
    def hobby(self):
        print("Painting")

class Child(Parent):
    def game(self):
        print("Playing football")

c = Child()
c.skill()  # Gardening
c.hobby()  # Painting
c.game()   # Playing football
Hierarchical Inheritance
class Vehicle:
    def move(self):
        print("Moving")

class Car(Vehicle):
    def wheels(self):
        print("4 wheels")

class Bike(Vehicle):
    def wheels(self):
        print("2 wheels")

c = Car()
b = Bike()
c.move(); c.wheels()  # Moving, 4 wheels
b.move(); b.wheels()  # Moving, 2 wheels

4. Encapsulation

Encapsulation hides the internal state of objects. Use private attributes with a leading underscore _ or double underscore __.

class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # private attribute

    def deposit(self, amount):
        self.__balance += amount

    def withdraw(self, amount):
        if amount <= self.__balance:
            self.__balance -= amount
        else:
            print("Insufficient funds")

    def get_balance(self):
        return self.__balance

acct = BankAccount(1000)
acct.deposit(500)
acct.withdraw(300)
print("Balance:", acct.get_balance())  # Balance: 1200

5. Polymorphism

Polymorphism allows different objects to respond to the same method differently.

class Cat:
    def speak(self):
        print("Meow!")

class Dog:
    def speak(self):
        print("Woof!")

def animal_sound(animal):
    animal.speak()

c = Cat()
d = Dog()
animal_sound(c)  # Meow!
animal_sound(d)  # Woof!

6. Special (Magic/Dunder) Methods

Python classes have special methods like __init__, __str__, __add__ to customize behavior.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

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

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

p1 = Point(2,3)
p2 = Point(4,5)
print(p1)        # Point(2, 3)
p3 = p1 + p2
print(p3)        # Point(6, 8)

Exercises (Solutions Included)

Exercise 1 — Create a Class

# Task: Create a class Student with attributes name, age, grade. Add method display_info()
Solution
class Student:
    def __init__(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def display_info(self):
        print(f"{self.name}, Age: {self.age}, Grade: {self.grade}")

s = Student("Alice", 16, "10th")
s.display_info()  # Alice, Age: 16, Grade: 10th

Exercise 2 — Inheritance

# Task: Create Parent class Animal with method sound(), and Child class Dog that inherits and adds method bark()
Solution
class Animal:
    def sound(self):
        print("Some sound")

class Dog(Animal):
    def bark(self):
        print("Woof!")

d = Dog()
d.sound()  # Some sound
d.bark()   # Woof!

Exercise 3 — Polymorphism

# Task: Create classes Cat and Dog with method speak(), and a function that calls speak() for any animal
Solution
class Cat:
    def speak(self):
        print("Meow!")

class Dog:
    def speak(self):
        print("Woof!")

animals = [Cat(), Dog()]
for a in animals:
    a.speak()
# Output:
# Meow!
# Woof!

Exercise 4 — Private Attributes

# Task: Create class Account with private balance attribute, deposit() and withdraw() methods
Solution
class Account:
    def __init__(self, balance):
        self.__balance = balance

    def deposit(self, amount):
        self.__balance += amount

    def withdraw(self, amount):
        if amount <= self.__balance:
            self.__balance -= amount
        else:
            print("Insufficient funds")

    def get_balance(self):
        return self.__balance

a = Account(1000)
a.deposit(500)
a.withdraw(200)
print(a.get_balance())  # 1300
Tips:
  • Use classes to structure your code and group related data and functions.
  • Inheritance helps reuse code and implement hierarchical relationships.
  • Encapsulation protects sensitive data in objects.
  • Polymorphism allows flexible code by using common interfaces.
  • Special methods can make your classes behave like built-in types.

Python Advanced Concepts help write cleaner, efficient, and more Pythonic code. This topic covers list comprehensions, generators & iterators, lambda functions, and decorators.

1. List Comprehensions

List comprehension provides a concise way to create lists using a single line of code. Syntax: [expression for item in iterable if condition]

# Create a list of squares from 1 to 5
squares = [x**2 for x in range(1, 6)]
print(squares)  # [1, 4, 9, 16, 25]

# List of even numbers only
evens = [x for x in range(10) if x % 2 == 0]
print(evens)  # [0, 2, 4, 6, 8]

2. Iterators

An iterator is an object that can be iterated (one item at a time). Use iter() and next() to access items.

my_list = [1, 2, 3]
it = iter(my_list)

print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# next(it)  # StopIteration error if exceeded

3. Generators

Generators are special iterators created with yield that generate values on-the-fly (lazy evaluation).

def my_generator(n):
    for i in range(n):
        yield i**2

gen = my_generator(5)
for val in gen:
    print(val)
# Output: 0 1 4 9 16

4. Lambda Functions

Lambda functions are anonymous (unnamed) functions for small tasks. Syntax: lambda arguments: expression

# Add two numbers
add = lambda x, y: x + y
print(add(3, 5))  # 8

# Sort a list of tuples by second element
data = [(1, 'b'), (2, 'a'), (3, 'c')]
data.sort(key=lambda x: x[1])
print(data)  # [(2, 'a'), (1, 'b'), (3, 'c')]

5. Decorators

Decorators allow you to modify the behavior of a function without changing its code. Syntax: @decorator

def decorator(func):
    def wrapper():
        print("Before function call")
        func()
        print("After function call")
    return wrapper

@decorator
def greet():
    print("Hello, World!")

greet()
# Output:
# Before function call
# Hello, World!
# After function call

6. Exercises (Solutions Included)

Exercise 1 — List Comprehension

# Task: Create a list of squares of even numbers from 1 to 10
Solution
squares_even = [x**2 for x in range(1, 11) if x % 2 == 0]
print(squares_even)  # [4, 16, 36, 64, 100]

Exercise 2 — Generator

# Task: Write a generator that yields first n Fibonacci numbers
Solution
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

for num in fibonacci(7):
    print(num)
# Output: 0 1 1 2 3 5 8

Exercise 3 — Lambda Function

# Task: Use lambda to filter out odd numbers from a list
nums = [1,2,3,4,5,6,7,8,9]
Solution
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)  # [2, 4, 6, 8]

Exercise 4 — Decorator

# Task: Create a decorator that prints "Starting" before and "Ending" after a function
Solution
def log_decorator(func):
    def wrapper():
        print("Starting")
        func()
        print("Ending")
    return wrapper

@log_decorator
def say_hello():
    print("Hello!")

say_hello()
# Output:
# Starting
# Hello!
# Ending
Tips:
  • Use list comprehensions for concise and readable list creation.
  • Generators save memory by producing items on demand.
  • Lambda functions are best for small, single-use functions.
  • Decorators help modify or enhance function behavior without changing its code.
  • Always test iterators and generators carefully to avoid StopIteration errors.