Show/Hide Code
name = "Prosper"A Complete Guide for Beginners, Professionals, enthusiasts & Builders. Practical Notes, Tutorials and Exercises
Python has rapidly become one of the most popular programming languages in the world, with growth unmatched in computing history. It’s highly versatile, used for scripting, automating tasks, web development, data analysis, machine learning, game creation, and even programming embedded systems.
It has also become the top choice for teaching computer science fundamentals in universities worldwide. Millions of beginners start with Python, and for many, it remains their main language due to its simplicity and broad capabilities.
Python’s appeal comes from its clear, easy-to-read syntax, large and supportive community, and extensive library ecosystem that covers nearly every imaginable task. It is a high-level language that balances ease of learning with professional utility.
Technically, Python is an interpreted and dynamically typed language. This means it executes code directly without a separate compilation step, and variable types are determined at runtime. While this speeds up development, it can also lead to runtime errors that compiled languages might catch earlier.
Python supports multiple programming paradigms, procedural, object-oriented, and functional, making it adaptable for a wide range of uses.
This course is carefully crafted and structured into 41 comprehensive chapters, taking learners from absolute fundamentals to advanced, professional-level Python concepts. Whether you are writing your first line of code or refining advanced skills, this course is designed to grow with you.
Created by Guido van Rossum in 1991, Python’s popularity has surged dramatically in recent years as this Google Trends infographic shows:
This course is intentionally designed to be inclusive and accessible, making it suitable for a wide and diverse audience, including:
Absolute beginners with no prior programming experience
Working professionals looking to automate tasks, improve productivity, or switch careers
Job seekers preparing for technical roles that require Python skills
University Students and recent graduates learning programming or computer science fundamentals
Developers and software engineers who want a strong, complete understanding of Python
Data enthusiasts interested in data analysis, visualization, and machine learning foundations
Researchers and analysts using Python for experimentation and problem solving
Entrepreneurs and startups building products and prototypes
Hobbyists and self-learners exploring programming for personal projects or curiosity
No matter your background, experience level, or goal, this course provides a clear, structured path to confidently using Python in real-world scenarios.
Starts from zero and progresses to advanced concepts
Covers almost every major aspect of Python
Designed for both learning and practical application
Suitable for academic, professional, and personal use
One complete course instead of multiple fragmented resources
Certificate of completion awarded upon successfully finishing the course, adding value to your resume and professional profile
By the end of this course, learners will not only understand Python, they will be able to think in Python, apply it confidently, and use it as a powerful tool for real world problem solving.
Getting started with Python is simple. You just need to download and install the official package from python.org, available for Windows, macOS, or Linux, and you can begin right away.
If you’re completely new to programming, the upcoming lessons will help you progress from a beginner to a capable Python programmar.
Even for experienced programmers who primarily use other languages, learning Python is highly worthwhile, as its popularity and influence continue to rise.
While lower level languages like C++ or Rust are powerful tools for expert programmers, they can be intimidating for beginners and require significant time to master.
Python, by contrast, is designed for everyone, students, people doing their day jobs with Excel, scientists, and more.
Python is the language everyone interested in coding should learn first.
Go to https://www.python.org, choose the Downloads menu, choose your operating system, and a panel with a link to download the official package will appear:
Be sure to follow the installation steps that match your operating system. If you’re using macOS, you can check out a comprehensive setup guide at Installing Python 3 on macOS
There are a few different ways to run Python programs.
In particular, there’s a distinction between using interactive prompts, where you type Python code and it’s immediately executed, and saving a Python program into a file and executing that.
Let’s start with interactive prompts.
If you open your terminal and type python, you will see a screen like this:
This is the Python REPL (Read-Evaluate-Print-Loop).
Notice the >>> symbol, and the cursor after that. You can type any Python code here, and press the enter key to run it.
For example try defining a new variable using
name = "Prosper"and then print its value, using print():
print(name)Prosper
Any line of Python you write here is going to be executed immediately.
Type quit() to exit this Python REPL.
You can access the same interactive prompt using the IDLE application that’s installed by Python automatically:
This approach might be easier since using a mouse lets you navigate and copy or paste text more conveniently than working directly in the terminal.
The tools mentioned so far are included with Python by default. However, I suggest installing IPython, which is widely regarded as one of the best interactive command-line REPL environments available.
You can install it by running:
pip install ipythonMake sure the pip binaries are in your path, then run ipython:
ipython is another interface that lets you work with a Python REPL, and provides some nice features like syntax highlighting, code completion, and much more.
The second way to run a Python program is to write your Python program code into a file, for example first_python_file.py:
and then run it with python first_python_file.py:
In this case the program is executed as a whole, not one line at a time. And that’s typically how we run programs.
We use the REPL for quick prototyping and for learning.
On Linux and macOS, a Python program can also be transformed into a shell script, by prepending all its content with a special line that indicates which executable to use to run it.
We have many other ways to run Python programs.
One of them is using VS Code, and in particular the official Python extension from Microsoft:
After installing this extension you will have Python code autocompletion and error checking, automatic formatting and code linting with pylint, and some special commands, including:
Python: Start REPL to run the REPL in the integrated terminal:
Python: Run Python File in Terminal to run the current file in the terminal:
and many more. Just open the Command Palette (View → Command Palette or press Ctrl + Shift + P) and type “Python” to see all the Python-related commands.
Another way to easily run Python code is to use replit, a very nice website that provides a coding environment you can create and run your apps on, in any language, Python included:
Signup (it’s free)!
I think replit is handy because:
you can easily share code just by sharing the link
multiple people can work on the same code
it can host long-running programs
you can install packages
it provides you a key-value database for more complex applications
Jupyter Notebook
Jupyter Notebook is one of the most popular tools for learning and practicing Python, especially for data science, statistics, and research.
It allows you to mix code, text, and visualizations in one interactive document.
It runs in your web browser.
You can write Python code in small cells and run them individually.
It displays immediate output (including charts, tables, and markdwn notes).
Example of a cell inside Jupyter:
# Let's try it!
name = "Prosper"
print(f"Hello, {name}! Welcome to Jupyter Notebook.")Hello, Prosper! Welcome to Jupyter Notebook.
Installing Jupyter Notebook (via Anaconda)
The easiest way to install Jupyter Notebook is through Anaconda, a free Python distribution that includes:
Python itself
Jupyter Notebook
Common data science libraries (NumPy, pandas, Matplotlib, etc.)
Step-by-Step Installation Guide
Go to the official Anaconda website: 👉 https://www.anaconda.com/download
Download the version for your operating system (Windows, macOS, or Linux).
Run the installer and follow the default instructions, it will install both Anaconda and Python.
Once installed, you can verify it by opening your terminal (or Anaconda Prompt on Windows) and typing:
conda --version
If you see a version number, Anaconda was installed successfully!
Launching Jupyter Notebook
You can launch Jupyter Notebook in two ways:
Option 1: From Anaconda Navigator
Open Anaconda Navigator from your Start Menu or Applications folder.
Click on the “Launch” button under Jupyter Notebook.
It will open in your default web browser.
Option 2: From Terminal or Command Prompt
Open your terminal (or Anaconda Prompt).
Navigate to the folder where you want to work. Example:
cd folder1then run:
jupyter notebookYour web browser will open automatically, showing a dashboard. From there, click New → Python 3 (ipykernel) to create a new notebook.
Try it Yourself
Create a new notebook called first_notebook.ipynb.
In the first cell, write and run:
print("Hello from my first Jupyter Notebook!")Hello from my first Jupyter Notebook!
for i in range(1, 6):
print("Number:", i)Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
You’ve just executed your first Python notebook!
A key topic we should cover right away is the Python 2 vs. Python 3 debate.
Python 3 was released in 2008 and has been the main focus of ongoing development ever since, while Python 2 continued to receive bug fixes and security updates until early 2020.
At that point, official support for Python 2 ended.
Many existing programs are still based on Python 2, and some organizations continue maintaining them, as migrating to Python 3 can be complex and time consuming, and large scale upgrades often risk introducing new bugs.
However, any new code should be written in Python 3, unless specific organizational policies require the use of Python 2.
Our focus is on Python 3.
In Python, variables are like containers that hold data, numbers, text, lists, or anything else your program needs to remember and use later.
What Is a Variable?
A variable is a name that refers to a value stored in memory.
You create a variable using the assignment operator =.
Let’s see it in action:
# Assign a string (text) to a variable
name = "Prosper"
# Assign a number to a variable
age = 7
# Display the variables
print(name)
print(age)Prosper
7
Here:
name and age are variable names (also called identifiers).
"Prosper" and 7 are values stored in those variables.
The = operator assigns the value on the right to the variable on the left.
Variables Can Change
Unlike constants, variables can be updated:
# Initial assignment
city = "Kigali"
print(city)
# Reassign a new value
city = "Musanze"
print(city)Kigali
Musanze
The variable city first stored "Kigali", then it was reassigned to "Musanze".
This is why we call them variables , their value can vary.
Rules for Naming Variables
A variable name can include:
Letters (A–Z, a–z)
Numbers (0–9)
Underscores _
But there are rules you must follow:
Valid variable names:
name1 = "Alice"
AGE = 25
aGE = 26
a11111 = 11111
my_name = "Prosper"
_name = "Hidden variable"Invalid variable names:
123 = "Invalid" # Cannot start with a number
test! = "Invalid" # No special characters like !, %, $, etc.
name% = "Invalid" # % not allowedYou cannot name a variable using Python’s reserved words (keywords).
These are special words that have meaning in Python, such as:
for, if, else, while, import, True, False, None, etc.
Python will stop you if you try to use them:
if = 5Use lowercase_with_underscores for variable names:
user_name, total_price, student_age
Avoid short or confusing names like x, y, z unless in math examples.
Use descriptive names that explain what they store.
Python is case-sensitive, so Name and name are different variables.
Example:
userName = "Alice" # Works, but not recommended
user_name = "Alice" # ✅ Recommended (PEP 8 style)In Python, your code is made up of expressions and statements, the building blocks of every program.
What’s an Expression?
An expression is any piece of code that produces a value.
When you type an expression in Python, it’s evaluated and returns a result.
Examples:
1 + 1
"Prosper"
3 * 4
len("Football")8
Each of these expressions produces a value. You can also use expressions inside functions, assignments, or other statements.
age = 5 + 2 # 5 + 2 is an expression
print(age) # statement that prints 77
What’s a Statement?
A statement is a complete instruction that Python can execute.
It might perform an action, such as assigning a value or printing output.
Examples:
name = "Prosper" # Assignment statement
print(name) # Function call statementProsper
Each line above is a statement, something Python runs to “do” something.
Combining Statements
A Python program is simply a series of statements that execute one after another.
Example:
name = "Prosper"
age = 7
print(name)
print(age)Prosper
7
You can even write multiple statements on the same line using a semicolon ;, though it’s not recommended (for readability):
name = "Prosper"; print(name)Prosper
Unlike many languages, indentation (spaces or tabs at the start of a line) is part of Python’s syntax.
Python uses indentation to define code blocks (sections of code that belong together).
Why Indentation Matters
In languages like C or JavaScript, code blocks are marked with {} brackets.
In Python, indentation replaces those braces.
Example:
name = "Prosper"
print(name)If you run this, sometimes Python gives an error IndentationError: unexpected indent
That’s because the second line is indented without reason, Python thinks you’re trying to start a block.
Correct Indentation Example
name = "Prosper"
print(name)Prosper
Now the indentation is consistent, both lines start at the same level.
Indentation Defines Code Blocks
When you write control structures like if, for, or while,
you must indent the code inside those blocks.
Example:
name = "Prosper"
if name == "Prosper":
print("Hello, Prosper") # indented = inside the if block
print("Welcome back!") # still inside the block
print("Program finished") # not indented = outside the blockHello, Prosper
Welcome back!
Program finished
Everything in Python is an object, and every object has a type. Data types tell Python what kind of data a variable holds and what operations can be performed on it.
Python automatically assigns a data type to a variable when you create it.
You don’t need to declare the type explicitly — Python figures it out for you.
Let’s see this in action
# Assign a string to a variable
name = "Prosper"
# Assign a number to a variable
age = 7
# Display their types
print(type(name))
print(type(age))<class 'str'>
<class 'int'>
Here:
"Prosper" is a string (text).
7 is an integer (whole number).
Python provides two useful built-in functions for checking types:
type()The type() function returns the class (type) of an object.
name = "Prosper"
print(type(name)) # True<class 'str'>
Using isinstance()
isinstance() checks if a variable is an instance of a specific type.
name = "Prosper"
print(isinstance(name, str)) # True
print(isinstance(name, int)) # FalseTrue
False
Python has three main numeric types:
| Type | Description | Example |
|---|---|---|
int |
Whole numbers | 10, -5, 7, 0 |
float |
Decimal (fractional) numbers | 3.14, 0.5, -10.2 |
complex |
Complex numbers | 2 + 3j, 5j |
Examples
# Integer
age = 7
print(type(age)) # <class 'int'>
# Float
fraction = 0.1
print(type(fraction)) # <class 'float'>
# Complex number
z = 2 + 3j
print(type(z)) # <class 'complex'><class 'int'>
<class 'float'>
<class 'complex'>
You can convert (or “cast”) between types using the class constructor functions like int(), float(), and str().
Example 1: Converting String to Integer
age = int("7")
print(age)
print(type(age))7
<class 'int'>
Example 2: Converting Float to Integer
fraction = 0.1
int_fraction = int(fraction)
print(int_fraction)0
When you convert a float to an int, the decimal part is truncated, not rounded.
Not every string can be converted to a number:
age = int("test")Output: ValueError: invalid literal for int() with base 10: 'test'
That’s because "test" isn’t a number.
You can create values explicitly using their class constructors:
name = str("Prosper")
another_name = str(name)
age = int("7")
price = float("9.99")Python automatically detects types when you assign literals, but constructors are useful for conversions and clarity.
Here’s a quick overview of the most common built-in types in Python:
| Type | Description | Example |
|---|---|---|
int |
Integer (whole number) | x = 42 |
float |
Decimal number | y = 3.14 |
complex |
Complex number | z = 2 + 3j |
str |
String (text) | name = "Roger" |
bool |
Boolean (True/False) | is_active = True |
list |
Ordered, mutable collection | colors = ["red", "green"] |
tuple |
Ordered, immutable collection | point = (10, 20) |
dict |
Key–value mapping | person = {"name": "Roger", "age": 8} |
set |
Unordered, unique items | numbers = {1, 2, 3} |
range |
Sequence of numbers | range(0, 10) |
NoneType |
Represents “no value” | value = None |
Operators are symbols or keywords used to perform operations on values and variables.
They allow you to combine, compare, and manipulate data.
Python supports a wide range of operators, grouped by the kind of operation they perform.
| Category | Example Operators | Description |
|---|---|---|
| Assignment | =, +=, -= |
Assign values to variables |
| Arithmetic | +, -, *, /, %, **, // |
Perform mathematical operations |
| Comparison | ==, !=, >, <, >=, <= |
Compare values |
| Logical | and, or, not |
Combine boolean expressions |
| Bitwise | &, |, ^, ~, <<, >> |
Work with binary (bit-level) data |
| Identity | is, is not |
Check if two objects are the same |
| Membership | in, not in |
Check if a value exists in a collection |
The assignment operator (=) is used to assign a value to a variable:
age = 7You can also assign one variable’s value to another:
age = 7
another_variable = ageSince Python 3.8, the walrus operator := allows assignment inside an expression:
if (n := 5) > 3:
print("n is greater than 3")n is greater than 3
Python includes several standard arithmetic operators:
| Operator | Name | Example | Result |
|---|---|---|---|
+ |
Addition | 2 + 3 |
5 |
- |
Subtraction | 5 - 2 |
3 |
* |
Multiplication | 4 * 3 |
12 |
/ |
Division | 8 / 2 |
4.0 |
% |
Modulus (remainder) | 10 % 3 |
1 |
** |
Exponentiation | 2 ** 3 |
8 |
// |
Floor Division | 7 // 2 |
3 |
Examples
print(1 + 1)
print(4 / 2)
print(4 % 3)
print(4 ** 2)
print(5 // 2)2
2.0
1
16
2
Unary Operators
You can use - as a unary minus to change the sign of a number:
print(-4) # -4-4
And + can also be used with strings for concatenation:
print("Pazzo" + " is a good dog")Pazzo is a good dog
Python lets you combine arithmetic with assignment in a single step:
| Operator | Meaning | Example | Equivalent To |
|---|---|---|---|
+= |
Add and assign | x += 2 |
x = x + 2 |
-= |
Subtract and assign | x -= 2 |
x = x - 2 |
*= |
Multiply and assign | x *= 3 |
x = x * 3 |
/= |
Divide and assign | x /= 2 |
x = x / 2 |
%= |
Modulus and assign | x %= 3 |
x = x % 3 |
Example:
age = 7
age += 1
print(age)8
Comparison operators are used to compare two values.
They always return a Boolean result (True or False).
| Operator | Meaning | Example | Result |
|---|---|---|---|
== |
Equal to | 3 == 3 |
True |
!= |
Not equal to | 3 != 2 |
True |
> |
Greater than | 4 > 2 |
True |
< |
Less than | 4 < 2 |
False |
>= |
Greater or equal | 4 >= 4 |
True |
<= |
Less or equal | 2 <= 3 |
True |
Example:
a = 1
b = 2
print(a == b)
print(a != b)
print(a > b)
print(a <= b)False
True
False
True
Logical operators are used to combine or modify Boolean expressions.
| Operator | Meaning | Example | Result |
|---|---|---|---|
not |
Logical NOT | not True |
False |
and |
Logical AND | True and False |
False |
or |
Logical OR | True or False |
True |
Example:
condition1 = True
condition2 = False
print(not condition1)
print(condition1 and condition2)
print(condition1 or condition2)False
False
True
Truthy and Falsy Behavior
Python treats some non-Boolean values as “truthy” or “falsy”.
Falsy values include:
False, 0, '', [], {}, Non e
That’s why:
print(0 or 1)
print(False or 'hey')
print('hi' or 'hey')
print([] or False)1
hey
hi
False
or returns the first truthy value, or the last one if all are falsy.
and returns the first falsy value, or the last one if all are truthy.
print(0 and 1)
print(1 and 0)
print('hi' and 'hey')0
0
hey
Bitwise operators work at the binary (bit) level.
They are mainly used in low-level programming, encryption, and performance optimization.
| Operator | Name | Description |
|---|---|---|
& |
AND | Sets each bit to 1 if both bits are 1 |
| |
OR | Sets each bit to 1 if at least one bit is 1 |
^ |
XOR | Sets each bit to 1 if only one of the bits is 1 |
~ |
NOT | Inverts all bits (0 → 1, 1 → 0) |
<< |
Shift Left | Shifts bits to the left, filling with 0s on the right |
>> |
Shift Right | Shifts bits to the right, discarding bits on the right |
a = 5 # 101 in binary
b = 3 # 011 in binary
print(a & b) # 1 (001)
print(a | b) # 7 (111)
print(a ^ b) # 6 (110)
print(~a) # -6
print(a << 1) # 10
print(a >> 1) # 21
7
6
-6
10
2
Identity Operators
Used to check if two objects are actually the same (not just equal in value).
a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a is b) # True (same object in memory)
print(a is c) # False (different objects)
print(a == c) # True (same contents)True
False
True
Membership Operators
Used to test whether a value is part of a sequence (like list, string, or tuple).
numbers = [1, 2, 3, 4, 5]
print(3 in numbers)
print(6 not in numbers)True
True
The ternary operator in Python allows you to write conditional expressions in a single line.
It’s a compact way of saying “if this condition is true, do this; otherwise, do that. ”
if and else)Let’s start with a regular if statement:
def is_adult(age):
if age >= 18:
return True
else:
return FalseThis works perfectly fine, but it takes four lines of code. Python provides a shorter way to express the same logic using a ternary operator.
The syntax of the ternary operator is:
# <value_if_true> if <condition> else <value_if_false>This can be read as:
“Return
<value_if_true>if<condition>is true, otherwise return<value_if_false>.”
def is_adult(age):
return True if age >= 18 else FalseOr even shorter:
status = "Adult" if age >= 18 else "Minor"If age is 20, status will be "Adult".
If age is 15, status will be "Minor".
Visual Flow of a Ternary Expression
Here’s how the ternary logic flows:
```{sql connection=, eval: false} ┌───────────────┐ │ Condition? │ └──────┬────────┘ │ ┌──────────┴──────────┐ │ │ True │ │ False ▼ ▼
Example:
::: {#5bdb676f .cell execution_count=56}
``` {.python .cell-code}
"Adult" if age >= 18 else "Minor"
'Minor'
:::
If the condition age >= 18 is True, the result is "Adult". Otherwise, it’s "Minor".
Advantages:
Makes your code more concise and readable for simple conditions.
Reduces the number of lines.
Perfect for inline decisions.
Avoid using it for complex conditions, as it can hurt readability.
If your condition or expressions are long, it’s better to use a normal if...else block.
Nested Ternary Operators (Use With Care)
You can nest ternary operators, but it quickly becomes hard to read:
result = "Positive" if x > 0 else "Zero" if x == 0 else "Negative"This works, but for clarity, you should use standard if...elif...else for more than one condition.
Example:
temperature = 30
message = "Hot" if temperature > 25 else "Cold"
print(message)Hot
What is a String
A string in Python is a sequence of characters enclosed in single quotes (' ') or double quotes (" ").
"Prosper"
'Arsenal''Arsenal'
You can assign a string value to a variable:
name = "Arsenal"Strings are one of the most common data types in Python — used to represent names, text, sentences, or any character-based data.
String Concatenation
You can combine (concatenate) two or more strings using the + operator:
phrase = "Arsenal" + " is a good team"
print(phrase)Arsenal is a good team
You can also append to an existing string using +=:
name = "Manchester United"
name += " is a bad team"
print(name)Manchester United is a bad team
Converting Numbers to Strings
To concatenate strings with numbers, convert the number using str():
print("Arsenal has won " + str(13) + " Premier League titles")Arsenal has won 13 Premier League titles
If you try to concatenate a number without converting it, Python will raise a TypeError.
Multi-line Strings
Strings can span multiple lines when enclosed in triple quotes (''' or """):
print("""FC Barcelona is
125
years old
""")FC Barcelona is
125
years old
Both single and double triple quotes work the same way:
print('''Mbappe is
26 years old
''')Mbappe is
26 years old
Python provides many built-in string methods that let you manipulate and analyze text easily.
| Method | Description | Example |
|---|---|---|
isalpha() |
Checks if string has only letters | "abc".isalpha() → True |
isalnum() |
Checks if string has letters and/or numbers | "abc123".isalnum() → True |
isdecimal() |
Checks if string has only digits | "123".isdecimal() → True |
lower() |
Converts to lowercase | "ROGER".lower() → "roger" |
upper() |
Converts to uppercase | "roger".upper() → "ROGER" |
title() |
Capitalizes first letters | "roger dog".title() → "Roger Dog" |
startswith() |
Checks if string starts with substring | "Roger".startswith("Ro") → True |
endswith() |
Checks if string ends with substring | "Roger".endswith("er") → True |
replace() |
Replaces part of a string | "Roger".replace("Ro", "Do") → "Doger" |
split() |
Splits string by separator | "a,b,c".split(",") → ["a","b","c"] |
strip() |
Removes whitespace from both ends | " Roger ".strip() → "Roger" |
join() |
Joins elements into one string | ",".join(["a","b"]) → "a,b" |
find() |
Finds position of substring | "Roger".find("g") → 2 |
Global String Functions
The built-in len() function returns the length of a string:
team = "Arsenal"
print(len(team))7
The in operator checks whether a substring exists within another string:
fact = "Lionel Messi is the GOAT of football"
print("Messi" in fact)True
To include special characters inside strings, use the backslash (\) escape character.
| Escape Sequence | Meaning | Example |
|---|---|---|
\" |
Double quote | "He said \"Hello\"" → He said "Hello" |
\' |
Single quote | 'It\'s fine' → It's fine |
\\ |
Backslash | "C:\\Users" → C:\Users |
\n |
New line | "Hello\nWorld" → prints on two lines |
\t |
Tab | "Hello\tWorld" → adds a tab space |
Example:
quote = "He said \"Ronaldo is great!\""
print(quote)He said "Ronaldo is great!"
Indexing and Slicing Strings
Each character in a string has an index number, starting from 0.
name = "Liverpool"
print(name[0])
print(name[1])
print(name[2])L
i
v
Negative indices count from the end:
print(name[-1])
print(name[-2])l
o
You can also slice strings to extract a range of characters:
name = "Prosper"
print(name[0:3])
print(name[:2])
print(name[2:])Pro
Pr
osper
A Boolean represents one of two values:
True
False
These are capitalized in Python (not true or false like in some other languages).
Booleans are part of the built-in bool data type, used to express logical truth or falsehood in your programs.
Why Booleans Matter
Booleans are essential in decision-making, they control how your program behaves under different conditions.
They’re especially useful with conditional statements like if, else, and while .
Example:
done = True
if done:
print("Task completed!")
else:
print("Task not done yet.")Task completed!
Python automatically evaluates many types of values as either True or False when used in a Boolean context (like if statements).
| Data Type | Example Value | Evaluates To |
|---|---|---|
| Numbers | 0 |
False |
| Numbers | 1, -5, 3.14 |
True |
| Strings | "" (empty string) |
False |
| Strings | "Hello" |
True |
| Lists / Tuples / Sets / Dicts | [], (), {}, dict() (empty) |
False |
| Lists / Tuples / Sets / Dicts | Non-empty ([1], {"a"}, {"key": 1}) |
True |
| None | None |
False |
Example:
print(bool(0))
print(bool(42))
print(bool(""))
print(bool("Python"))
print(bool([]))
print(bool([1, 2, 3]))False
True
False
True
False
True
Checking if a Value is Boolean
You can verify whether a variable is a Boolean using either type() or isinstance().
done = True
print(type(done) == bool)
print(isinstance(done, bool))True
True
The bool() Constructor
You can convert other data types into booleans using the bool() function.
print(bool(1))
print(bool(0))
print(bool("hello"))
print(bool("")) True
False
True
False
This is often used to simplify conditions or clean data for logical evaluation.
Booleans work closely with logical operators:
| Operator | Meaning | Example | Result |
|---|---|---|---|
and |
True if both are True | True and False |
False |
or |
True if at least one is True | True or False |
True |
not |
Negates the boolean | not True |
False |
Example:
is_sunny = True
is_weekend = False
print(is_sunny and is_weekend)
print(is_sunny or is_weekend)
print(not is_weekend)False
True
True
Boolean Logic Diagram
The any() and all() Functions
Python provides two very useful built-in functions for working with Boolean lists or iterables:
any()
Returns True if at least one element in the list is True.
book_1_read = True
book_2_read = False
read_any_book = any([book_1_read, book_2_read])
print(read_any_book)True
all()
Returns True only if all elements in the list are True.
book_1_read = True
book_2_read = False
read_any_book = all([book_1_read, book_2_read])
print(read_any_book)False
| Function | Returns True if… |
Example | Result |
|---|---|---|---|
any() |
At least one value is True | any([False, True, False]) |
True |
all() |
All values are True | all([True, True, True]) |
True |
Let’s combine what we learned into a simple example:
user_logged_in = True
has_admin_rights = False
if user_logged_in and has_admin_rights:
print("Welcome, Admin!")
elif user_logged_in and not has_admin_rights:
print("Welcome, User!")
else:
print("Please log in.")Welcome, User!
Numbers are one of the most fundamental data types in Python.
They can be of three main types:
| Type | Class | Example | Description |
|---|---|---|---|
| Integer | int |
8 |
Whole numbers, positive or negative, without decimals. |
| Floating Point | float |
3.14 |
Real numbers with decimal points. |
| Complex | complex |
2 + 3j |
Numbers with real and imaginary parts. |
Integer Numbers in Python
Integer numbers are represented using the int class.
Example:
age = 8
print(type(age))<class 'int'>
You can also use the constructor:
age = int(8)Floating Point Numbers in Python
Floating point numbers (fractions or decimals) use the float class.
fraction = 0.1
print(type(fraction))<class 'float'>
You can also define them with the constructor:
fraction = float(0.1)Complex Numbers in Python
Complex numbers are represented using the complex class.
complexNumber = 2 + 3j
print(type(complexNumber))<class 'complex'>
Or you can create them using the constructor:
complexNumber = complex(2, 3)You can access their real and imaginary parts:
complexNumber.real
complexNumber.imag3.0
Arithmetic Operations on Numbers
You can perform arithmetic operations using Python’s arithmetic operators:
| Operator | Name | Example | Result |
|---|---|---|---|
+ |
Addition | 1 + 1 |
2 |
- |
Subtraction | 2 - 1 |
1 |
* |
Multiplication | 2 * 2 |
4 |
/ |
Division | 4 / 2 |
2.0 |
% |
Modulus (Remainder) | 4 % 3 |
1 |
** |
Exponentiation | 4 ** 2 |
16 |
// |
Floor Division | 7 // 2 |
3 |
Compound Assignment Operators
These let you perform an operation and assignment in one step:
| Operator | Example | Equivalent To |
|---|---|---|
+= |
x += 1 |
x = x + 1 |
-= |
x -= 1 |
x = x - 1 |
*= |
x *= 2 |
x = x * 2 |
/= |
x /= 3 |
x = x / 3 |
%= |
x %= 2 |
x = x % 2 |
Example:
age = 7
age += 1
age8
Built-in Numeric Functions
Python provides several built-in functions for numeric manipulation:
| Function | Description | Example | Output |
|---|---|---|---|
abs(x) |
Absolute value | abs(-7) |
7 |
round(x) |
Round to nearest integer | round(4.6) |
5 |
round(x, n) |
Round to n decimals | round(3.14159, 2) |
3.14 |
Useful Math Modules
Python’s standard library provides several modules for mathematical operations:
| Module | Purpose |
|---|---|
math |
Common mathematical functions and constants (e.g., sqrt, pi, sin) |
cmath |
Functions for complex numbers |
decimal |
High-precision decimal arithmetic |
fractions |
Operations on rational numbers |
Example:
import math
print(math.sqrt(16))
print(math.pi)4.0
3.141592653589793
math ModuleThe math module provides access to common mathematical functions and constants.
import math
print(math.pi)
print(math.sqrt(16))
print(math.factorial(5))
print(math.ceil(2.3))
print(math.floor(2.9))3.141592653589793
4.0
120
3
2
Useful constants and functions
math.pi → π (pi)
math.e → Euler’s number (≈2.718)
math.pow(x, y) → x raised to power y
math.log(x, base) → logarithm
math.sin(), math.cos(), math.tan() → trigonometric functions
decimal ModuleThe decimal module helps you perform high-precision decimal arithmetic, useful in finance or where rounding errors are unacceptable.
from decimal import Decimal, getcontext
getcontext().prec = 4 # set precision to 4 decimal places
x = Decimal('1.10')
y = Decimal('2.30')
print(x + y) # precise result3.40
Why use Decimal? Because regular floats can introduce tiny rounding errors
fractions ModuleThe fractions module allows you to represent and work with rational numbers (fractions).
from fractions import Fraction
f1 = Fraction(1, 3)
f2 = Fraction(2, 5)
result = f1 + f2
print(result)11/15
Fraction can also convert floats or strings:
print(Fraction('0.25'))
print(Fraction(0.5))1/4
1/2
In Python, constants are variables whose values are meant to stay unchanged throughout the program.
However, unlike other languages (like Java or C++), Python doesn’t have a built-in way to enforce immutability, it relies on naming conventions and discipline.
Declaring Constants (By Convention)
By convention, constants are written in ALL UPPERCASE letters with underscores separating words.
WIDTH = 1024
HEIGHT = 256
APP_NAME = "MyPythonApp"This tells other developers:
“These values are fixed. Don’t modify them!”
However, Python won’t stop you from reassigning them:
WIDTH = 800 # This works, but it's bad practice!Using Enums as True Constants
If you want to make your constants truly unchangeable, you can use the Enum class from the enum module.
from enum import Enum
class Constants(Enum):
WIDTH = 1024
HEIGHT = 256Now, you can access constants safely:
print(Constants.WIDTH.value)
print(Constants.HEIGHT.value)1024
256
Enums prevent accidental reassignment:
# Constants.WIDTH = 500 # ❌ This will raise an errorEnums (short for Enumerations) are a set of symbolic names bound to unique, constant values.
They make your code more readable, organized, and error-resistant .
Defining an Enum
You define an Enum by importing the Enum class and creating a subclass:
from enum import Enum
class State(Enum):
INACTIVE = 0
ACTIVE = 1Now, you can use them like named constants:
print(State.INACTIVE)
print(State.ACTIVE)State.INACTIVE
State.ACTIVE
Accessing Enum Values
print(State.ACTIVE.name)ACTIVE
print(State.ACTIVE.value)1
print(State(1))
print(State['ACTIVE'])State.ACTIVE
State.ACTIVE
Iterating and Counting Enum Members
You can list all members:
list(State)[<State.INACTIVE: 0>, <State.ACTIVE: 1>]
Count how many members exist:
len(State)2
Loop through members:
for state in State:
print(state.name, "=", state.value)INACTIVE = 0
ACTIVE = 1
When to Use Enums
Use Enums when you need:
Named constants with clear meaning
Predefined states (e.g., ON/OFF, SUCCESS/FAILURE)
Safety against accidental changes
Interacting with the user is one of the most common tasks in programming. In Python, you can both display output and receive input from the user using simple built-in functions.
Displaying Output with print()
To show text or data on the screen, we use the print() function.
name = "Bukayo Saka"
print(name)Bukayo Saka
You can print multiple values by separating them with commas:
print(name, "plays for Arsenal")
print("I like", name)Bukayo Saka plays for Arsenal
I like Bukayo Saka
[critical] Getting User Input with input()
To let users type something into your program, use the input() function.
print("What is your age?")
age = input()
11
print("Your age is " + age)How it works:
The program pauses and waits for the user to type something.
When the user presses Enter, the input is stored in the variable (age here).
Everything you get from input() is a string, even numbers.
Converting Input to Numbers
Since input() always returns a string, you often need to convert the input into another type, such as int or float.
age = int(input("Enter your age:"))
print("In 10 years, you will be", age + 10)Output example:
Enter your age: 25
In 10 years, you will be 35If you expect decimals:
height = float(input("Enter your height in meters: "))
print("Your height is", height, "m")Handling Invalid Input
If the user types something that can’t be converted to a number, the program will raise an error.
To prevent this, use a try/except block.
try:
age = int(input("Enter your age: "))
print("You are", age, "years old.")
except ValueError:
print("That doesn’t look like a valid number!")Combining Output and Input
You can make your input prompts shorter and cleaner by passing the message directly to input():
name = input("What is your name? ")
Prosper
print("Hello,", name + "!")Output:
What is your name? Prosper
Hello, Prosper!Control statements allow your program to make decisions, running certain code only when a condition is met.
They form the foundation of logic in Python programming.
The if Statement
The simplest form of a control statement is the if statement.
It checks a condition, if it’s True, Python executes the indented block of code that follows.
condition = True
if condition == True:
print("The condition")
print("was True")The condition
was True
How it works:
If the condition evaluates to True, the indented code runs.
If it’s False, Python skips that block.
What Is a Block?
A block is a group of indented lines of code that belong together.
Indentation (4 spaces by convention) tells Python which statements are inside a block.
if True:
print("Inside the block")
print("Outside the block")Inside the block
Outside the block
⚠️ If indentation is inconsistent, Python raises an IndentationError.
The else Statement
You can attach an else clause to handle the case when the condition is False.
condition = False
if condition:
print("Condition is True")
else:
print("Condition is False")Condition is False
The elif Statement
When you have multiple possible conditions, use elif (short for “else if”).
name = "Prosper"
if name == "Beko":
print("Hello Beko")
elif name == "Prosper":
print("Hello Prosper")
elif name == "Patrick":
print("Hello Patrijk")
else:
print("Hello Stranger")Hello Prosper
Only one block runs, the first one with a True condition. Once Python finds a match, it skips the rest.
Chaining Multiple Conditions
You can check several conditions in one expression using logical operators (and, or, not):
age = 19
has_id = True
if age >= 18 and has_id:
print("You are allowed to enter.")
else:
print("Access denied.")You are allowed to enter.
Inline if (Ternary Expression)
Sometimes you want to assign a value depending on a condition, all in one line.
That’s when the inline if (also called the ternary operator) is used.
a = 2
result = 2 if a == 0 else 3
print(result)3
syntax reminder:
<value_if_true> if <condition> else <value_if_false>Example: Nested Conditions
You can also put one if inside another. This is known as nested if statements.
age = 20
citizen = True
if age >= 18:
if citizen:
print("You can vote.")
else:
print("You must be a citizen to vote.")
else:
print("You are too young to vote.")You can vote.
Visual Flow of if–elif–else
| Keyword | Purpose | Example |
|---|---|---|
if |
Executes code if condition is True |
if x > 0: |
elif |
Checks another condition if previous is False |
elif x == 0: |
else |
Runs if all previous conditions are False |
else: |
and, or, not |
Combine or invert conditions | if x > 0 and y > 0: |
Lists are one of the most commonly used data structures in Python.
They allow you to store multiple values in a single variable and access them easily.
Creating Lists
A list is defined using square brackets []:
dogs = ["Max", "Bob"]A list can hold different data types:
items = ["Cup", 1, "Tree", True]You can create an empty list:
# items = []Checking if an Item Exists
Use the in operator:
print("Tree" in items)
print("Peter" in dogs)True
False
Accessing Items (Indexing)
List indexes start at 0:
items[0]
items[1]
items[3]True
Negative Indexing
Negative indexes count from the end:
items[-1]
items[-2]'Tree'
Changing Items
You can replace values at a specific index:
items[0] = "Banana"Finding the Index of an Item
Use the index() method:
names = ["Ronaldo", "Messi", "Bukayo saka"]
print(names.index("Messi"))1
List Slicing
You can extract parts of a list:
items[0:2]
items[2:]
items[:3]['Banana', 1, 'Tree']
Getting the Length of a List
Use len():
len(items)4
Adding Items
append() → adds ONE item at the end
items.append("Test")extend() → adds MULTIPLE items
items.extend(["A", "B"])+= operator → also extends
items += ["C", "D"]Removing Items
Use remove():
items.remove("Test") #Removes the first occurrence only.Inserting Items
Insert one item at a specific index:
items.insert(1, "Test")
# (value, position)(The earlier version had arguments reversed — now fixed.)
Insert multiple items at a specific position:
items[1:1] = ["Test1", "Test2"]Sorting Lists
Basic sorting:
items.sort()Sorting only works if items are comparable (e.g., all strings, or all numbers).
Fix inconsistent string cases:
items.sort(key=str.lower)Copy a list before sorting:
copy_items = items[:]
copy_items.sort()Or use sorted() (does NOT change original):
new_list = sorted(items, key=str.lower)Operations Summary Table
| Operation | Description | Example |
|---|---|---|
| Create list | Define new list | items = [1,2,3] |
| Check item | Membership test | "a" in items |
| Access item | Get value by index | items[0] |
| Modify item | Change a value | items[1] = 10 |
| Find index | Locate item | items.index("Bob") |
| Slice | Extract part | items[1:3] |
| Length | Number of items | len(items) |
| Append | Add one | items.append(x) |
| Extend | Add many | items.extend([...]) |
| Insert | Add at position | items.insert(1, x) |
| Remove | Remove item | items.remove(x) |
| Sort | Order list | items.sort() |
List Index Visualization
Tuples are another core data structure in Python, very similar to lists, with one important difference:
Tuples are immutable
Once a tuple is created:
You cannot add new items
You cannot remove items
You cannot change existing items
Because of this, tuples are often used for:
Fixed collections of values
Values that must not be changed
Faster operations compared to lists (tuples are more lightweight)
Creating Tuples
Tuples use parentheses ( ) instead of square brackets:
names = ("Prosper", "Kigali")You can also mix types:
info = ("Prosper", 42, True)Creating an empty tuple:
empty_tuple = ()Creating a single-item tuple
Important: you must include a comma, otherwise Python sees it as a normal value.
single = ("Prosper",) # This is a tuple
not_tuple = ("Prosper") # This is just a stringAccessing Tuple Elements (Indexing)
Tuples are ordered, so you can access items by index:
names = ("Prosper", "Kigali")
names[0]
names[1]'Kigali'
Negative indexing works too:
names[-1]
names[-2]'Prosper'
Useful Tuple Methods
index()
Returns the index of the first matching value:
names.index("Prosper")
names.index("Kigali")1
count()
Counts how many times a value appears:
("a", "b", "a").count("a")2
Tuple Length
Use the len() function:
len(names)2
Membership Test
Check if an item exists with in:
"Roger" in names
"Syd" in names
"Tina" in namesFalse
Tuple Slicing
Just like lists and strings:
names = ("Messi", "Kigali", "Venice", "Nyamirambo")
names[0:2]
names[1:]
names[:3]('Messi', 'Kigali', 'Venice')
Sorting Tuples
sorted() can sort the tuple but returns a list, not a tuple:
sorted(names) ['Kigali', 'Messi', 'Nyamirambo', 'Venice']
Convert back to a tuple if needed:
tuple(sorted(names))('Kigali', 'Messi', 'Nyamirambo', 'Venice')
Concatenating Tuples
Use the + operator:
names = ("Rutsiro", "Karongi")
new_tuple = names + ("Rubavu", "Musanze")
print(new_tuple)('Rutsiro', 'Karongi', 'Rubavu', 'Musanze')
Why Use Tuples Instead of Lists?
Tuples are chosen when:
The data must remain unchanged
The collection is fixed
You want faster performance
You want to use the values as dictionary keys or set elements (only immutable objects can be used as keys)
Example:
location = (10, 20)
positions = {location: "Player1"} # Works because tuple is hashable
location
positions{(10, 20): 'Player1'}
Lists cannot do this because they are mutable.
Tuple indexing works the same way as list indexing.
Dictionaries are one of the most powerful and widely used data structures in Python.
While lists store collections of values, dictionaries store collections of key–value pairs, allowing for fast lookups, updates, and flexible data organization.
You can create a dictionary using curly braces {} with key–value pairs:
artist = {'name': 'Kivumbi'}A dictionary can contain multiple key–value pairs:
artist = {'name': 'Kivumbi', 'age': 26}Keys must be immutable
Common key types include:
strings
numbers
tuples
Values can be of any type (strings, numbers, lists, other dictionaries, etc.)
Access a value by using its key:
artist['name']
artist['age']26
If you try to access a key that doesn’t exist, Python raises a KeyError.
You can modify a value using the same key-access notation:
artist['name'] = 'Bwiza'get() allows safe access to keys and lets you specify a default value:
artist.get('name')
artist.get('color', 'N/A')'N/A'
pop()
Removes a key and returns its value:
artist.pop('name')'Bwiza'
popitem()
Removes and returns the last inserted key–value pair:
artist.popitem()('age', 26)
Use the in operator:
player = {'name': 'Lamine', 'age': 18}
'name' in playerTrue
Convert the dictionary views to lists if needed:
list(player.keys())
list(player.values())
list(player.items())[('name', 'Lamine'), ('age', 18)]
items() returns a list of (key, value) tuples.
Dictionary Length
len(player)2
Add a new key–value pair simply by assignment:
player['sport'] = 'Football'Removing Items with del
del player['sport']Copying Dictionaries
Use the copy() method to create a shallow copy:
playerCopy = player.copy()Key–Value Mapping
Sets are another key data structure built into Python.
They behave like mathematical sets: unordered collections of unique elements, optimized for membership tests, deduplication, and “set arithmetic”.
According to the official Python:
“A set is an unordered collection with no duplicate elements.”
Sets support operations like union, intersection, difference and symmetric difference.
What-makes a Set Special?
Unordered: no index or guaranteed order.
Unique elements only: duplicates are automatically removed.
Mutable (for standard set): you can add or remove elements.
There is an immutable version, frozenset, which cannot be changed after creation.
Creating Sets
Literal syntax:
names = {"Prosper", "Kigali"}Set constructor:
names = set(["Prosper", "Kigali"])Membership test:
"Prosper" in names
"Rusizi" in namesFalse
Length of a set:
len(names) 2
Convert to list (for ordered processing):
| Operation | Example Code | Result |
|---|---|---|
| Intersection (common elements) | set1 & set2 or set1.intersection(set2) |
elements in both |
| Union (all elements) | `set1 | set2orset1.union(set2)` |
| Difference (items in first not in second) | set1 - set2 or set1.difference(set2) |
elements only in first |
| Symmetric Difference (in either but not both) | set1 ^ set2 or set1.symmetric_difference(set2) |
elements exclusive to each |
Examples:
set1 = {"Messi", "Dimaria"}
set2 = {"Cristiano", "Dimaria"}
intersect = set1 & set2
union = set1 | set2
difference = set1 - set2
sym_diff = set1 ^ set2
print(intersect)
print(union)
print(difference)
print(sym_diff){'Dimaria'}
{'Cristiano', 'Dimaria', 'Messi'}
{'Messi'}
{'Cristiano', 'Messi'}
From documentation:
“Set objects also support mathematical operations like union, intersection, difference, and symmetric difference.”
set1 = {"Mugisha", "Benjamin"}
set2 = {"Benjamin"}
set2 <= set1 # (set2 is subset of set1)
set1 >= set2 # (set1 is superset of set2)True
One can also use <, > for proper subset/superset (when not equal).
Add an element:
names.add("Musanze")Remove an element:
# names.remove("Huye") # raises KeyError if missing
names.discard("Huye") # safer — no error if missingPop an arbitrary element:
item = names.pop() # removes and returns some element (no order)Clear all elements:
names.clear()frozensetWhen you need a hashable, immutable set (for using as dictionary keys, set elements, etc.), use frozenset:
fs = frozenset(["Roger", "Syd"])
# fs.add("Luna") → TypeError: cannot modify frozensetA function in Python is a named block of code that you can define once and run (call) whenever needed. It helps you organize your code, avoid repetition, and make your programs easier to understand and maintain.
Why Functions Matter
Functions allow you to break your program into smaller, reusable pieces (“modular code”). datacamp.com
They improve readability and help you avoid writing the same code multiple times.
A function can take inputs (parameters), execute logic, and optionally return a value.
Definition
Use the def keyword, name your function, possibly specify parameters, then a colon and an indented block of code:
def hello():
print('Hello!')Here hello is the function name; the body (print statement) is indented. Without calling it, nothing will happen.
Call
To run the function, use:
hello()Hello!
You can call the function multiple times.
Parameters and Arguments
Parameters are the names listed in the function definition.
Arguments are the actual values you pass when calling the function.
Example:
def hello(name):
print('Hello ' + name + '!')
hello('Munezero') # 'Munezero' is argument; name is parameterHello Munezero!
Default Parameter Values
You can provide default values so the caller doesn’t have to pass that argument:
def hello(name='my friend'):
print('Hello ' + name + '!')
hello()
hello('Ngarambe')Hello my friend!
Hello Ngarambe!
Multiple Parameters
def hello(name, age):
print('Hello ' + name + ', you are ' + str(age) + ' years old!')
hello('Kanamugire', 7)Hello Kanamugire, you are 7 years old!
Immutable types (int, float, string, tuple) cannot be changed inside the function in a way that impacts the caller.
def change(value):
value = 2
val = 1
change(val)
print(val)Mutable types (list, dict, set) can be changed inside the function and those changes will reflect outside.
Returning Values
Functions can use the return statement to send a value back to the caller, and that ends the function execution
def hello(name):
print('Hello ' + name + '!')
return name
result = hello('Kanamugire')
print(result)Hello Kanamugire!
Kanamugire
You can omit a value and just write return (or no return), in which case the function returns None.
def hello(name):
if not name:
return
print('Hello ' + name + '!')
hello('')You can return multiple values separated by commas, Python packs them into a tuple.
def hello(name):
print('Hello ' + name + '!')
return name, 'Prosper', 7
res = hello('Beko')
print(res) # ('Syd', 'Roger', 8)Hello Beko!
('Beko', 'Prosper', 7)
Good Function Design & Best Practices
Use descriptive function names (verbs): calculate_total(), get_user_input().
Keep functions short and focused, they should do one task.
Use default values for common cases.
Document what the function does (docstrings).
Avoid side effects unless intended (modifying global data).
Use return values rather than printing inside if you need to reuse results.
Python is built on a very important idea:
Everything in Python is an object.
This includes:
This includes:
Integers (int)
Strings (str)
Floats (float)
Lists (list)
Tuples (tuple)
Dictionaries (dict)
Functions
Even modules and classes themselves
Every object has:
Attributes → stored data
Methods → functions that belong to the object
You access both using dot notation:
Integer Objects
Let’s define a simple integer:
age = 8Even though 8 looks like a primitive value, it is actually a full Python object. This means it has attributes and methods, just like any other object:
print(age.real)
print(age.imag)
print(age.bit_length())8
0
4
Explanation:
real → real part of the number
imag → imaginary part (0 for real numbers)
bit_length() → number of bits required to represent the number in binary
List Objects
When you create a list:
items = [1, 2]The list object exposes methods such as:
items.append(3) # Adds item at the end
items.pop() # Removes last item3
Each object type exposes different methods, because different types behave differently.
The id() Function: Memory Address
Python provides the built-in function id() to inspect the memory address of an object:
id(age)140723354798744
The number will differ on your machine.
Reassigning a Variable Changes Its Object
If you change the value of a variable, Python creates a new object in memory.
age = 8
print(id(age))
age = 9
print(id(age))140723354798744
140723354798776
The two id() values will be different because:
8 and 9 are different objects
Reassigning the variable makes it point to a new object
Modifying a Mutable Object Keeps the Same Address
Lists are mutable, meaning they can be changed in place.
items = [1, 2]
print(id(items))
items.append(3)
print(items) # [1, 2, 3]
print(id(items))2069010423168
[1, 2, 3]
2069010423168
The memory address stays the same because the list object itself is modified, not replaced.
Mutable vs Immutable Objects
Python objects come in two main types:
Cannot be changed after creation.
Examples:
int
float
bool
str
tuple
If you “change” them, Python creates a new object.
Example:
age = 8
print(id(age))
age += 1
print(id(age))140723354798744
140723354798776
The id() value changes → new object.
Can change without replacing the object.
Examples:
list
dict
set
Modifying a list:
things = [1, 2]
print(id(things))
things.append(3)
print(id(things)) # Same address2069010739392
2069010739392
Loops allow us to repeat a block of code multiple times.
They are one of the most important control structures in programming.
Python provides two types of loops:
while loops → repeat as long as a condition is true
for loops → repeat for each item in a sequence
while LoopsA while loop runs its block as long as the condition evaluates to True.
Basic Structure
while condition:
# repeated codewhile condition:
# repeated codeExample: Infinite Loop
#condition = True
#while condition == True:
# print("The condition is True")This loop never ends because condition never becomes False.
Stopping a While Loop
We can stop the loop by updating the condition:
condition = True
while condition == True:
print("The condition is True")
condition = False
print("After the loop")The condition is True
After the loop
Flow:
First iteration → condition is True
We print the message
We set condition = False
Next iteration → condition is False → loop stops
Using a Counter
A common pattern uses a counter variable:
count = 0
while count < 10:
print("Count is:", count)
count = count + 1
print("Loop finished")Count is: 0
Count is: 1
Count is: 2
Count is: 3
Count is: 4
Count is: 5
Count is: 6
Count is: 7
Count is: 8
Count is: 9
Loop finished
how it works:
count = 0
↓
Is count < 10?
↓ Yes
Print count → count += 1
↓
Loop again
↓ No
End loop
Common Mistake: Forgetting Counter Update
This causes an infinite loop:
# count = 0
# while count < 5:
# print(count) # Never changes -> infinite loop!A for loop allows you to iterate over:
lists
strings
tuples
ranges
dictionaries
etc.
Python takes each item in a sequence and assigns it to a loop variable.
Example: Loop Through a List
items = [1, 2, 3, 4]
for item in items:
print(item)1
2
3
4
Looping a Set Number of Times Using range()
range(n) generates numbers from 0 to n-1.
example:
for item in range(4):
print(item)0
1
2
3
Using enumerate() to Get Index + Value
enumerate() returns both the index and the item:
items = [1, 2, 3, 4]
for index, item in enumerate(items):
print(index, item)0 1
1 2
2 3
3 4
while vs for, When to Use Which| Use Case | Best Loop |
|---|---|
| You know the number of iterations | for |
| You want to repeat until a condition changes | while |
| Iterating over lists/strings | for |
| Waiting for user input | while |
| Building counters | both work |
For Loop Over a List (illustration)
Mistake: Misusing range()
range(1,5) # gives 1,2,3,4 (not 5)range(1, 5)
Mistake: Changing a list while iterating over it
Mistake: Infinite while loops caused by forgetting updates
Inside both while and for loops, Python provides two special keywords that control how the loop behaves:
1. continue
Skips the current iteration and jumps to the next iteration of the loop.
2. break
Immediately stops the loop completely, and Python continues with the next line after the loop.
These tools give you more control inside loops, especially when you need to skip certain values or stop early.
continue Statementcontinue tells Python:
“Skip the rest of this loop iteration and move on to the next one.”
Example—Skip number 2:
items = [1, 2, 3, 4]
for item in items:
if item == 2:
continue
print(item)1
3
4
Explanation:
When item == 2, the continue statement triggers
Python skips the print() and moves to the next item
break Statementbreak tells Python:
“Stop the loop right now and jump out completely.”
Example—Stop when item reaches 2:
items = [1, 2, 3, 4]
for item in items:
if item == 2:
break
print(item)1
When item == 2, Python hits break
Loop ends immediately
It does not check items 3 and 4
Execution continues after the loop
break and continue in while LoopsSkip even numbers:
x = 0
while x < 5:
x += 1
if x % 2 == 0:
continue
print(x)1
3
5
Stop when x reaches 3:
x = 0
while x < 5:
x += 1
if x == 3:
break
print(x)1
2
Object-Oriented Programming (OOP) is a major part of Python.
While Python gives you built-in types like int, str, list, you can also create your own types using classes .
A class is a blueprint.
An object is an instance of that blueprint.
Defining a Class
To define a class, use the class keyword:
class Dog:
# class definition
passRight now the class does nothing, but it exists as a type.
Creating Objects (Instances)
You create an object by calling the class as if it were a function:
Max = Dog()Now Max is an object of type Dog.
print(type(Max))<class '__main__.Dog'>
Adding Methods to a Class
A class normally contains methods (functions inside a class).
class Dog:
def bark(self):
print('WOF!')Methods must include self as the first parameter.
self refers to the current instance.
Example:
Max = Dog()
Max.bark()WOF!
What is self?
self is a reference to the object calling the method.
Dog object (Max)
↓
Max.bark()
↓
self = Max inside method
__init____init__() is a constructor.
It runs automatically whenever you create an object.
Example:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print('WOF!')Creating an object:
Max = Dog("Roger", 8)
print(Max.name)
print(Max.age)
Max.bark()Roger
8
WOF!
__init__ does:Assigns values to object attributes
Prepares the object for use
Objects can have:
Attributes → Variables that belong to the object
Methods → Functions that belong to the object
Example:
class Dog:
def __init__(self, name):
self.name = name
def speak(self):
print(self.name + " says WOF!")Instance attribute:
Defined inside __init__.
Unique for each object.
self.nameClass attribute:
Shared by all objects.
class Dog:
species = "Canine" # class attributeEvery object sees the same value unless overridden.
Example with Both Types
class Dog:
species = "Canine" # class attribute
def __init__(self, name):
self.name = name # instance attribute
fido = Dog("Fido")
buddy = Dog("Buddy")
print(fido.species) # Canine
print(buddy.species) # CanineCanine
Canine
A class can inherit from another class:
class Animal:
def walk(self):
print("Walking...")Child class:
class Dog(Animal):
def bark(self):
print("WOF!")Using the inherited method:
Max= Dog()
Max.walk()
Max.bark()Walking...
WOF!
Method Overriding
A child class can replace a parent class method:
class Animal:
def speak(self):
print("Animal sound")
class Dog(Animal):
def speak(self):
print("WOF!")Using super()
You can call the parent class method inside the child class:
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, age):
super().__init__(name)
self.age = age✓ Classes define custom data types
✓ Objects are instances of classes
✓ self references the current instance
✓ __init__ initializes new objects
✓ Attributes hold data
✓ Methods perform actions
✓ Inheritance allows code reuse
As your programs grow larger, organizing everything in a single file becomes messy. Modules help us structure code by splitting it into multiple files, each containing related functions, classes, and variables.
What Is a Module?
A module is simply a Python file (.py) that contains code.
Examples:
math.py → a module
dog.py → a module
utils/helpers.py → a module inside a folder
Python itself comes with many built-in modules like:
math (Provides mathematical functions and constants.)
random (Generates random numbers and performs random operations.)
os (Enables interaction with the operating system (files, paths, processes).
datetime (Works with dates, times, and time intervals.)
You have used modules already, whenever you write:
import mathYou are importing a module.
Why Use Modules?
Modules help you:
Organize code into logical pieces
Reuse code across multiple files
Collaborate with others on large projects
Avoid extremely long Python files
Let’s create a simple module called dog.py.
dog.py
def bark():
print("WOF!")This file defines a single function, bark().
To use the code inside dog.py, you import it in another file.
main.py
import dog
dog.bark()import dog loads the module
dog.bark() calls the function using dot notation:
module_name.function_name
Instead of importing the whole module, you can import just what you need.
main2.py
from dog import bark
bark() # No need for dog.bark()WOF!
When to use which?
| Style | When to Use |
|---|---|
import dog |
When you want many functions or want to keep clear namespaces |
from dog import bark |
When you only need one item and want clean code |
Projects often look like this:
Why is __init__.py required?
It signals to Python that this folder is a package, meaning it contains modules.
Importing From a Folder
Option A: Import the module
from lib import dog
dog.bark()WOF!
Option B: Import a specific function
from lib.dog import bark
bark()WOF!
dog2.py
def bark():
print("WOF!")
def sleep():
print("zzzz...")main2.py
from dog2 import bark, sleep
bark()
sleep()WOF!
zzzz...
Module alias
import dog2 as d
d.bark()WOF!
Function alias
from dog2 import bark as b
b()WOF!
Useful when modules have long names.
Math operations
import math
print(math.sqrt(49))7.0
Random numbers
import random
print(random.randint(1, 100))58
Dates and times
from datetime import datetime
print(datetime.now())2026-01-20 17:18:18.411514
Sometimes a module contains:
def bark():
print("WOF!")
if __name__ == "__main__":
bark()WOF!
This allows:
Running the file directly → it barks
Importing it in another file → it does NOT bark automatically
This is very common in real projects.
| Error | Meaning |
|---|---|
ModuleNotFoundError |
Python cannot find the file/folder |
ImportError |
The function or class does not exist in the module |
Missing __init__.py |
Python treats the folder as normal, not a package |
| Circular import | Two modules import each other |
Python comes with a huge collection of ready-to-use modules called the Python Standard Library.
You don’t need to install anything, these modules come built-in with every Python installation.
Think of the Standard Library as Python’s toolbox:
✔ tools for math ✔ tools for dates ✔ tools for file operations ✔ tools for databases ✔ tools for networking ✔ tools for random numbers ✔ tools for statistics ✔ tools for text processing ✔ tools for debugging …and hundreds of others.
A massive set of modules written in Python and C, included by default.
You can see the full official list here: https://docs.python.org/3/library/
It includes over 200 modules.
Why the Standard Library Is Important
Because it allows you to:
Avoid reinventing the wheel
Build powerful programs quickly
Access system-level features (files, OS, networking)
Manipulate data formats (JSON, CSV, XML)
Work with time, dates, numbers, randomness
Do advanced computation
Many beginners don’t know how powerful it is, professional developers use the standard library daily.
Below is a short overview of the most important ones.
math – Mathematical FunctionsUsed for:
Square roots
Trigonometry
Powers
Constants like pi (π)
Example:
import math
print(math.sqrt(9))
print(math.pi)
print(math.sin(0))3.0
3.141592653589793
0.0
random – Random Number GenerationUsed for:
Random integers
Random floats
Random selection
Shuffling lists
Example:
import random
print(random.randint(1, 10))
print(random.choice(["dog", "cat", "mouse"]))6
mouse
datetime – Dates and TimesUsed for:
Getting current date/time
Date math
Formatting timestamps
Example:
from datetime import datetime
now = datetime.now()
print(now)2026-01-20 17:18:18.450678
json – Working with JSON DataEssential for APIs and web data.
import json
data = '{"name": "Alice", "age": 25}'
obj = json.loads(data) # JSON → Python dict
print(obj["name"])Alice
os – Operating System UtilitiesUsed for:
Creating/deleting folders
Listing files
Environment variables
Example:
import os
print(os.listdir()) # list files in the current directoryre – Regular ExpressionsUsed for searching text patterns.
import re
match = re.search(r"\d+", "My number is 173")
print(match.group()) # 173173
statistics – Statistical FunctionsUsed for:
Mean
Median
Variance
Standard deviation
Example:
import statistics
print(statistics.mean([1, 2, 3, 4]))2.5
sqlite3 – Built-in DatabasePython includes an SQL database with no installation.
import sqlite3
conn = sqlite3.connect("data.db")This is extremely useful for small applications.
urllib – URL HandlingUsed for downloading data or handling URLs.
Note: requests is NOT part of the standard library
Your original notes say:
requests to perform HTTP network requests
However, requests is NOT in the Python standard library.
It is the most popular external library, but you must install it:
pip install requestsTo stay accurate, replace it with:
urllib.request → standard library alternativeYou import them exactly like your own modules:
Option 1: Import the whole module
import math
print(math.sqrt(4))2.0
Option 2: Import a specific function
from math import sqrt
print(sqrt(4))2.0
Same rules as custom modules.
Writing clean and readable code is a fundamental skill for every Python developer. Even though Python is known for being simple and expressive, your code can still become messy if you ignore consistent formatting.
To help developers write clean and readable code, Python has an official style guide called PEP 8.
What is PEP 8?
PEP 8 stands for Python Enhancement Proposal 8.
It defines the coding style conventions that all Python programmers are encouraged to follow.
These conventions cover:
indentation
naming rules
code layout
whitespace
comments
imports
line length
overall readability
PEP 8 helps maintain a consistent look across Python projects, making your code easier for others to understand, and easier for future you to understand too.
You can read the full guide here: https://www.python.org/dev/peps/pep-0008/
Following PEP 8 helps you:
✔ Write code that looks professional
✔ Make your programs easier to maintain
✔ Work smoothly with teams
✔ Reduce confusion and errors
✔ Learn the “Pythonic” way of coding
Good code isn’t just correct, it’s readable.
Below are the most important rules you should start applying from day one.
Use 4 spaces per indentation level
Never use tabs
Never mix tabs and spaces
Example:
def greet():
print("Hello")Python files should use:
UTF-8This ensures all characters and symbols work correctly.
Maximum recommended line length: 80 characters
Modern codebases sometimes use up to 120, but 80 is the standard in training and exams
This helps avoid horizontal scrolling and improves readability.
Bad:
x = 5; y = 10Good
x = 5
y = 10snake_case for:
functions
variables
file names
def calculate_total():
user_age = 21CamelCase for classes:
class DataProcessor:
passUPPERCASE for constants:
MAX_USERS = 200
PI = 3.14lowercase (no underscores) for package names:
mypackage
tools
datascienceBad:
a = 10
b = 3Good:
height = 10
retry_delay = 3Add spaces around operators:
Correct:
x = a + b * 2Wrong:
x=a+b*2Avoid unnecessary spaces:
Wrong:
print( "Hello" )Hello
Correct:
print("Hello")Hello
Good comments explain why, not what.
Bad:
x = x + 1 # add 1 to xGood:
# Increase retry count after failed attempt
retry_count = retry_count + 1Avoid obvious comments and focus on clarifying logic or intent.
Blank lines help structure your code.
Add one blank line before each function
Add one blank line between methods inside a class
Inside a function, use blank lines to separate logical parts
Example:
def load_data():
data = read_file()
cleaned = clean(data)
return cleaned❌ my_list = [1 , 2 , 3]
✔ my_list = [1, 2, 3]
❌ print (“Hello”)
✔ print(“Hello”)
PEP 8 recommends this order:
Standard library imports
Third-party imports
Local imports
Example:
import os
import sys
import numpy as np
import requests
from dog2 import bark as bPEP 8 teaches you how to write clean, readable, and standardized Python code.
Following it from the beginning will make you a better programmer and prepare you for real-world projects, teamwork, and professional development.
Debugging is one of the most important skills every programmer must learn. No matter how careful you are, your code will eventually have bugs, mistakes, unexpected behavior, or logical errors.
Knowing how to debug will save you hours of frustration and make you a stronger Python developer.
Python provides a built-in debugger called pdb (Python Debugger), which lets you pause your program, inspect variables, and run code step by step.
What Is Debugging?
Debugging means:
finding errors
understanding why they happen
fixing them
preventing them in the future
Debugging is not guesswork, it’s a structured investigation of how your code behaves.
Python makes debugging easy with the built-in command:
breakpoint()When your code reaches a breakpoint(), execution pauses and the debugger opens.
You can place as many breakpoints as you want in different parts of your code.
Example:
x = 10
breakpoint()
y = 20
print(x + y)30
When Python reaches breakpoint(), it stops and waits for your commands.
When the debugger activates, you enter an interactive mode where you can:
inspect variable values
move through the code
run commands
continue execution
This is extremely useful when you’re trying to understand why something behaves incorrectly
Here are the commands you will use most often in pdb:
Inspect a Variable
Just type its name:
(Pdb) xThis prints the current value of x.
n — Next line
n runs the next line of code within the current function
(Pdb) nThis is good when you want to advance step-by-step but avoid entering functions.
s — Step into
s steps into a function call if the next line is a function.
(Pdb) sUse this when you want to debug inside another function.
c — Continue
c runs the program normally until:
the next breakpoint
or the program finishes
(Pdb) cUse this when you’re done inspecting the current part.
q — Quit
q stops the debugger and terminates your program immediately.
(Pdb) qWhy Debugging Is Important
Debugging helps you:
✔ Understand why errors happen
✔ See exactly what your code is doing
✔ Check the value of variables at any moment
✔ Solve tricky logic issues
✔ Avoid print()-based debugging (which gets messy)
For complex loops, recursion, or unexpected behavior, debugging is often the fastest way to find the problem.
When you create a variable in Python, that variable is only accessible (or visible) within a certain part of your program. This concept is called variable scope.
Understanding scope is important because it helps you avoid errors and makes your code easier to understand and maintain.
Python has four levels of scope, following the LEGB rule:
L → Local
E → Enclosing
G → Global
B → Built-in
We’ll explain each one step by step.
A variable defined outside of all functions belongs to the global scope.
It can be accessed by any function as long as you don’t redefine it inside the function .
age = 8 # global variabl
def test():
print(age)
print(age)
test()Here, age is global and both the main program and the function can see it.
If you define a variable inside a function, it belongs to the function’s local scope.
This means:
It exists only while the function is running
It cannot be accessed outside the function
Example:
def test():
age = 8 # local variable
print(age)
test() # 8
print(age) # Error! age is not definedTrying to access age outside the function gives a NameError.
Enclosing Scope (Inner/Nested Functions)
If you have a function inside another function, the inner function can access variables from the outer function.
Example:
def outer():
x = 10
def inner():
print(x) # can access outer variable
inner()
outer()x is local to outer()
but it is also enclosing for inner()
This is the “E” in the LEGB rule.
Built-in Scope
Python has many built-in functions and keywords like:
len, print, range, list, dict(<function len(obj, /)>,
<function print(*args, sep=' ', end='\n', file=None, flush=False)>,
range,
list,
dict)
These are always available unless you override them (which you should avoid).
Example:
print(len("hello"))5
Python finds these names in the built-in scope.
If you want to change a global variable inside a function, you must declare it using the global keyword:
counter = 0
def increase():
global counter
counter += 1
increase()
print(counter)1
Without global, Python thinks you want to create a new local variable, leading to an error.
Best Practice: Avoid Too Many Globals
While global variables work, relying on them too much makes your code:
harder to debug
harder to understand
more likely to break as programs grow
Prefer function parameters instead of globals when possible.
Summary
| Scope Level | Where Defined | Accessible In |
|---|---|---|
| Local | Inside function | Only inside that function |
| Enclosing | Outer function of nested functions | Inner functions |
| Global | Top level of file | Entire file + functions (read-only unless global) |
| Built-in | Python interpreter | Everywhere |
When you run a Python program from the terminal, you can pass extra values called command-line arguments directly at execution time.
This allows you to:
configure how the program behaves
pass input without modifying code
build real scripts and utilities like CLI tools
Example:
python app.py hello
python app.py 10 20
python app.py -c redPython provides multiple ways to read and process these arguments.
sys.argv (Basic Method)The simplest way to access command-line arguments is using the sys module.
import sys
print(len(sys.argv))
print(sys.argv)4
['C:\\Users\\NIST\\AppData\\Roaming\\Python\\Python312\\site-packages\\ipykernel_launcher.py', '-f', 'C:\\Users\\NIST\\AppData\\Local\\Temp\\tmpk3i9reid.json', '--HistoryManager.hist_file=:memory:']
sys.argv is a list of strings
The first item (sys.argv[0]) is always the script file name
Following items are the arguments typed by the user
Example:
python main.py blue 20sys.argv becomes:
['main.py', 'blue', '20']Limitations of sys.argv
You must manually check if arguments exist
No automatic error messages
No automatic help (-h)
All arguments are strings, you must convert manually
Because of these limitations, professional Python CLI tools use argparse.
argparse (Recommended Standard Approach)argparse is a module in the Python Standard Library used to build user-friendly command-line interfaces.
It automatically:
validates arguments
converts types
prints help messages
shows clear errors
➤ Step 1: Create a parser
import argparse
parser = argparse.ArgumentParser(
description='This program prints a color HEX value'
)➤ Step 2: Add arguments
Example: expecting a -c or --color option.
parser.add_argument(
'-c', '--color',
metavar='color',
required=True,
help='the color to search for'
)_StoreAction(option_strings=['-c', '--color'], dest='color', nargs=None, const=None, default=None, type=None, choices=None, required=True, help='the color to search for', metavar='color')
➤ Step 3: Parse arguments
args = parser.parse_args()
print(args.color)Running:
python program.py -c redArgument missing → automatic error
python program.pyProduces:
usage: program.py [-h] -c color
program.py: error: the following arguments are required: -cYou get professional CLI errors for free.
You can restrict an option using choices=:
parser.add_argument(
'-c', '--color',
required=True,
choices={'red', 'yellow'},
help='the color to search for'
)Running with invalid input:
python program.py -c blueProduces:
argument -c/--color: invalid choice: 'blue' (choose from 'yellow', 'red')argparse supports:
parser.add_argument('filename')parser.add_argument('--age', type=int)parser.add_argument('--level', default='info')parser.add_argument('--debug', action='store_true')Some popular third-party CLI frameworks:
| Library | Notes |
|---|---|
| Click | Very popular, clean syntax, good for production CLIs |
| Typer | Built on Click; modern & easy; used for AI apps |
| Prompt Toolkit | For interactive shells (menus, inputs, autocompletion) |
But argparse is built-in and perfectly fine for most scripts.
In Python, a lambda function (also called an anonymous function) is a small, unnamed function defined using the lambda keyword.
It is useful when you need a quick function for a short period of time, especially when passing functions to other functions like map(), filter(), or sorted().
A lambda function looks like this:
lambda <arguments> : <expression>It can take any number of arguments
It must contain exactly one expression
It automatically returns the result of that expression
It cannot contain statements (like for, return, or print inside it)
Example:
lambda num: num * 2<function __main__.<lambda>(num)>
This function doubles the input value.
Although lambda functions have no name, you can store them in a variable:
multiply = lambda a, b: a * b
print(multiply(2, 2))4
This works exactly like a normal function:
def multiply(a, b):
return a * bBut lambda is shorter and lightweight.
Lambda functions can take multiple arguments:
sum_three = lambda x, y, z: x + y + z
print(sum_three(1, 2, 3)) # 66
The true power of lambda functions appears when used with built-in Python functions that expect another function as an argument.
✔ a. Using lambda with map()
map() applies a function to every element in a list.
numbers = [1, 2, 3, 4]
doubled = list(map(lambda n: n * 2, numbers))
print(doubled)[2, 4, 6, 8]
✔ b. Using lambda with filter()
filter() keeps only the elements that match a condition.
numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda n: n % 2 == 0, numbers))
print(evens)[2, 4, 6]
✔ c. Using lambda with sorted()
Sorting items by a custom key:
students = [('Alice', 25), ('Bob', 20), ('Chris', 22)]
sorted_by_age = sorted(students, key=lambda s: s[1])
print(sorted_by_age)[('Bob', 20), ('Chris', 22), ('Alice', 25)]
✔ d. Using lambda with reduce() (from functools)
reduce() combines elements into a single value.
from functools import reduce
result = reduce(lambda a, b: a + b, [1, 2, 3, 4])
print(result)10
Recursion is a programming technique where a function calls itself to solve a problem.
It is especially useful when a problem can be broken down into smaller subproblems of the same kind.
Recursion is often used in:
mathematical computations (factorials, Fibonacci numbers)
navigating nested structures (folders, trees)
divide-and-conquer algorithms (quick sort, merge sort)
processing hierarchical data (JSON, DOM trees)
Every recursive function needs two essential components:
✔ Base Case
A condition that stops the recursion.
✔ Recursive Case
The part where the function calls itself with a smaller problem.
Without a base case, the function would keep calling itself forever.
The factorial of a number multiplies the number by all positive integers below it:
3! = 3 × 2 × 1 = 6
5! = 5 × 4 × 3 × 2 × 1 = 120
Recursive definition of factorial:
Base case: 1! = 1
Recursive case: n! = n × (n − 1)!
Here’s the recursive implementation:
def factorial(n):
if n == 1:
return 1 # base case
return n * factorial(n - 1) # recursive case
print(factorial(3))
print(factorial(4))
print(factorial(5)) 6
24
120
If you mistakenly write:
factorial(n)instead of
factorial(n - 1)you create an infinite loop of recursive calls.
Python will stop after around 1000 recursive calls and raise:
RecursionError: maximum recursion depth exceededThis is Python’s safety limit to prevent your program from crashing.
Here is a simple recursive function to sum numbers from 1 to n:
def recursive_sum(n):
if n == 1:
return 1
return n + recursive_sum(n - 1)
print(recursive_sum(5))15
| Recursion | Iteration (loops) |
|---|---|
| Elegant and shorter | Often faster |
| Easier for tree-like problems | Uses less memory |
| Risk of recursion limit | No recursion limit |
Use recursion when the problem naturally fits a recursive structure.
Recursion shines in situations like:
✔ Problem can be divided into smaller identical problems
Example: factorial, Fibonacci, power, sum of digits
✔ Data is hierarchical
Example: folders inside folders, tree data structures
✔ Algorithms that depend on divide-and-conquer
Example: quicksort, mergesort, binary search
In Python, you can define a function inside another function.
These are called nested functions or inner functions.
Nested functions are important because they allow you to:
encapsulate helper logic that should not be accessed outside
keep your code organized
use closures (functions that remember values)
✔ Encapsulation
You can create small helper functions that only make sense inside a specific outer function.
✔ Avoid Naming Conflicts
Since the inner function is not visible outside, it won’t clash with function names elsewhere.
✔ Closures
Inner functions can remember and use values from the outer function’s scope (explained fully in the next chapter).
def talk(phrase):
def say(word):
print(word)
words = phrase.split(' ')
for word in words:
say(word)
talk('I am going to buy the milk')I
am
going
to
buy
the
milk
talk() is the outer function
say() is the inner function
say() is only available inside talk()
talk() splits the phrase and uses say() to print each word individually
Try calling say() outside the function — it will cause an error.
Inner functions can read variables from the outer function with no problem.
But to modify a variable from the outer function, you must declare it as nonlocal.
Example:
def count():
count = 0
def increment():
nonlocal count
count = count + 1
print(count)
increment()
count()1
Why nonlocal?
Without nonlocal, Python thinks count inside increment() is a new local variable, not the one in the outer function.
With nonlocal, we tell Python:
“Use the variable from the outer function.”
✔ When a helper function is only relevant inside another function
Example: text parsing, repeated calculations, input validation
✔ When using closures
Inner functions can capture values from the outer function.
This becomes powerful when returning functions.
✔ Creating function generators
Example: creating a function that returns another function with some pre-configured behavior.
Simple Use Case Example
def greeting(name):
def message():
print("Hello,", name)
message()
greeting("Prosper")Hello, Prosper
he inner function message() remembers name from its outer function.
A closure is one of the most powerful features in Python.
It happens when:
A function is defined inside another function (nested function)
The inner function captures variables from the outer function
The outer function returns the inner function
The returned inner function continues to remember the variables even after the outer function has finished running
In simple terms:
A closure is a function that remembers values from the environment it was created in.
Closures allow you to:
✔ Preserve state without using classes
You can create functions that keep their own memory.
✔ Build function factories
You can make functions that generate other customized functions.
✔ Encapsulate logic and data
Only the inner function has access to certain variables (automatic data hiding).
Here’s the counter code from the original text, now fully explained:
def counter():
count = 0 # local variable
def increment():
nonlocal count
count = count + 1
return count
return incrementExplanation:
count is defined in counter()
increment() is defined inside counter()
increment() uses the variable count
counter() returns the inner function but count does not disappear
Each call to increment() updates and returns the preserved value
Using the closure:
increment = counter()
print(increment())
print(increment())
print(increment())1
2
3
Even though counter() finished long ago, increment() still remembers count.
Visualizing What Happens
When calling increment = counter()
counter() runs
count = 0 is created
increment() is created
increment() forms a closure capturing count
counter() returns increment()
increment is now a function that carries its own “state”
Each call to counter() creates a new independent closure:
c1 = counter()
c2 = counter()
print(c1())
print(c1())
print(c2())
print(c2())1
2
1
2
Closures can be used to build function factories:
def multiply_by(n):
def multiply(x):
return x * n
return multiply
double = multiply_by(2)
triple = multiply_by(3)
print(double(5))
print(triple(5))10
15
Here:
n is captured by the inner function
double remembers n = 2
triple remembers n = 3
Closures are great when you want to:
✔ Keep state without using a class
Like counters, accumulators, or configuration holders.
✔ Create customized functions
E.g., discount calculators, mathematical functions, validators.
✔ Encapsulate and protect variables
Variables inside closures cannot be accessed directly from outside.
A decorator in Python is a special tool that lets you modify or enhance how a function behaves without changing the function’s original code.
Decorators are commonly used to:
Add logging
Measure execution time
Check permissions
Add caching
Validate inputs
…and much more.
A decorator is applied using the @ symbol right above the function you want to modify:
@logtime
def hello():
print("hello!")This means:
“Wrap the function hello() with the decorator logtime.”
So every time you call hello(), Python actually calls logtime(hello).
A decorator is a function that:
Takes another function as input
Defines an inner function (the wrapper) that adds extra behavior
Calls the original function inside the wrapper
Returns the wrapper
Example:
def logtime(func):
def wrapper():
print("Before calling function...")
value = func()
print("After calling function...")
return value
return wrapperNow you can apply it:
@logtime
def greet():
print("Hello!")Calling:
greet()Before calling function...
Hello!
After calling function...
The original function runs, but with extra steps added before and after.
Because they help you separate reusable behavior from your main logic.
For example, if you want multiple functions to log their execution time, you can write a single decorator instead of copying code into every function.
If your original function accepts parameters, your wrapper must be flexible too:
def debug(func):
def wrapper(*args, **kwargs):
print("Function called with:", args, kwargs)
return func(*args, **kwargs)
return wrapperUsage:
@debug
def add(a, b):
return a + b
print(add(3, 4))Function called with: (3, 4) {}
7
Decorators rely on closures (from previous chapter), because the inner wrapper function remembers the original function reference even after the decorator finishes running.
Docstrings (documentation strings) are special strings in Python used to describe what a function, class, method, or module does.
Good documentation is important because:
It helps other people understand your code.
It helps YOU in the future understand what you were thinking.
It allows tools to automatically extract the documentation.
It keeps your code clean and professional.
Comments vs Docstrings
Comments use #:
# This is a comment
num = 1 # This is another commentDocstrings are triple-quoted strings placed inside functions, classes, or modules.
Docstrings follow conventions, so documentation tools can extract and use them.
Function Docstrings
A docstring is the first string inside a function, written with triple quotes:
def increment(n):
"""Increment a number by 1."""
return n + 1This docstring tells anyone using the function what it does.
Class and Method Docstrings
You can document classes and their methods the same way:
class Dog:
"""A class representing a dog."""
def __init__(self, name, age):
"""Initialize a new Dog with name and age."""
self.name = name
self.age = age
def bark(self):
"""Let the dog bark."""
print("WOF!")Module Docstrings
A module (a Python file) can also start with a docstring:
"""
Dog module
This module defines the Dog class and related utilities.
"""'\nDog module\n\nThis module defines the Dog class and related utilities.\n'
Then your class definition follows.
Multiline Docstrings
Docstrings can span multiple lines:
def increment(n):
"""
Increment
a number
by 1.
"""
return n + 1Using help() to Read Docstrings
Python uses docstrings when you call the built-in help() function:
help(increment)Help on function increment in module __main__:
increment(n)
Increment
a number
by 1.
This is extremely useful when exploring unfamiliar code.
Docstring Formats / Styles
There are several standard formats:
Google Style (very popular)
NumPy / SciPy Style
reStructuredText (Sphinx)
Google Style example:
def add(a, b):
"""
Add two numbers.
Args:
a (int): First number.
b (int): Second number.
Returns:
int: The sum of a and b.
"""
return a + bThese standards allow tools like Sphinx or pdoc to automatically generate full documentation websites from your code.
Introspection means examining objects at runtime, in other words, asking Python questions about functions, objects, variables, and modules while the program is running.
Python is highly introspective, which makes it very powerful for debugging, learning, and metaprogramming.
help()The built-in help() function displays the documentation (from docstrings):
def increment(n):
return n + 1
print(increment)
# <function increment at 0x7f420e2973a0><function increment at 0x000001E1BA9E32E0>
This is useful for quick reference.
print() to inspect objectsIf you print a function, Python shows information about its memory location:
def increment(n):
return n + 1
print(increment)
# <function increment at 0x7f420e2973a0><function increment at 0x000001E1BA9E27A0>
Same for objects:
class Dog:
def bark(self):
print("WOF!")
Max = Dog()
print(Max)
# <__main__.Dog object at 0x7f42099d3340><__main__.Dog object at 0x000001E1BA933A70>
type()The type() function shows the type/class of any object:
print(type(increment))
print(type(Max))
print(type(1))
print(type('test'))<class 'function'>
<class '__main__.Dog'>
<class 'int'>
<class 'str'>
dir()dir() lists all attributes and methods an object has:
print(dir(Max))['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'bark']
id()id() shows the memory address (identity) of an object:
print(id(Max))
print(id(1))2069009480304
140723354798520
You can use this to check if two variables reference the same object.
inspect Module (Advanced Introspection)The inspect module gives much more detailed information, such as:
getting the source code of a function
listing parameters
seeing which module something came from
checking if a function is a coroutine, generator, etc.
Documentation: https://docs.python.org/3/library/inspect.html
Python is a dynamically typed language, meaning that variables and functions do not require a declared type. You can write:
def increment(n):
return n + 1and Python will infer the type at runtime.
However, as programs grow larger, it becomes useful to document expected types. This is where annotations come in.
Annotations let you optionally specify:
the type of each parameter
the type of the return value
Example:
def increment(n: int) -> int:
return n + 1Here:
n: int means we expect n to be an integer
-> int means the function should return an integer
Python does not enforce these rules—they are only hints.
Variables can also be annotated:
count: int = 0
name: str = "Alice"
my_list: list[int] = [1, 2, 3]These annotations are purely informational.
Even though Python ignores them at runtime, annotations help:
✔ Static type checking
Tools like mypy, pyright, or IDEs (VS Code, PyCharm) can check your code before you run it, helping catch errors early.
Example:
mypy my_program.py✔ Better auto-completion
Editors can provide smarter suggestions when types are known.
✔ Clearer code
Types make functions easier to understand, especially in big projects.
def greet(name: str, age: int) -> str:
return f"Hello {name}, you are {age} years old."Python also supports more complex annotations:
from typing import Optional, Union
def parse_age(age: Optional[int]) -> Union[int, None]:
return ageType annotations are optional, not enforced by Python.
They improve readability and help external tools catch bugs early.
They are especially useful for large codebases and refactoring.
Programs often encounter unexpected situations: missing files, invalid user input, network errors, division by zero, and more.
To prevent your program from crashing, Python provides a structured way to detect and handle errors using exceptions.
The try Block
You wrap code that might cause an error inside a try: block:
try:
# code that may raise an errorIf no error occurs, the program continues normally.
The except Block
If the code inside try: raises an exception, Python jumps into the appropriate except: block:
try:
# some lines of code
except ERROR1:
# handle ERROR1
except ERROR2:
# handle ERROR2Each except block handles a specific type of exception.
Catching All Exceptions
You can catch any exception using a bare except::
try:
# risky code
except ERROR1:
# handle ERROR1
except:
# handle all other exceptionsThis should be used sparingly, because it hides all error types.
The else Block
The else: block runs only if no exception happened in the try: block:
try:
# code
except ERROR1:
# handle ERROR1
except ERROR2:
# handle ERROR2
else:
# runs only when no exceptions occurThis is great for code that should execute only if everything went well.
The finally Block
The finally: block always runs, whether an exception occurred or not:
try:
# code
except ERROR1:
# handler
else:
# executed if no errors
finally:
# always executedTypical use cases include:
closing files
closing database connections
cleaning resources
Different operations raise different exceptions:
| Error | When It Happens |
|---|---|
ZeroDivisionError |
dividing by zero |
TypeError |
wrong data type used |
ValueError |
invalid value passed |
EOFError |
reading file input past the end |
IndexError |
using an invalid list index |
Example:
reult = 2 / 0 # this gives an errorThe program stops and does not execute further lines unless handled.
Handling an Exception
try:
result = 2 / 0
except ZeroDivisionError:
print('Cannot divide by zero!')
finally:
result = 1
print(result)Cannot divide by zero!
1
The program recovers gracefully.
Raising Your Own Exceptions
Use the raise keyword:
raise Exception('An error occurred!')Handling it:
try:
raise Exception('An error occurred!')
except Exception as error:
print(error)An error occurred!
You can define your own exception classes by extending Python’s built-in Exception class:
class DogNotFoundException(Exception):
passUsing it:
try:
raise DogNotFoundException()
except DogNotFoundException:
print('Dog not found!')Dog not found!
Custom exceptions help you create meaningful, domain-specific error handling logic.
with Statement in PythonMany operations in programming require setting something up, doing some work, and then cleaning it up afterward.
For example:
opening and closing a file
acquiring and releasing a lock
connecting and disconnecting from a database
If you forget the cleanup step, your program may leak resources or behave incorrectly.
Python provides the with statement to manage these tasks cleanly and safely.
It ensures that necessary cleanup happens automatically, even if errors occur.
with: Manual Resource HandlingWhen opening files, for example, you must explicitly remember to close the file:
filename = 'test.txt'
try:
file = open(filename, 'r')
content = file.read()
print(content)
finally:
file.close()I am Prosper, and this is testing
the text you see is from tezt.txt
Using try...finally ensures the file closes even if an exception occurs.
But this is verbose and easy to forget.
with: Automatic CleanupThe with statement simplifies the pattern:
filename = 'test.txt'
with open(filename, 'r') as file:
content = file.read()
print(content)I am Prosper, and this is testing
the text you see is from tezt.txt
Here’s what happens:
Python opens the file
The code inside the with block runs
When the block ends, file.close() is called automatically, even if an error occurs
This makes the code safer and more readable.
The object after with must implement two special methods:
__enter__() — runs at the start (sets up the resource)
__exit__() — runs at the end (cleans up the resource)
Objects that implement these methods are called context managers.
open() returns a file object that is a context manager.
with Statement Works with Many ResourcesAlthough it’s commonly used with files, with can manage any resource that needs setup and teardown. Examples include:
managing locks (threading.Lock())
opening network connections
database sessions
temporary file operations
redirecting output
handling transactions
Example with a lock:
from threading import Lock
lock = Lock()
with lock:
# safe critical section
print("Locked safely")Locked safely
The lock is always released automatically.
withCleaner code
Less error-prone
Automatic resource management
Built-in safety for exceptions
Easier to read and understand
Whenever you use resources that require cleanup, prefer the with statement.
pipPython comes with a powerful standard library that gives us many built-in tools. However, real-world projects usually need more specialized functionality, like data analysis, web development, machine learning, or working with APIs.
To bridge this gap, Python developers around the world create 3rd party packages. These packages are shared publicly so everyone can benefit from them.
All these packages are stored in one place: The Python Package Index (PyPI) https://pypi.org
At the time of writing, PyPI hosts over 270,000 packages, and you can install any of them using pip, Python’s package manager.
pip?pip stands for Pip Installs Packages.
It is installed automatically when you install Python (as long as you used the official installer).
You can check if pip is installed by running:
pip --versionor
python -m pip --versionTo install a package from PyPI, use:
pip install <package>For example, installing the popular HTTP library requests:
pip install requestsAfter installation, you can import and use it in any Python script:
import requestspip install gives errorsSometimes you might have multiple Python versions installed. If so, run:
python -m pip install <package>This ensures you’re using the correct Python interpreter.
Packages are installed globally in a folder like:
Windows:
C:\Users\<username>\AppData\Local\Programs\Python\Python39\Lib\site-packages
macOS (Python 3.9 example):
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages
Linux:
/usr/lib/python3/dist-packages
or inside your user folder.
To update a package to its latest version:
pip install -U <package>Example:
pip install -U pandasSometimes you need an older or exact version:
pip install <package>==<version>Example:
pip install numpy==1.24.2To remove a package completely:
pip uninstall <package>Example:
pip uninstall requestsTo see the installed version, author, documentation URL, and installation path:
pip show <package>Example:
pip show requestsThis is especially useful when debugging version issues.
You can see all installed libraries with:
pip listPython uses pip to install 3rd-party packages.
Packages come from PyPI, a massive open-source repository.
You can install, upgrade, remove, or inspect packages easily using the commands shown in this c.
Installed packages can be imported in any Python script.
In Python, a list comprehension is a concise and elegant way to create new lists.
Instead of writing multiple lines using loops, you can generate lists in a single, readable line.
Basic Example
Suppose you have a list:
numbers = [1, 2, 3, 4, 5]You can create a new list where each number is squared using a list comprehension:
numbers_power_2 = [n**2 for n in numbers]This does the same thing as a for loop but in a shorter, clearer way.
Why Use List Comprehensions?
List comprehensions are:
Shorter than loops
More readable when the logic fits on one line
Often faster than using loops
More Pythonic
Equivalent Using a Loop
Here is the same operation written as a normal loop:
numbers_power_2 = []
for n in numbers:
numbers_power_2.append(n**2)Both work, but the list comprehension is cleaner.
Equivalent Using map() and lambda
You can also achieve the same using map():
numbers_power_2 = list(map(lambda n: n**2, numbers))This works but is usually less readable than a list comprehension.
General Syntax
[expression for item in iterable if condition]Where:
expression → what to put in the new list
item → variable used inside the comprehension
iterable → existing list, string, or range
condition → optional filter
Polymorphism is an important concept in object-oriented programming (OOP).
The word means “many forms”, and in Python it allows the same function or method name to work on different types of objects.
In simple terms:
Different objects can respond to the same method name in different ways.
We can define two different classes, and each class can have a method with the same name:
class Dog:
def eat(self):
print('Eating dog food')
class Cat:
def eat(self):
print('Eating cat food')Now, when we create objects:
animal1 = Dog()
animal2 = Cat()
animal1.eat() # Eating dog food
animal2.eat() # Eating cat foodEating dog food
Eating cat food
Even though both objects have an eat() method, each one behaves differently.
This allows us to write code that doesn’t need to care whether the object is a Dog or a Cat, it just calls .eat() and Python handles the rest.
Why Polymorphism Matters
Polymorphism helps you:
Write more flexible and extensible code
Avoid large if…else or switch statements
Work at a higher level of abstraction
Design functions that works with many types
A Common Real-World Example: len()
Python’s built-in len() function is polymorphic.
len("hello")
len([1, 2, 3])
len({"a": 1})1
len() works on strings, lists, dictionaries, and more — even though they are completely different types.
This is also polymorphism.
In OOP, polymorphism often appears when subclasses override the same parent method.
Example:
class Animal:
def sound(self):
print("Some sound")
class Dog(Animal):
def sound(self):
print("Woof!")
class Cat(Animal):
def sound(self):
print("Meow!")for animal in [Dog(), Cat(), Animal()]:
animal.sound()Woof!
Meow!
Some sound
Each object responds in its own way, that is polymorphism.
Summary
Polymorphism allows:
Same method name, different behaviors
Code that works with different objects interchangeably
Cleaner and more reusable design
It is one of the core features of object-oriented programming in Python.
Python allows you to redefine how operators like +, >, ==, *, and many others behave when used on objects of your own classes.
This powerful feature is called operator overloading.
Operator overloading lets your custom objects behave more like built-in types (integers, strings, lists, etc.).
Basic Example
Consider a simple Dog class:
class Dog:
def __init__(self, name, age):
self.name = name
self.age = ageLet’s create two Dog objects:
Max = Dog('Max', 8)
syd = Dog('Syd', 7)Normally, comparing them does not work:
print(Max > syd) #TypeErrorYou must tell Python how to compare them. We can overload the greater than operator (>) by implementing __gt__():
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def __gt__(self, other):
return self.age > other.ageNow this works:
print(Max > syd) # TrueWe have customized how > behaves for the Dog class.
Python calls special methods (sometimes called “dunder” methods — double underscore) when you use operators.
Here are the most common comparison overloads:
| Operator | Method | Meaning |
|---|---|---|
== |
__eq__(self, other) |
Equal to |
!= |
__ne__(self, other) |
Not equal |
< |
__lt__(self, other) |
Less than |
<= |
__le__(self, other) |
Less or equal |
> |
__gt__(self, other) |
Greater than |
>= |
__ge__(self, other) |
Greater or equal |
You can also make your objects work with arithmetic operators:
| Operator | Method |
|---|---|
+ |
__add__(self, other) |
- |
__sub__(self, other) |
* |
__mul__(self, other) |
/ |
__truediv__(self, other) |
// |
__floordiv__(self, other) |
% |
__mod__(self, other) |
** |
__pow__(self, other) |
Example: Add the ages of two dogs
def __add__(self, other):
return self.age + other.ageNow:
print(Max + syd) # 15Bitwise and Shift Operators
Your classes can also respond to operators like:
>>→ rshift()
<< → lshift()
&→ and()
| → or()
^ → xor()
These are less common unless you are working with binary or numeric data structures.
It helps you:
Make your custom objects behave naturally with operator
Write cleaner and more intuitive code
Create classes that feel like built-in Python types
Encapsulate logic inside the class instead of writing separate functions everywhere
Examples in real-world libraries:
datetime uses + to add timedeltas
NumPy overloads *, +, -, etc. for arrays
Django models overload operators for database queries
Operator overloading lets you:
Redefine what operators mean for your objects
Implement comparison, arithmetic, logical, and bitwise behavior
Make class instances more powerful, readable, and pythonic
When building Python applications, you will often encounter a situation where:
One project needs version 1 of a package
Another project needs version 2 of the same package
If all your packages are installed globally, these projects will conflict.
To avoid this problem, Python provides virtual environments, isolated “containers” that hold their own independent dependencies.
This ensures each project has its own clean and controlled environment.
A virtual environment:
Acts like a mini Python installation inside your project
Has its own site-packages directory
Keeps dependencies separated from your system Python
Prevents version conflicts between projects
Python comes with a built-in tool called venv.
Inside your project folder, run:
python -m venv .venv This creates a hidden directory named .venv that contains the entire environment.
You will see folders inside it like:
bin/ (or Scripts/ on Windows)
lib/
pyvenv.cfg
Once created, activate it:
On macOS & Linux
source .venv/bin/activate On Fish shell
source .venv/bin/activate.fish On Windows (PowerShell)
.\.venv\Scripts\Activate.ps1 After activation, your terminal prompt usually changes.
For example:
Before:
➜ myproject After:
(.venv) ➜ myproject This indicates you are inside the virtual environment.
Once activated, pip automatically installs packages into this environment only, not globally.
Example:
pip install requests The installed package stays inside .venv, fully isolated from your system.
Your global Python remains clean and untouched.
Deactivating a Virtual Environment
To exit the virtual environment, simply run:
deactivate Your terminal returns to normal, and you’re back to using the system Python.
Using virtual environments solves many problems:
✔ Avoids version conflicts
✔ Keeps projects lightweight and reproducible
✔ Makes collaboration easier
✔ Matches production environments more reliably
✔ Keeps your system Python clean
Almost every professional Python developer uses virtual environments for every project.
Besides venv, other environment managers exist:
pipenv — combines virtualenv + dependency management
poetry — powerful dependency and project manager
conda — environment and package manager (used in data science)
But venv remains the simplest and is included in Python by default.
Python is a powerful, flexible, and expressive programming language that continues to grow in popularity across fields such as web development, data science, automation, machine learning, and more.
Throughout this training, we covered a wide range of topics, from basic syntax to advanced concepts, designed to give you a strong foundation and the confidence to build real applications. You learned how Python handles variables, loops, functions, classes, exceptions, modules, virtual environments, and many more features that make it one of the most enjoyable languages to work with.
As you continue your Python journey, remember:
Practice is essential. The more you code, the more the concepts will become second nature.
Read other people’s code. It helps you discover new patterns and techniques.
Explore Python’s ecosystem. With hundreds of thousands of packages available, Python becomes even more powerful.
Continue learning. The language evolves, and so will your skills.
With this foundation, you are now well-prepared to move into more advanced areas such as data analysis, machine learning, automation, web development, or any other Python-driven field.
Congratulations on completing the course, and keep building!
2.3 Comments in Python
Sometimes, you’ll want to write notes in your code to explain what it does. Python lets you do this with comments, lines that Python ignores when running your program.
Single-Line Comments
Use the
#symbol to write a comment. Everything after#is ignored.Show/Hide Code
Comments are great for:
Explaining what your code does
Leaving reminders for yourself or others
Temporarily disabling lines of code
Example:
Show/Hide Code
Multi-line Comments (Docstring Style)
Python doesn’t have a special syntax for multi-line comments,
but you can use triple quotes (
'''or""") as a common workaround for large notes:Show/Hide Code