These notes provide a structured and technically detailed overview of the concepts covered in the CS50 Python course.
They are written to serve as a clear reference for anyone revisiting foundational Python topics, offering concise explanations, definitions, examples, and key insights.
The goal is to create a comprehensive resource that supports learning, revision, and practical understanding of Python programming principles, regardless of prior experience level.
print()is a predefined (built-in) function in Python.- It displays output on the screen.
- Example:
print("Hello, world!")Arguments are the values we pass into a function.
- In print("Hello"), the argument is "Hello".
- Arguments are written inside the parentheses of a function.
- They can be:
- Strings
- Numbers
- Variables
- Expressions
No matter how experienced a programmer is, mistakes will happen. These mistakes are called bugs.
- Bugs can occur due to:
- Wrong arguments
- Wrong syntax
- Logical mistakes
- A side effect is something a function does outside of returning a value.
Example:
-
Printing to the screen
-
Writing to a file
-
Sending data to a server
-
The print() function is a perfect example of a function whose purpose is a side effect: showing text on the screen.
- The input() function is used to take input from the user.
- The value typed by the user is always received as a string.
- We can pass a prompt as an argument inside input().
Example:
name = input("What is your name? ")
print("Hello, " + name)- print() shows output → side effect.
- input() collects user input → argument is the question prompt.
- Bugs are normal in coding; they help us learn.
- Python functions are powerful and rely heavily on arguments.
A variable is a named location in memory that stores a value.
In Python, a variable is created automatically when you assign a value to it.
Example:
name = input("What is your name? ")Here:
- name → variable
- = → assignment operator
- input("What is your name? ") → value stored inside the variable
- They hold data so it can be used later.
- The value of a variable can change (hence the name “variable”).
- Variables should have meaningful names for readability.
Since input() always returns a string, combining it with another string is called concatenation. Example:
print("Hello, " + name)Python joins the two strings into: Hello, <user_input>
If you try to add a string and an integer without converting, Python will show a TypeError.
Example (❌ wrong):
print("You are " + age)Example (✔ correct):
print("You are " + str(age))- Comments help document your code for yourself and others.
- Python ignores comments during execution.
# This is a comment- Improve readability
- Help explain logic
- Useful for debugging
- Good practice in professional codebases
- We can assign multiple variables and print them together.
name = input("Name: ")
city = input("City: ")
print("Hello,", name)
print("You live in", city)- This introduces the idea of writing programs with multiple inputs and multiple operations.
*Important
- When using concatenation ("Hello, " + name), we must add spaces ourselves because the entire expression becomes one single argument.
- But when we separate items with commas in print():
print("Hello,", name)- Python automatically inserts a space between them because each item is treated as a separate argument to the print() function.
- This makes comma-separated printing cleaner and safer than manual string concatenation.
Pseudocode is a plain-language outline of what a program should do before writing the actual code.
It is not real code — just a step-by-step description of the logic.
Useful for:
- Understanding the flow of a program
- Planning before coding
- Reducing bugs by thinking through logic first
Example pseudocode for asking a name and printing a greeting:
- ask user for their name
- store the name
- print a greeting using the name
Pseudocode helps convert ideas → logic → actual Python code.Another way to combine text and variables is by using formatted string literals, also called f-strings.
Example:
name = input("What is your name? ")
print(f"Hello {name}")- The f before the string allows us to directly insert variables inside { }.
- This is cleaner and easier to read than concatenation or comma-separated printing.
f-strings can also format numbers in different ways. 1. Adding commas to large numbers If you have a large integer and want to display it with commas:
num = 1000000
print(f"{num:,}")Output:
1,000,0002. Formatting floating-point numbers You can round a float to a specific number of decimal places using:
value = 3.14159
print(f"{value:.2f}")Explanation:
- .2 → round to 2 decimal places
- f → format as a floating point number
Output:
3.14These formatting options make f-strings very powerful for displaying clean, readable output.
Python provides several built-in string functions to clean and format user input.
Removes any leading and trailing whitespace.
name = name.strip()Capitalizes only the first letter of the string.
name = name.capitalize()Capitalizes the first letter of every word.
name = name.title()String methods can be combined for cleaner and more efficient code.
name = name.strip().title()This removes extra spaces and applies proper capitalization in one line.
.split() breaks a string into multiple parts based on a delimiter (default is space).
first, last = name.split(" ")This extracts the first and last name separately.
int is a data type in Python used to represent whole numbers (positive, negative, or zero).
Example:
x = 5
y = -2
z = 0In Python, int supports several arithmetic operations:
+(addition)-(subtraction)*(multiplication)/(division — always returns float)%(modulus — remainder)
Example:
x = 10
y = 3
print(x + y, x - y, x * y, x / y, x % y)- Python provides an interactive mode (also called the Python REPL(Read, Evaluate , Print , Loop)) where code is interpreted and executed immediately.
- You can open it by running:
python- Executes code line by line.
- Useful for quick tests and trying small expressions.
- Helps in maintaining continuity when experimenting with values or operations.
- Example inside interactive mode:
>>> 2 + 3
5
>>> "hello".upper()
'HELLO'Interactive mode is often used for quick debugging or checking how Python behaves with certain operations.
- We create a simple calculator program.
- This project helps reinforce how each operator works (
+,-,*,/,%) and how user input interacts with arithmetic in Python.
You can find the calculator file here:
➡️ IntroductionToProgramming/Calculator.py
- Take input from the user
- Convert the input to integers
- Perform arithmetic operations
- Display results using
print()
It serves as a practical way to understand operators and how Python handles number calculations.
A float is a data type used to store numbers with decimal points.
Example:
x = 3.14
y = -2.5
z = 10.0Floats allow Python to represent fractional values, unlike integers which store only whole numbers.
Python provides the built-in round() function to round a floating-point number.
Official Python documentation syntax:
round(number[, ndigits])- The square brackets [] mean optional argument.
- number → the value to round
- ndigits → (optional) how many decimal places to round to
Examples: Without ndigits (rounds to nearest whole number):
round(3.6) # 4
round(3.2) # 3With ndigits (rounds to specific decimal place):
round(3.14159, 2) # 3.14
round(7.856, 1) # 7.9ndigits lets us control the precision — for example:
- 1 → tenths
- 2 → hundredths
- 0 → tens (python returns float e.g., round(67, -1) rounds to tens place)
In Python, we can create our own functions using the keyword def, which stands for define.
Functions allow us to group code together so it can be reused whenever needed.
Basic structure:
def function_name():
# code block- def → used to define a new function
- function_name → the name we choose for our function
- () → parentheses used when defining and calling functions
- : → indicates the start of the function block
- The code inside the function must be indented
Example:
def hello():
print("Hello!")To run the function, we call it:
hello()Functions help avoid repetition and make the program cleaner, readable, and easier to maintain.
If we define a user function at the bottom of the file and try to call it before it's defined, Python will give an error saying the function does not exist.
This happens because Python reads code from top to bottom, so it must encounter the function definition before it is called.
To solve this, we follow a common structure:
- Put the main logic inside a
main()function - Define other functions above or below — order doesn't matter
- Call
main()at the end of the file
This ensures Python always knows what to execute first.
Example:
def main():
name = input("What is your name? ")
hello(name)
def hello(name):
print(f"Hello, {name}")
main()- main() contains the program flow
- hello() can be written anywhere in the file
- Calling main() at the bottom starts the program
The scope of a variable refers to where the variable is accessible in the program.
In Python:
- A variable created inside a function is called a local variable
- It can only be used inside that function
- Outside the function, the variable does not exist
Example:
def hello():
name = "Aryan" # local variable
print(name)
hello()
print(name) # ❌ Error: name is not definedVariables defined outside functions are global, but using too many globals is not recommended. Always prefer local variables for cleaner, safer code.
The return statement is used inside a function to send a value back to the place where the function was called.
- print() → shows something on the screen
- return → gives back a value so it can be stored or used later
def square(x):
return x * x
result = square(5)
print(result) # 25After return executes, the function stops immediately
- A function can return:
- numbers
- strings
- lists
- booleans
- or even nothing (returns None)
def test():
return 10
print("This will never run") # unreachable code- Using return makes functions more reusable because they can give back values instead of just printing them.
Conditionals help us make decisions in a program.
They allow code to execute only when certain conditions are true.
Python provides the following operators to compare values:
<(less than)>(greater than)<=(less than or equal to)>=(greater than or equal to)==(equal to)!=(not equal to)
These operators are used inside conditional statements.
if→ runs only when the condition is trueelif→ runs when the previous conditions were false, but this one is trueelse→ runs when none of the conditions are true
Basic structure:
if condition:
# code
elif another_condition:
# code
else:
# codeLogical operators allow us to combine multiple conditions.
and All conditions must be true for the whole expression to be true.
or Only one condition needs to be true for the expression to be true.
Python also provides a match statement (introduced in Python 3.10) which works similar to a switch-case in other languages.
It is used to compare a value against several patterns and execute the matching block.
Basic structure:
match variable:
case value1:
# code
case value2:
# code
case _:
# default case- match → checks the variable
- case → pattern/value to compare
- _ → wildcard (acts like “else”), matches anything
Loops allow us to repeat a block of code multiple times without writing it again and again.
Python mainly provides two types of loops: while loop and for loop.
A while loop continues running as long as the condition remains true.
Basic structure:
while condition:
# repeated codeImportant:
- Must update the variable inside the loop
- Otherwise it becomes an infinite loop
A for loop is used to iterate over a sequence, such as:
- a range of numbers
- a string
- a list
Basic Structure:
for item in sequence:
# repeated codeThe range() function is commonly used with loops. Example:
for i in range(3):
print("Hello")Output:
Hello
Hello
HelloExample:
for char in "Aryan":
print(char)- break and continue (Optional basics)
- break → exits the loop immediately
- continue → skips the current iteration and moves to the next
Example:
for i in range(5):
if i == 3:
break
print(i)Loops allow us to perform repeated tasks efficiently and reduce code repetition.
A list in Python is a data structure used to store multiple values in a single variable.
Lists are ordered, changeable (mutable), and can hold different data types together.
Example:
numbers = [1, 2, 3, 4]
names = ["Aryan", "Rahul", "Kumar"]
mixed = [1, "Hello", 3.14]Key Properties of Lists
- Ordered → items have fixed positions (indexing starts at 0)
- Mutable → you can change, add, or remove items
- Allow duplicates → same value can appear more than once
- Can store different data types → int, str, float, etc.
Assessing elements:
names = ["Aryan", "Rahul", "Kumar"]
print(names[0]) # Aryan
print(names[2]) # Kumarprint(names[-1]) # last element → Kumarnumbers = [10, 20, 30]
numbers[1] = 25
print(numbers) # [10, 25, 30]- .append(x) → adds item to end
- .insert(i, x) → adds item at position
- .remove(x) → removes first occurrence
- .pop() → removes last item
- .sort() → sorts list
- .reverse() → reverses list
for item in fruits:
print(item)A dictionary is a data structure in Python that allows us to associate one value with another.
It stores data in key–value pairs, where each key maps to a specific value.
Example:
student = {"name": "Aryan", "age": 20, "grade": "A"}- "name" → key
- "Aryan" → value
- Keys must be unique
- Values can be of any data type
- Key–Value mapping
- Unordered (Python 3.7+ keeps insertion order)
- Mutable → values can be changed
- Keys must be immutable types (strings, numbers, tuples)
1. Accessing Values Use the key:
print(student["name"]) # Aryan
print(student["age"]) # 202. Modifying a Dictionary
student["age"] = 21Adding a new key–value pair: student["city"] = "Delhi"
- .get(key) → safely get value
- .keys() → list of keys
- .values() → list of values
- .items() → list of key-value pairs
Example:
for key, value in student.items():
print(key, value)An exception is an event that occurs while the program is running and disrupts the normal flow of execution.
Examples include:
- Dividing by zero
- Converting invalid input
- Accessing out-of-range list index
Python stops the program when an exception occurs unless we handle it.
Happens when the programmer writes something invalid according to Python's rules.
Examples:
- Missing colon
- Misspelled keyword
- Incorrect indentation
These must be fixed by the programmer before the code can run.
Example:
if x == 3
print("Hello")
# ❌ SyntaxError (missing :)Occurs while the program is running, even if the syntax is correct.
x = int("abc") # ❌ ValueErrorWe cannot always predict runtime errors, so to prevent the program from crashing, we use exception handling.
We add extra code to catch errors and prevent the program from stopping.
Example:
try:
x = int(input("Enter a number: "))
print(x)
except:
print("Invalid input!")- Code inside try runs normally
- If an error occurs → except block runs
A NameError after a try/except usually means the variable was never assigned because an exception occurred before the assignment finished. This is about existence of the variable, not scope.
Fixes:
- Initialize the variable before
try(e.g.,x = None) and check it after. - Use
try/except/elseand reference the variable only inelse. - Use a loop to repeatedly ask for input until valid (recommended for user input).
The pass keyword is used when a statement is required syntactically,
but you don’t want to write any code there yet.
It acts as a placeholder so the program doesn’t throw an error.
Example:
def todo():
pass # code will be written laterpass does nothing — it simply allows the program to run without errors.
The raise keyword is used to manually trigger an exception in Python.
You use raise when:
- You want to signal that something went wrong
- You want to enforce rules
- You want to stop execution at a certain condition
Example:
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative")Here:
- We purposely “raise” a ValueError
- The message helps identify the error
raise stops the program immediately unless handled using try/except.
In Python, a module is a file that contains reusable code — such as functions, variables, or classes.
A library is a collection of modules that provide additional features beyond the basic language.
Modules help us:
- Organize code
- Reuse code across programs
- Access pre-written functionality
- Keep programs clean and modular
We use the import keyword to bring a module into our program.
Example:
import mathNow we can use anything inside the math module:
print(math.sqrt(25))We can rename a module or function using as:
import math as m
print(m.sqrt(25))or
from math import sqrt as s
print(s(25))Aliases help shorten long module names.
import random
num = random.randint(1, 10)
print(num)- random.randint(a, b) returns a random integer between a and b.
import random
coin = random.choice(["heads" , "tails"]) # choice is the function and it only takes seq i.e. lists
print(coin)Instead of importing the whole module, we can import only what we need:
from math import sqrt
print(sqrt(25))Any .py file can act as your own module.
Example:
- helpers.py
def greet(name):
print(f"Hello, {name}")- main.py
import helpers
helpers.greet("Aryan")Python comes with a built-in library called statistics, which allows us to easily perform common statistical calculations like:
- mean (average)
- median (middle value)
- mode (most frequent value)
These functions save time because we don’t need to write the logic ourselves.
import statisticsnumbers = [10, 20, 30]
print(statistics.mean(numbers)) # Output: 20numbers = [10, 20, 30, 40]
print(statistics.median(numbers)) # Output: 25numbers = [1, 2, 2, 3]
print(statistics.mode(numbers)) # Output: 2from statistics import mean, median, mode
print(mean([1, 2, 3]))Python programs can also receive input through the command line, not just through input().
For this, we use the built-in sys module.
Command-line arguments allow us to pass values to a Python script when running it, like:
python hello.py Aryan
Here, `"Aryan"` is a command-line argument.
---
### Importing sys
```python
import sysAll command-line arguments are stored in a list called:
sys.argv- sys.argv[0] → the name of the script
- sys.argv[1] → first argument
- sys.argv[2] → second argument and so on..........
argv stands for argument vector
Example:
import sys
print(sys.argv)Running:
python greet.py Aryan Kumar
Output:
['greet.py', 'Aryan', 'Kumar']import sys
if len(sys.argv) < 2:
print("Missing argument")
else:
print(f"Hello, {sys.argv[1]}")Explanation:
- len(sys.argv) tells how many arguments were passed
- We avoid errors by checking the length first
If we try to access an argument that isn’t given:
sys.argv[1]Python will raise
IndexError
So, we are checking before using it to avoid crashes
if len(sys.argv) < 2:
print("Please provide a name")
sys.exit()sys.exit() ends the program immediately.
Command-line arguments are useful for making scripts that can be automated, scheduled, or run with different inputs without changing the code.
Slicing allows us to extract a portion (a slice) of a sequence such as:
- strings
- lists
- tuples
The general slice syntax is:
sequence[start : end]
- `start` → index where the slice begins (inclusive)
- `end` → index where the slice stops (exclusive)Example with a string:
name = "Aryan"
print(name[0:3]) # AryIf start is omitted, it begins from the start of the sequence:
name[:2] # ArIf end is omitted, it slices till the end:
name[2:] # yanNegative indexes count from the end:
name = "Aryan"
print(name[-3:]) # yanWe can also specify a step:
sequence[start : end : step]Example:
text = "abcdef"
print(text[0:6:2]) # aceExample:
numbers = [10, 20, 30, 40, 50]
print(numbers[1:4]) # [20, 30, 40]Slicing is powerful for extracting specific parts of sequences in an efficient and readable way.
A package is a collection of third-party modules that are not included in Python by default.
These packages provide additional functionality that we can install and use in our programs.
Examples:
pandas→ data analysisnumpy→ numerical computingrequests→ HTTP requestsflask→ web development
We use pip, Python’s package manager, to install packages.
Example:
pip install requestsThis downloads and installs the package so it can be used in your Python code.
Once installed, import them like any other module:
import requests
response = requests.get("https://example.com")
print(response.status_code)pip listpip install --upgrade package_nameUnit testing is a method of testing individual parts (units) of your code — usually functions — to ensure they work correctly.
Python provides a built-in module called unittest to write and run these tests.
Unit tests help in:
- Catching bugs early
- Ensuring functions behave as expected
- Preventing future changes from breaking existing code
- Making code more reliable and maintainable
A unit test file usually:
- Imports
unittest - Imports the function(s) you want to test
- Contains test classes with test methods
- assertEqual(a, b) → checks if a == b
- assertNotEqual(a, b)
- assertTrue(x)
- assertFalse(x)
- assertRaises(error_type)
Example:
self.assertRaises(ValueError, square, "abc")Unit testing is an important habit in professional development and ensures your code remains correct as it grows.
File I/O refers to reading from and writing to files using Python.
This helps us store data permanently instead of only keeping it in memory.
Python provides the built-in open() function to work with files.
Basic syntax:
file = open("filename.txt", "mode")Common modes:
- "r" → read (default)
- "w" → write (overwrites existing file)
- "a" → append (adds to the file)
- "r+" → read and write
with open("data.txt", "r") as file:
content = file.read()
print(content)- read() → reads entire file as a single string
- Using with automatically closes the file
with open("data.txt", "r") as file:
for line in file:
print(line.strip())with open("data.txt", "w") as file:
file.write("Hello, world!")- "w" creates the file if it doesn't exist
- "w" overwrites existing content
with open("data.txt", "a") as file:
file.write("\nNew line added.")lines = ["Apple\n", "Banana\n", "Cherry\n"]
with open("fruits.txt", "w") as file:
file.writelines(lines)Safest Way: Using with
- Using with open(...) is recommended because:
- File closes automatically
- Prevents memory leaks
- Cleaner and more readable
File I/O is essential for saving data, reading configuration files, logs, reports, and many real-world applications.
CSV (Comma-Separated Values) files are widely used for storing tabular data.
Python provides the built-in csv module to read and write CSV files easily.
import csvThe reader class reads CSV files row-by-row as lists.
Example:
import csv
with open("students.csv") as file:
reader = csv.reader(file)
for row in reader:
print(row)If students.csv contains:
Aryan,20,CS
Rahul,21,ITOutput:
['Aryan', '20', 'CS']
['Rahul', '21', 'IT']- Each row is returned as a list
- Columns remain positional (indexed)
DictReader reads each row as a dictionary where keys come from the header row.
Example:
import csv
with open("students.csv") as file:
reader = csv.DictReader(file)
for row in reader:
print(row)If the file contains:
name,age,branch
Aryan,20,CS
Rahul,21,ITOutput:
{'name': 'Aryan', 'age': '20', 'branch': 'CS'}
{'name': 'Rahul', 'age': '21', 'branch': 'IT'}Advantages: Easier to access using column names:
print(row["name"])- Makes code more readable than positional indexing
DictWriter writes data as dictionaries, automatically using keys as headers. Example:
import csv
with open("output.csv", "w", newline="") as file:
writer = csv.DictWriter(file, fieldnames=["name", "age", "branch"])
writer.writeheader() # writes: name,age,branch
writer.writerow({"name": "Aryan", "age": 20, "branch": "CS"})
writer.writerow({"name": "Rahul", "age": 21, "branch": "IT"})This will produce:
name,age,branch
Aryan,20,CS
Rahul,21,ITImportant:
- fieldnames must match the keys in each dict
- writeheader() writes the first row (column names)
| Class | Use Case |
|---|---|
reader |
When the CSV has no header or structure is simple |
DictReader |
When file has column names and you want readable code |
DictWriter |
When writing data using dictionaries for clarity |
Handling CSVs is extremely common in data processing, automation scripts, reporting, and backend development.
PIL stands for Python Imaging Library.
Its modern version is called Pillow — a powerful library used to perform operations on image files.
To install:
pip install pillowfrom PIL import ImagePillow allows you to:
- Open images
- Resize images
- Crop image
- Convert image formats (PNG → JPG, etc.)
- Rotate and flip images
- Create thumbnails
- Save modified images
from PIL import Image
img = Image.open("photo.jpg")
img.show()img = Image.open("photo.jpg")
resized = img.resize((300, 300))
resized.save("photo_resized.jpg")img = Image.open("photo.png")
img.save("photo.jpg")img = Image.open("photo.jpg")
cropped = img.crop((0, 0, 200, 200))
cropped.save("photo_cropped.jpg")img = Image.open("photo.jpg")
img.thumbnail((150, 150))
img.save("photo_thumbnail.jpg")Pillow is widely used in:
- Web development
- Automation scripts
- Data preprocessing
- Machine learning (image-based models)
- It provides simple functions to manipulate and process image files efficiently.
Regular expressions (also called regexes) are used to find, match, or validate patterns in text.
They are commonly used to:
- Validate user input (email, phone number, password)
- Search for specific patterns in text
- Extract required information from strings
Python provides a built-in library called re to work with regular expressions.
import reBasic Example: Email Validation Suppose we want the user to enter an email address and check whether it is valid.
import re
email = input("Enter your email: ")
if re.search(r"^[\w\.-]+@[\w\.-]+\.\w+$", email):
print("Valid email")
else:
print("Invalid email")Explanation:
- ^ → start of string
- @ → must contain @
- . → dot (.)
- $ → end of string
Common re Functions
- re.search() → searches for a pattern anywhere in the string
- re.match() → checks pattern only at the beginning
- re.findall() → returns all matches as a list
- re.sub() → replaces matches with another string
Example:
text = "My phone number is 9876543210"
numbers = re.findall(r"\d+", text)
print(numbers)Regular expressions use special symbols called quantifiers to specify how many times a character or pattern should repeat.
.→ Matches any single character except a newline*→ Matches 0 or more repetitions of the preceding pattern+→ Matches 1 or more repetitions of the preceding pattern?→ Matches 0 or 1 repetition of the preceding pattern{m}→ Matches exactlymrepetitions of the preceding pattern
Why Use Regular Expressions?
- Flexible pattern matching
- Saves time compared to manual checks
- Widely used in validation and parsing tasks
Object-Oriented Programming (OOP) is a programming paradigm that organizes code using objects and classes instead of only functions.
OOP helps in:
- Structuring large programs
- Reusing code
- Improving readability and maintainability
- Modeling real-world entities
A class is a blueprint for creating objects.
It defines the properties (attributes) and behaviors (methods) of an object.
Syntax:
class ClassName:
pass- An object is an instance of a class.
- Objects are created from classes.
Example:
student = Student()The init method is a special method that runs automatically when an object is created.
It is used to initialize object attributes.
Example:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age- self refers to the current object
- Used to access variables and methods belonging to the class
Methods defined inside a class that operate on object data.
Example:
class Student:
def greet(self):
print(f"Hello, my name is {self.name}")In Object-Oriented Programming, properties allow us to access and modify class attributes in a controlled way.
Instead of accessing variables directly, we use:
- getter → to read a value
- setter → to update a value with validation
Python provides the @property decorator for this purpose.
- To protect data
- To add validation logic
- To keep attribute access simple and readable
- To avoid breaking code when internal implementation changes
A getter allows us to access a private attribute like a normal variable.
class Student:
def __init__(self, name):
self._name = name # protected attribute
@property
def name(self):
return self._nameUsage:
s = Student("Nutika")
print(s.name)Here, name behaves like an attribute, but internally it is a method.
A setter allows us to control how values are assigned.
- _variable → convention for protected attributes
- @property → defines a getter
- @property_name.setter → defines a setter
- Access looks like a normal variable, not a function call
- Without @property → explicit get_name() / set_name() methods
- With @property → clean, pythonic attribute access
A global variable is a variable that is defined outside all functions and is accessible throughout the program.
- A local variable is defined inside a function
→ It can be used only inside that function - Trying to access a local variable outside its function will raise an error
Example:
def func():
x = 10 # local variable
print(x) # ❌ NameErrorA variable defined outside functions is global:
x = 10
def func():
print(x) # ✅ allowed (reading global variable)
func()Reading a global variable inside a function works without global.
If you want to modify a global variable inside a function, Python treats it as local by default. To modify it, you must use the global keyword.
❌ This will raise an error:
x = 10
def func():
x = x + 1 # ❌ UnboundLocalError
func()✅ Correct way:
x = 10
def func():
global x
x = x + 1
func()
print(x) # 11- Local variables cannot be accessed outside their function
- Global variables can be read inside functions without global
- To modify a global variable inside a function, global is required
- Excessive use of global variables is discouraged
In many programming languages, constants are defined using a special keyword like const.
However, Python does not have a built-in keyword to define constants.
In Python, constants are created by naming convention, not enforcement.
- Variables written in ALL CAPITAL LETTERS are treated as constants by convention
- This indicates that the value should not be modified
Example:
PI = 3.14159
MAX_USERS = 100Although Python allows reassignment, developers choose not to change such values.
### Important Note Python does not prevent you from changing constants:
PI = 3.14
PI = 3.14159265 # Allowed, but discouraged
There will be no error, because Python is a dynamically typed language.Python is a dynamically typed language, which means variable types are checked at runtime, not before execution.
However, Python also supports type hints, which allow us to specify the expected data types.
Type hints help:
- Improve code readability
- Catch type-related mistakes early
- Make code easier to understand and maintain
Type hints indicate what type of data a variable, function parameter, or return value is expected to have.
Example:
def add(a: int, b: int) -> int:
return a + bHere:
- a: int → a should be an integer
- b: int → b should be an integer
- -> int → function should return an integer
age: int = 20
name: str = "Nutika"These are hints for developers and tools, not strict rules.
mypy is a static type checker for Python. It checks whether your code follows the type hints you provided.
It helps verify:
- Function arguments
- Return types
- Variable assignments
Copy code
pip install mypyRun this command in the terminal:
mypy your_file.pyIf the code violates type hints, mypy reports errors before runtime.
Example:
def greet(name: str) -> None:
print("Hello", name)
greet(10) # mypy will flag this as an error- Type hints are optional
- They do not change how Python runs
- Tools like mypy use type hints to catch bugs early
- Widely used in professional and large-scale Python projects
A docstring is a special string used to document code in Python.
It explains what a module, class, function, or method does.
Docstrings improve:
- Code readability
- Maintainability
- Understanding for other developers
- Automatic documentation generation
Docstrings are written using triple quotes (""" """) and are placed:
- Immediately after a function definition
- Immediately after a class definition
- At the top of a file (module docstring)
def add(a: int, b: int) -> int:
"""
Adds two integers and returns the result.
"""
return a + b- Docstring → describes the purpose
- Comments → explain specific lines
- Pseudocode → would have been written before this code existed
argparse is a built-in Python library used to create user-friendly command-line interfaces.
It is an improvement over sys.argv because it:
- Parses arguments automatically
- Validates input
- Generates help messages
- Handles errors gracefully
- No manual index handling (
sys.argv[1],sys.argv[2], etc.) - Automatic
--helpsupport - Clear error messages
- Supports optional and named arguments
import argparse
parser = argparse.ArgumentParser(description="Greet the user")
parser.add_argument("name", help="User's name")
args = parser.parse_args()
print(f"Hello, {args.name}")Unpacking is a feature in Python that allows us to assign elements of a collection to multiple variables in a single statement.
It improves:
- Readability
- Cleaner assignments
- Easier handling of sequences and function returns
a, b = [1, 2]- a gets 1
- b gets 2
The number of variables must match the number of values.
x, y = (10, 20)x, y = y, xNo temporary variable is required.
Functions can return multiple values as a tuple, which can be unpacked:
def get_coordinates():
return 3, 4
x, y = get_coordinates()The * operator allows collecting multiple values into a list.
a, *b = [1, 2, 3, 4]
a → 1
b → [2, 3, 4]pairs = [(1, 2), (3, 4), (5, 6)]
for x, y in pairs:
print(x, y)- Works with lists, tuples, strings, and other iterables
- Makes code concise and expressive
- Commonly used in real-world Python code
Unpacking is a feature in Python that allows us to assign elements of a collection to multiple variables in a single statement.
It improves:
- Readability
- Cleaner assignments
- Easier handling of sequences and function returns
a, b = [1, 2]- a gets 1
- b gets 2
The number of variables must match the number of values.
x, y = (10, 20)x, y = y, xNo temporary variable is required.
Functions can return multiple values as a tuple, which can be unpacked:
def get_coordinates():
return 3, 4
x, y = get_coordinates()The * operator allows collecting multiple values into a list.
a, *b = [1, 2, 3, 4]
a → 1
b → [2, 3, 4]pairs = [(1, 2), (3, 4), (5, 6)]
for x, y in pairs:
print(x, y)- Works with lists, tuples, strings, and other iterables
- Makes code concise and expressive
- Commonly used in real-world Python code
Definition
- *args collects extra positional arguments into a tuple.
- “args” is just a name (convention)
-
- is what actually matters
Example
def f(*args):
print(args)
f(10, 20, 30)Output:
(10, 20, 30)So:
- args is a tuple
- You can loop over it, index it, etc.
Definition
- **kwargs collects extra keyword arguments into a dictionary.
- “kwargs” = keyword arguments
- ** is what matters
Example
def f(**kwargs):
print(kwargs)
f(name="Nutika", age=21)Output:
bash
{'name': 'Nutika', 'age': 21}
So:
- kwargs is a dict
- Keys are argument names
- Values are argument values
Python provides powerful tools to process collections efficiently and readably.
map() applies a function to each item in an iterable and returns a map object.
numbers = [1, 2, 3, 4]
squared = map(lambda x: x * x, numbers)
print(list(squared))Key points:
Applies the same operation to all elements
Often replaced by list comprehensions for readability
filter() selects elements from an iterable that satisfy a condition.
numbers = [1, 2, 3, 4, 5]
even = filter(lambda x: x % 2 == 0, numbers)
print(list(even))Key points:
- Keeps elements where condition is True
- Returns a filter object
List comprehension provides a compact and readable way to create lists.
squares = [x * x for x in range(5)]evens = [x for x in range(10) if x % 2 == 0]Benefits:
- Cleaner than map + filter
- More Pythonic
Dictionary comprehension creates dictionaries in a concise way.
squares = {x: x * x for x in range(5)}With condition:
even_squares = {x: x * x for x in range(10) if x % 2 == 0}enumerate() returns both the index and value while looping over an iterable.
names = ["Aryan", "Nutika", "Radha"]
for index, name in enumerate(names):
print(index, name)You can also start indexing from a custom value:
for i, name in enumerate(names, start=1):
print(i, name)When to Use What
- map() → apply a function to all elements
- filter() → select elements based on a condition
- List comprehension → preferred for clarity and simplicity
- Dictionary comprehension → concise dictionary creation
- enumerate() → access index and value together