mirror of
https://github.com/blshaer/python-by-example.git
synced 2026-03-27 23:29:25 +01:00
Initial commit: Python by Example structure
This commit is contained in:
commit
10fc3566bb
223
01_basics/01_print.py
Normal file
223
01_basics/01_print.py
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_print.py
|
||||||
|
Topic: The print() Function in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates the print() function, which is used to display output
|
||||||
|
to the console. It's one of the most fundamental functions in Python and
|
||||||
|
essential for debugging and displaying information.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Basic printing
|
||||||
|
- Multiple arguments
|
||||||
|
- Separators and end characters
|
||||||
|
- Formatted strings (f-strings)
|
||||||
|
- Escape characters
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Simple Output
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# The most basic use of print()
|
||||||
|
|
||||||
|
print("--- Simple Output ---")
|
||||||
|
|
||||||
|
print("Hello, World!")
|
||||||
|
print("Welcome to Python!")
|
||||||
|
print("Learning is fun!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Printing Different Data Types
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# print() can output any data type
|
||||||
|
|
||||||
|
print("\n--- Different Data Types ---")
|
||||||
|
|
||||||
|
print(42) # Integer
|
||||||
|
print(3.14159) # Float
|
||||||
|
print(True) # Boolean
|
||||||
|
print(None) # NoneType
|
||||||
|
print([1, 2, 3]) # List
|
||||||
|
print({"a": 1}) # Dictionary
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Printing Multiple Items
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Pass multiple arguments separated by commas
|
||||||
|
|
||||||
|
print("\n--- Multiple Items ---")
|
||||||
|
|
||||||
|
print("Hello", "World")
|
||||||
|
print("Python", "is", "awesome")
|
||||||
|
print("Name:", "Baraa", "| Age:", 25)
|
||||||
|
print(1, 2, 3, 4, 5)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. The sep Parameter
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# sep defines what goes between multiple items (default is space)
|
||||||
|
|
||||||
|
print("\n--- Separator Parameter ---")
|
||||||
|
|
||||||
|
print("Python", "Java", "C++", sep=", ")
|
||||||
|
print("2025", "01", "15", sep="-") # Date format
|
||||||
|
print("192", "168", "1", "1", sep=".") # IP address
|
||||||
|
print("apple", "banana", "cherry", sep=" | ")
|
||||||
|
print("a", "b", "c", sep="") # No separator
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. The end Parameter
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# end defines what goes at the end (default is newline \n)
|
||||||
|
|
||||||
|
print("\n--- End Parameter ---")
|
||||||
|
|
||||||
|
print("Loading", end="")
|
||||||
|
print("...", end="")
|
||||||
|
print(" Done!")
|
||||||
|
|
||||||
|
print("First line", end=" --> ")
|
||||||
|
print("Second line")
|
||||||
|
|
||||||
|
# Creating a progress bar effect
|
||||||
|
print("\nProgress: ", end="")
|
||||||
|
for i in range(5):
|
||||||
|
print("█", end="")
|
||||||
|
print(" Complete!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Variables in Print
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Print variable values
|
||||||
|
|
||||||
|
print("\n--- Variables ---")
|
||||||
|
|
||||||
|
name = "Baraa"
|
||||||
|
age = 25
|
||||||
|
city = "Gaza"
|
||||||
|
is_student = True
|
||||||
|
|
||||||
|
print("Name:", name)
|
||||||
|
print("Age:", age)
|
||||||
|
print("City:", city)
|
||||||
|
print("Student:", is_student)
|
||||||
|
|
||||||
|
# Print with variable calculations
|
||||||
|
x = 10
|
||||||
|
y = 5
|
||||||
|
print("Sum:", x + y)
|
||||||
|
print("Product:", x * y)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. String Formatting - f-strings (Recommended)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Modern way to embed variables in strings (Python 3.6+)
|
||||||
|
|
||||||
|
print("\n--- f-strings (Formatted String Literals) ---")
|
||||||
|
|
||||||
|
name = "Baraa"
|
||||||
|
age = 25
|
||||||
|
height = 1.75
|
||||||
|
|
||||||
|
print(f"My name is {name}")
|
||||||
|
print(f"I am {age} years old")
|
||||||
|
print(f"Next year I'll be {age + 1}")
|
||||||
|
|
||||||
|
# Formatting numbers
|
||||||
|
pi = 3.14159265359
|
||||||
|
print(f"Pi to 2 decimals: {pi:.2f}")
|
||||||
|
print(f"Pi to 4 decimals: {pi:.4f}")
|
||||||
|
|
||||||
|
# Padding and alignment
|
||||||
|
print(f"{'Left':<10}|{'Center':^10}|{'Right':>10}")
|
||||||
|
print(f"{1:<10}|{2:^10}|{3:>10}")
|
||||||
|
|
||||||
|
# Currency formatting
|
||||||
|
price = 1234.567
|
||||||
|
print(f"Price: ${price:,.2f}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. String Formatting - Other Methods
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Alternative formatting methods
|
||||||
|
|
||||||
|
print("\n--- Other Formatting Methods ---")
|
||||||
|
|
||||||
|
# .format() method
|
||||||
|
name = "Alice"
|
||||||
|
age = 30
|
||||||
|
print("Hello, {}! You are {} years old.".format(name, age))
|
||||||
|
print("Hello, {0}! You are {1} years old.".format(name, age))
|
||||||
|
print("Hello, {n}! You are {a} years old.".format(n=name, a=age))
|
||||||
|
|
||||||
|
# % operator (older style)
|
||||||
|
print("Hello, %s! You are %d years old." % (name, age))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Escape Characters
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Special characters using backslash \
|
||||||
|
|
||||||
|
print("\n--- Escape Characters ---")
|
||||||
|
|
||||||
|
print("Line 1\nLine 2\nLine 3") # \n = newline
|
||||||
|
print("Column1\tColumn2\tColumn3") # \t = tab
|
||||||
|
print("She said: \"Hello!\"") # \" = quote
|
||||||
|
print('It\'s a beautiful day') # \' = apostrophe
|
||||||
|
print("Path: C:\\Users\\Documents") # \\ = backslash
|
||||||
|
print("Bell sound: \a") # \a = bell (may not work)
|
||||||
|
|
||||||
|
# Raw strings - ignore escape characters
|
||||||
|
print("\nRaw string:")
|
||||||
|
print(r"C:\Users\Baraa\Desktop") # r prefix for raw string
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Multi-line Printing
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Multi-line Strings ---")
|
||||||
|
|
||||||
|
# Using triple quotes
|
||||||
|
message = """
|
||||||
|
This is a multi-line message.
|
||||||
|
It spans across several lines.
|
||||||
|
Very useful for long text!
|
||||||
|
"""
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
# ASCII art example
|
||||||
|
print("""
|
||||||
|
╔═══════════════════════════╗
|
||||||
|
║ Welcome to Python! ║
|
||||||
|
║ Let's learn together! ║
|
||||||
|
╚═══════════════════════════╝
|
||||||
|
""")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 11. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Receipt example
|
||||||
|
print("\n========== RECEIPT ==========")
|
||||||
|
item1, price1 = "Coffee", 4.99
|
||||||
|
item2, price2 = "Sandwich", 8.50
|
||||||
|
item3, price3 = "Cookie", 2.25
|
||||||
|
total = price1 + price2 + price3
|
||||||
|
|
||||||
|
print(f"{item1:.<20}${price1:.2f}")
|
||||||
|
print(f"{item2:.<20}${price2:.2f}")
|
||||||
|
print(f"{item3:.<20}${price3:.2f}")
|
||||||
|
print("=" * 30)
|
||||||
|
print(f"{'TOTAL':.<20}${total:.2f}")
|
||||||
|
|
||||||
|
# Table example
|
||||||
|
print("\n| Name | Age | City |")
|
||||||
|
print("|----------|-----|------------|")
|
||||||
|
print(f"| {'Alice':<8} | {25:<3} | {'New York':<10} |")
|
||||||
|
print(f"| {'Bob':<8} | {30:<3} | {'London':<10} |")
|
||||||
|
print(f"| {'Charlie':<8} | {35:<3} | {'Tokyo':<10} |")
|
||||||
343
01_basics/02_comments.py
Normal file
343
01_basics/02_comments.py
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_comments.py
|
||||||
|
Topic: Comments in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates how to write comments in Python. Comments are essential
|
||||||
|
for code documentation, making your code readable, and helping others (and
|
||||||
|
your future self) understand what the code does.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Single-line comments
|
||||||
|
- Multi-line comments
|
||||||
|
- Docstrings
|
||||||
|
- Best practices for commenting
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Single-Line Comments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use the hash symbol (#) to create single-line comments
|
||||||
|
|
||||||
|
print("--- Single-Line Comments ---")
|
||||||
|
|
||||||
|
# This is a single-line comment
|
||||||
|
print("Hello, World!") # This is an inline comment
|
||||||
|
|
||||||
|
# Comments can explain complex logic
|
||||||
|
x = 10 # Store the initial value
|
||||||
|
y = 20 # Store the second value
|
||||||
|
result = x + y # Calculate the sum
|
||||||
|
|
||||||
|
# Comments can be used to organize sections of code
|
||||||
|
# ---- Configuration ----
|
||||||
|
debug_mode = True
|
||||||
|
max_retries = 3
|
||||||
|
|
||||||
|
# ---- Main Logic ----
|
||||||
|
print(f"Debug mode is: {debug_mode}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Multi-Line Comments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Python doesn't have true multi-line comments, but there are two approaches
|
||||||
|
|
||||||
|
print("\n--- Multi-Line Comments ---")
|
||||||
|
|
||||||
|
# Approach 1: Multiple single-line comments (preferred)
|
||||||
|
# This is a multi-line comment
|
||||||
|
# that spans across multiple lines.
|
||||||
|
# Each line starts with a hash symbol.
|
||||||
|
|
||||||
|
# Approach 2: Triple-quoted strings (not recommended for comments)
|
||||||
|
# These are actually string literals, not true comments
|
||||||
|
"""
|
||||||
|
This looks like a multi-line comment, but it's actually
|
||||||
|
a string literal. If not assigned to a variable,
|
||||||
|
Python will ignore it, but it's still stored in memory.
|
||||||
|
Use this approach only for docstrings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Multi-line comments explained above!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Docstrings (Documentation Strings)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Docstrings are special strings used to document modules, functions, classes
|
||||||
|
|
||||||
|
print("\n--- Docstrings ---")
|
||||||
|
|
||||||
|
|
||||||
|
def greet(name):
|
||||||
|
"""
|
||||||
|
Greet a person by their name.
|
||||||
|
|
||||||
|
This function takes a person's name and prints a friendly
|
||||||
|
greeting message to the console.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The name of the person to greet.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: A greeting message.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> greet("Baraa")
|
||||||
|
'Hello, Baraa! Welcome!'
|
||||||
|
"""
|
||||||
|
return f"Hello, {name}! Welcome!"
|
||||||
|
|
||||||
|
|
||||||
|
# Access the docstring
|
||||||
|
print(greet("Baraa"))
|
||||||
|
print(f"Function docstring: {greet.__doc__[:50]}...")
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_area(length, width):
|
||||||
|
"""
|
||||||
|
Calculate the area of a rectangle.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
length (float): The length of the rectangle.
|
||||||
|
width (float): The width of the rectangle.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: The area of the rectangle.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If length or width is negative.
|
||||||
|
"""
|
||||||
|
if length < 0 or width < 0:
|
||||||
|
raise ValueError("Length and width must be non-negative")
|
||||||
|
return length * width
|
||||||
|
|
||||||
|
|
||||||
|
print(f"Area: {calculate_area(5, 3)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Class Docstrings
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Class Docstrings ---")
|
||||||
|
|
||||||
|
|
||||||
|
class Rectangle:
|
||||||
|
"""
|
||||||
|
A class to represent a rectangle.
|
||||||
|
|
||||||
|
This class provides methods to calculate the area and perimeter
|
||||||
|
of a rectangle, as well as other utility methods.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
length (float): The length of the rectangle.
|
||||||
|
width (float): The width of the rectangle.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
area(): Returns the area of the rectangle.
|
||||||
|
perimeter(): Returns the perimeter of the rectangle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, length, width):
|
||||||
|
"""
|
||||||
|
Initialize a Rectangle instance.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
length (float): The length of the rectangle.
|
||||||
|
width (float): The width of the rectangle.
|
||||||
|
"""
|
||||||
|
self.length = length
|
||||||
|
self.width = width
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
"""Calculate and return the area of the rectangle."""
|
||||||
|
return self.length * self.width
|
||||||
|
|
||||||
|
def perimeter(self):
|
||||||
|
"""Calculate and return the perimeter of the rectangle."""
|
||||||
|
return 2 * (self.length + self.width)
|
||||||
|
|
||||||
|
|
||||||
|
rect = Rectangle(10, 5)
|
||||||
|
print(f"Rectangle area: {rect.area()}")
|
||||||
|
print(f"Rectangle perimeter: {rect.perimeter()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Comment Best Practices
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Comment Best Practices ---")
|
||||||
|
|
||||||
|
# ✅ GOOD: Explain WHY, not WHAT
|
||||||
|
# Calculate discount for loyalty members (15% off for 2+ years)
|
||||||
|
years_as_member = 3
|
||||||
|
discount = 0.15 if years_as_member >= 2 else 0
|
||||||
|
|
||||||
|
# ❌ BAD: Explains what the code obviously does
|
||||||
|
# x = x + 1 # Add 1 to x
|
||||||
|
|
||||||
|
# ✅ GOOD: Document complex algorithms
|
||||||
|
# Using binary search for O(log n) time complexity
|
||||||
|
# instead of linear search O(n) for performance
|
||||||
|
|
||||||
|
# ✅ GOOD: Mark TODO items for future work
|
||||||
|
# TODO: Implement caching for better performance
|
||||||
|
# FIXME: Handle edge case when input is empty
|
||||||
|
# NOTE: This function requires Python 3.8+
|
||||||
|
# HACK: Temporary workaround for API limitation
|
||||||
|
|
||||||
|
# ✅ GOOD: Use comments for code sections
|
||||||
|
# ============================================
|
||||||
|
# DATABASE CONNECTION SETUP
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# USER AUTHENTICATION
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
print("Best practices demonstrated!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Commenting Out Code
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Commenting Out Code ---")
|
||||||
|
|
||||||
|
# You can temporarily disable code by commenting it out
|
||||||
|
# print("This line won't execute")
|
||||||
|
# old_function()
|
||||||
|
# deprecated_code()
|
||||||
|
|
||||||
|
# Useful during debugging
|
||||||
|
value = 100
|
||||||
|
# value = 200 # Uncomment to test with different value
|
||||||
|
print(f"Current value: {value}")
|
||||||
|
|
||||||
|
# Multiple lines can be commented at once
|
||||||
|
# line_1 = "first"
|
||||||
|
# line_2 = "second"
|
||||||
|
# line_3 = "third"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Module-Level Docstrings
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Module-Level Docstrings ---")
|
||||||
|
|
||||||
|
# At the top of a Python file, you can include a module docstring
|
||||||
|
# (like the one at the top of this file)
|
||||||
|
|
||||||
|
# Access a module's docstring
|
||||||
|
print("This module's docstring starts with:")
|
||||||
|
print(__doc__[:100] + "...")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Type Hints with Comments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Type Hints with Comments ---")
|
||||||
|
|
||||||
|
|
||||||
|
def process_data(
|
||||||
|
data: list, # The input data to process
|
||||||
|
threshold: float = 0.5, # Minimum value to include (default: 0.5)
|
||||||
|
verbose: bool = False # Print progress if True
|
||||||
|
) -> list:
|
||||||
|
"""
|
||||||
|
Process a list of numerical data.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: List of numbers to process.
|
||||||
|
threshold: Minimum value to include in results.
|
||||||
|
verbose: Whether to print processing details.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Filtered list containing only values above threshold.
|
||||||
|
"""
|
||||||
|
if verbose:
|
||||||
|
print(f"Processing {len(data)} items...")
|
||||||
|
return [x for x in data if x > threshold]
|
||||||
|
|
||||||
|
|
||||||
|
result = process_data([0.1, 0.6, 0.3, 0.8, 0.4], threshold=0.5)
|
||||||
|
print(f"Filtered data: {result}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Practical Example: Well-Commented Code
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example ---")
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_shipping_cost(weight, distance, express=False):
|
||||||
|
"""
|
||||||
|
Calculate the shipping cost based on weight and distance.
|
||||||
|
|
||||||
|
The cost is calculated using a base rate plus additional charges
|
||||||
|
for weight and distance. Express shipping doubles the final cost.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
weight (float): Package weight in kilograms.
|
||||||
|
distance (float): Shipping distance in kilometers.
|
||||||
|
express (bool): Whether to use express shipping.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: Total shipping cost in dollars.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> calculate_shipping_cost(2.5, 100)
|
||||||
|
17.0
|
||||||
|
>>> calculate_shipping_cost(2.5, 100, express=True)
|
||||||
|
34.0
|
||||||
|
"""
|
||||||
|
# Base shipping rate
|
||||||
|
BASE_RATE = 5.00
|
||||||
|
|
||||||
|
# Rate per kilogram of weight
|
||||||
|
WEIGHT_RATE = 2.00
|
||||||
|
|
||||||
|
# Rate per 100 kilometers
|
||||||
|
DISTANCE_RATE = 0.05
|
||||||
|
|
||||||
|
# Calculate component costs
|
||||||
|
weight_cost = weight * WEIGHT_RATE
|
||||||
|
distance_cost = distance * DISTANCE_RATE
|
||||||
|
|
||||||
|
# Sum up total cost
|
||||||
|
total = BASE_RATE + weight_cost + distance_cost
|
||||||
|
|
||||||
|
# Apply express multiplier if applicable
|
||||||
|
if express:
|
||||||
|
total *= 2 # Express shipping is 2x the normal rate
|
||||||
|
|
||||||
|
return round(total, 2)
|
||||||
|
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
package_weight = 3.5 # kg
|
||||||
|
shipping_distance = 250 # km
|
||||||
|
|
||||||
|
standard_cost = calculate_shipping_cost(package_weight, shipping_distance)
|
||||||
|
express_cost = calculate_shipping_cost(package_weight, shipping_distance, express=True)
|
||||||
|
|
||||||
|
print(f"Standard shipping: ${standard_cost}")
|
||||||
|
print(f"Express shipping: ${express_cost}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("COMMENTS SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
print("""
|
||||||
|
1. Single-line: Use # for short comments
|
||||||
|
2. Multi-line: Use multiple # lines
|
||||||
|
3. Docstrings: Use triple quotes for documentation
|
||||||
|
4. Explain WHY, not WHAT
|
||||||
|
5. Keep comments up-to-date with code changes
|
||||||
|
6. Use TODO, FIXME, NOTE for special markers
|
||||||
|
7. Don't over-comment obvious code
|
||||||
|
""")
|
||||||
407
01_basics/03_variables.py
Normal file
407
01_basics/03_variables.py
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_variables.py
|
||||||
|
Topic: Variables in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates how to create and use variables in Python. Variables
|
||||||
|
are containers for storing data values. Unlike other programming languages,
|
||||||
|
Python has no command for declaring a variable - it's created the moment
|
||||||
|
you assign a value to it.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Variable creation and assignment
|
||||||
|
- Variable naming conventions
|
||||||
|
- Multiple assignments
|
||||||
|
- Variable types and type checking
|
||||||
|
- Constants
|
||||||
|
- Variable scope basics
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Creating Variables
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Variables are created when you assign a value to them
|
||||||
|
|
||||||
|
print("--- Creating Variables ---")
|
||||||
|
|
||||||
|
# Simple variable assignments
|
||||||
|
name = "Baraa"
|
||||||
|
age = 25
|
||||||
|
height = 1.75
|
||||||
|
is_student = True
|
||||||
|
|
||||||
|
print(f"Name: {name}")
|
||||||
|
print(f"Age: {age}")
|
||||||
|
print(f"Height: {height}m")
|
||||||
|
print(f"Is student: {is_student}")
|
||||||
|
|
||||||
|
# Variables can be reassigned to different values
|
||||||
|
x = 10
|
||||||
|
print(f"\nx = {x}")
|
||||||
|
x = 20
|
||||||
|
print(f"x = {x} (reassigned)")
|
||||||
|
|
||||||
|
# Variables can even change types (dynamic typing)
|
||||||
|
value = 100
|
||||||
|
print(f"\nvalue = {value} (type: {type(value).__name__})")
|
||||||
|
value = "one hundred"
|
||||||
|
print(f"value = {value} (type: {type(value).__name__})")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Variable Naming Rules
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Rules for Python variable names
|
||||||
|
|
||||||
|
print("\n--- Variable Naming Rules ---")
|
||||||
|
|
||||||
|
# ✅ Valid variable names
|
||||||
|
my_variable = 1 # Lowercase with underscores (snake_case) - RECOMMENDED
|
||||||
|
myVariable = 2 # camelCase - valid but not Pythonic
|
||||||
|
MyVariable = 3 # PascalCase - usually for classes
|
||||||
|
_private_var = 4 # Starts with underscore - convention for private
|
||||||
|
__very_private = 5 # Double underscore - name mangling
|
||||||
|
var123 = 6 # Can contain numbers (but not start with them)
|
||||||
|
CONSTANT = 7 # ALL CAPS - convention for constants
|
||||||
|
|
||||||
|
print(f"my_variable = {my_variable}")
|
||||||
|
print(f"_private_var = {_private_var}")
|
||||||
|
print(f"CONSTANT = {CONSTANT}")
|
||||||
|
|
||||||
|
# ❌ Invalid variable names (would cause errors):
|
||||||
|
# 123var = 1 # Cannot start with a number
|
||||||
|
# my-variable = 1 # Hyphens not allowed
|
||||||
|
# my variable = 1 # Spaces not allowed
|
||||||
|
# class = 1 # Cannot use reserved keywords
|
||||||
|
|
||||||
|
# Reserved keywords in Python (cannot use as variable names):
|
||||||
|
import keyword
|
||||||
|
print(f"\nPython reserved keywords: {keyword.kwlist[:10]}...")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Naming Conventions (PEP 8)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Naming Conventions (PEP 8) ---")
|
||||||
|
|
||||||
|
# Variables and functions: snake_case
|
||||||
|
user_name = "john_doe"
|
||||||
|
max_value = 100
|
||||||
|
is_active = True
|
||||||
|
|
||||||
|
# Constants: UPPER_SNAKE_CASE
|
||||||
|
PI = 3.14159
|
||||||
|
MAX_CONNECTIONS = 100
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
|
||||||
|
# Classes: PascalCase (CapWords)
|
||||||
|
# class MyClass:
|
||||||
|
# pass
|
||||||
|
|
||||||
|
# Private variables: leading underscore
|
||||||
|
_internal_state = "private"
|
||||||
|
|
||||||
|
# "Very private" / Name-mangled: double leading underscore
|
||||||
|
__name_mangled = "mangled"
|
||||||
|
|
||||||
|
print(f"user_name: {user_name}")
|
||||||
|
print(f"PI (constant): {PI}")
|
||||||
|
print(f"MAX_CONNECTIONS (constant): {MAX_CONNECTIONS}")
|
||||||
|
|
||||||
|
# Descriptive names are better than short names
|
||||||
|
# ✅ Good
|
||||||
|
total_price = 99.99
|
||||||
|
customer_name = "Alice"
|
||||||
|
is_logged_in = True
|
||||||
|
|
||||||
|
# ❌ Bad (avoid!)
|
||||||
|
# tp = 99.99
|
||||||
|
# cn = "Alice"
|
||||||
|
# il = True
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Multiple Variable Assignment
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Multiple Variable Assignment ---")
|
||||||
|
|
||||||
|
# Assign the same value to multiple variables
|
||||||
|
a = b = c = 100
|
||||||
|
print(f"a = {a}, b = {b}, c = {c}")
|
||||||
|
|
||||||
|
# Assign different values in one line
|
||||||
|
x, y, z = 1, 2, 3
|
||||||
|
print(f"x = {x}, y = {y}, z = {z}")
|
||||||
|
|
||||||
|
# Swap variables easily
|
||||||
|
print(f"\nBefore swap: x = {x}, y = {y}")
|
||||||
|
x, y = y, x
|
||||||
|
print(f"After swap: x = {x}, y = {y}")
|
||||||
|
|
||||||
|
# Unpack a list/tuple into variables
|
||||||
|
coordinates = (10, 20, 30)
|
||||||
|
x, y, z = coordinates
|
||||||
|
print(f"\nUnpacked coordinates: x={x}, y={y}, z={z}")
|
||||||
|
|
||||||
|
# Extended unpacking with *
|
||||||
|
first, *rest = [1, 2, 3, 4, 5]
|
||||||
|
print(f"first = {first}, rest = {rest}")
|
||||||
|
|
||||||
|
*beginning, last = [1, 2, 3, 4, 5]
|
||||||
|
print(f"beginning = {beginning}, last = {last}")
|
||||||
|
|
||||||
|
first, *middle, last = [1, 2, 3, 4, 5]
|
||||||
|
print(f"first = {first}, middle = {middle}, last = {last}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Variable Types and Type Checking
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Variable Types and Type Checking ---")
|
||||||
|
|
||||||
|
# Python is dynamically typed - types are determined at runtime
|
||||||
|
integer_var = 42
|
||||||
|
float_var = 3.14
|
||||||
|
string_var = "Hello"
|
||||||
|
bool_var = True
|
||||||
|
list_var = [1, 2, 3]
|
||||||
|
dict_var = {"key": "value"}
|
||||||
|
none_var = None
|
||||||
|
|
||||||
|
# Check types using type()
|
||||||
|
print(f"integer_var: {integer_var} -> {type(integer_var)}")
|
||||||
|
print(f"float_var: {float_var} -> {type(float_var)}")
|
||||||
|
print(f"string_var: {string_var} -> {type(string_var)}")
|
||||||
|
print(f"bool_var: {bool_var} -> {type(bool_var)}")
|
||||||
|
print(f"list_var: {list_var} -> {type(list_var)}")
|
||||||
|
print(f"dict_var: {dict_var} -> {type(dict_var)}")
|
||||||
|
print(f"none_var: {none_var} -> {type(none_var)}")
|
||||||
|
|
||||||
|
# Check if variable is of a specific type using isinstance()
|
||||||
|
print(f"\nIs integer_var an int? {isinstance(integer_var, int)}")
|
||||||
|
print(f"Is string_var a str? {isinstance(string_var, str)}")
|
||||||
|
print(f"Is list_var a list? {isinstance(list_var, list)}")
|
||||||
|
|
||||||
|
# Check multiple types
|
||||||
|
print(f"Is integer_var int or float? {isinstance(integer_var, (int, float))}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Type Casting (Converting Types)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Type Casting ---")
|
||||||
|
|
||||||
|
# String to integer
|
||||||
|
str_num = "123"
|
||||||
|
int_num = int(str_num)
|
||||||
|
print(f"'{str_num}' (str) -> {int_num} (int)")
|
||||||
|
|
||||||
|
# String to float
|
||||||
|
str_float = "3.14"
|
||||||
|
float_num = float(str_float)
|
||||||
|
print(f"'{str_float}' (str) -> {float_num} (float)")
|
||||||
|
|
||||||
|
# Number to string
|
||||||
|
number = 42
|
||||||
|
str_number = str(number)
|
||||||
|
print(f"{number} (int) -> '{str_number}' (str)")
|
||||||
|
|
||||||
|
# Float to integer (truncates, doesn't round)
|
||||||
|
pi = 3.99
|
||||||
|
int_pi = int(pi)
|
||||||
|
print(f"{pi} (float) -> {int_pi} (int) [truncated]")
|
||||||
|
|
||||||
|
# Boolean conversions
|
||||||
|
print(f"\nbool(1) = {bool(1)}") # True
|
||||||
|
print(f"bool(0) = {bool(0)}") # False
|
||||||
|
print(f"bool('hello') = {bool('hello')}") # True
|
||||||
|
print(f"bool('') = {bool('')}") # False
|
||||||
|
print(f"bool([1, 2]) = {bool([1, 2])}") # True
|
||||||
|
print(f"bool([]) = {bool([])}") # False
|
||||||
|
|
||||||
|
# Integer to boolean
|
||||||
|
print(f"\nint(True) = {int(True)}") # 1
|
||||||
|
print(f"int(False) = {int(False)}") # 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Constants
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Constants ---")
|
||||||
|
|
||||||
|
# Python doesn't have true constants, but by convention:
|
||||||
|
# - Use UPPER_SNAKE_CASE for constants
|
||||||
|
# - Don't modify them after initial assignment
|
||||||
|
|
||||||
|
# Mathematical constants
|
||||||
|
PI = 3.14159265359
|
||||||
|
E = 2.71828182845
|
||||||
|
GOLDEN_RATIO = 1.61803398875
|
||||||
|
|
||||||
|
# Application constants
|
||||||
|
MAX_USERS = 1000
|
||||||
|
API_TIMEOUT = 30
|
||||||
|
BASE_URL = "https://api.example.com"
|
||||||
|
DEBUG_MODE = False
|
||||||
|
|
||||||
|
print(f"PI = {PI}")
|
||||||
|
print(f"MAX_USERS = {MAX_USERS}")
|
||||||
|
print(f"BASE_URL = {BASE_URL}")
|
||||||
|
|
||||||
|
# You CAN modify them (Python won't stop you), but you SHOULDN'T
|
||||||
|
# PI = 3 # Don't do this!
|
||||||
|
|
||||||
|
# For true constants, you can use:
|
||||||
|
# 1. Separate constants module (constants.py)
|
||||||
|
# 2. typing.Final (Python 3.8+)
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
MAX_SIZE: Final = 100
|
||||||
|
# MAX_SIZE = 200 # Type checker will warn about this
|
||||||
|
|
||||||
|
print(f"MAX_SIZE (Final) = {MAX_SIZE}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Variable Scope (Basic)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Variable Scope ---")
|
||||||
|
|
||||||
|
# Global variable
|
||||||
|
global_var = "I'm global"
|
||||||
|
|
||||||
|
|
||||||
|
def my_function():
|
||||||
|
# Local variable
|
||||||
|
local_var = "I'm local"
|
||||||
|
print(f"Inside function - global_var: {global_var}")
|
||||||
|
print(f"Inside function - local_var: {local_var}")
|
||||||
|
|
||||||
|
|
||||||
|
my_function()
|
||||||
|
print(f"Outside function - global_var: {global_var}")
|
||||||
|
# print(local_var) # Error! local_var doesn't exist here
|
||||||
|
|
||||||
|
|
||||||
|
# Modifying global variables inside functions
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
|
||||||
|
def increment():
|
||||||
|
global counter # Declare we want to use global counter
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
|
||||||
|
print(f"\nBefore increment: counter = {counter}")
|
||||||
|
increment()
|
||||||
|
increment()
|
||||||
|
print(f"After 2 increments: counter = {counter}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Variable Identity and Memory
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Variable Identity and Memory ---")
|
||||||
|
|
||||||
|
# id() returns the memory address of a variable
|
||||||
|
a = 10
|
||||||
|
b = 10
|
||||||
|
c = a
|
||||||
|
|
||||||
|
print(f"a = {a}, id(a) = {id(a)}")
|
||||||
|
print(f"b = {b}, id(b) = {id(b)}")
|
||||||
|
print(f"c = {c}, id(c) = {id(c)}")
|
||||||
|
|
||||||
|
# Small integers (-5 to 256) are cached in Python
|
||||||
|
print(f"\na is b: {a is b}") # True (same memory location)
|
||||||
|
print(f"a is c: {a is c}") # True
|
||||||
|
|
||||||
|
# Larger numbers may have different ids
|
||||||
|
x = 1000
|
||||||
|
y = 1000
|
||||||
|
print(f"\nx = {x}, id(x) = {id(x)}")
|
||||||
|
print(f"y = {y}, id(y) = {id(y)}")
|
||||||
|
|
||||||
|
# 'is' vs '=='
|
||||||
|
list1 = [1, 2, 3]
|
||||||
|
list2 = [1, 2, 3]
|
||||||
|
list3 = list1
|
||||||
|
|
||||||
|
print(f"\nlist1 == list2: {list1 == list2}") # True (same values)
|
||||||
|
print(f"list1 is list2: {list1 is list2}") # False (different objects)
|
||||||
|
print(f"list1 is list3: {list1 is list3}") # True (same object)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Deleting Variables
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Deleting Variables ---")
|
||||||
|
|
||||||
|
temp_var = "I will be deleted"
|
||||||
|
print(f"temp_var exists: {temp_var}")
|
||||||
|
|
||||||
|
del temp_var
|
||||||
|
# print(temp_var) # Error! NameError: name 'temp_var' is not defined
|
||||||
|
|
||||||
|
print("temp_var has been deleted")
|
||||||
|
|
||||||
|
# Check if variable exists
|
||||||
|
if 'temp_var' not in dir():
|
||||||
|
print("temp_var no longer exists in current scope")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 11. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Example 1: User profile
|
||||||
|
first_name = "Baraa"
|
||||||
|
last_name = "Shaer"
|
||||||
|
full_name = f"{first_name} {last_name}"
|
||||||
|
email = f"{first_name.lower()}.{last_name.lower()}@example.com"
|
||||||
|
|
||||||
|
print(f"Full Name: {full_name}")
|
||||||
|
print(f"Email: {email}")
|
||||||
|
|
||||||
|
# Example 2: Shopping cart
|
||||||
|
item_price = 29.99
|
||||||
|
quantity = 3
|
||||||
|
discount_percent = 10
|
||||||
|
|
||||||
|
subtotal = item_price * quantity
|
||||||
|
discount_amount = subtotal * (discount_percent / 100)
|
||||||
|
total = subtotal - discount_amount
|
||||||
|
|
||||||
|
print(f"\nSubtotal: ${subtotal:.2f}")
|
||||||
|
print(f"Discount ({discount_percent}%): -${discount_amount:.2f}")
|
||||||
|
print(f"Total: ${total:.2f}")
|
||||||
|
|
||||||
|
# Example 3: Temperature conversion
|
||||||
|
celsius = 25
|
||||||
|
fahrenheit = (celsius * 9/5) + 32
|
||||||
|
kelvin = celsius + 273.15
|
||||||
|
|
||||||
|
print(f"\n{celsius}°C = {fahrenheit}°F = {kelvin}K")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("VARIABLES SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
print("""
|
||||||
|
1. Variables are created by assignment (=)
|
||||||
|
2. Python is dynamically typed
|
||||||
|
3. Use snake_case for variables (PEP 8)
|
||||||
|
4. Use UPPER_CASE for constants
|
||||||
|
5. Multiple assignments: x, y, z = 1, 2, 3
|
||||||
|
6. Check types with type() or isinstance()
|
||||||
|
7. Convert types with int(), str(), float(), bool()
|
||||||
|
8. Variables have scope (local vs global)
|
||||||
|
9. Use 'is' for identity, '==' for equality
|
||||||
|
10. Delete variables with 'del'
|
||||||
|
""")
|
||||||
536
01_basics/04_data_types.py
Normal file
536
01_basics/04_data_types.py
Normal file
|
|
@ -0,0 +1,536 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 04_data_types.py
|
||||||
|
Topic: Data Types in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates Python's built-in data types. Understanding data types
|
||||||
|
is fundamental to programming as they determine what operations can be
|
||||||
|
performed on data and how it's stored in memory.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Numeric types (int, float, complex)
|
||||||
|
- Text type (str)
|
||||||
|
- Boolean type (bool)
|
||||||
|
- Sequence types (list, tuple, range)
|
||||||
|
- Mapping type (dict)
|
||||||
|
- Set types (set, frozenset)
|
||||||
|
- None type
|
||||||
|
- Type checking and conversion
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Numeric Types Overview
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("NUMERIC TYPES")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1.1 Integers (int)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Integers (int) ---")
|
||||||
|
|
||||||
|
# Basic integers
|
||||||
|
positive_int = 42
|
||||||
|
negative_int = -17
|
||||||
|
zero = 0
|
||||||
|
|
||||||
|
print(f"Positive: {positive_int}")
|
||||||
|
print(f"Negative: {negative_int}")
|
||||||
|
print(f"Zero: {zero}")
|
||||||
|
|
||||||
|
# Large integers (Python handles arbitrary precision)
|
||||||
|
big_number = 123456789012345678901234567890
|
||||||
|
print(f"Big number: {big_number}")
|
||||||
|
print(f"Type: {type(big_number)}")
|
||||||
|
|
||||||
|
# Different number bases
|
||||||
|
binary = 0b1010 # Binary (base 2)
|
||||||
|
octal = 0o17 # Octal (base 8)
|
||||||
|
hexadecimal = 0xFF # Hexadecimal (base 16)
|
||||||
|
|
||||||
|
print(f"\nBinary 0b1010 = {binary}")
|
||||||
|
print(f"Octal 0o17 = {octal}")
|
||||||
|
print(f"Hex 0xFF = {hexadecimal}")
|
||||||
|
|
||||||
|
# Underscores for readability (Python 3.6+)
|
||||||
|
million = 1_000_000
|
||||||
|
credit_card = 1234_5678_9012_3456
|
||||||
|
print(f"\nMillion: {million}")
|
||||||
|
print(f"Credit card: {credit_card}")
|
||||||
|
|
||||||
|
# Integer operations
|
||||||
|
a, b = 17, 5
|
||||||
|
print(f"\na = {a}, b = {b}")
|
||||||
|
print(f"a + b = {a + b}") # Addition
|
||||||
|
print(f"a - b = {a - b}") # Subtraction
|
||||||
|
print(f"a * b = {a * b}") # Multiplication
|
||||||
|
print(f"a / b = {a / b}") # Division (returns float)
|
||||||
|
print(f"a // b = {a // b}") # Floor division
|
||||||
|
print(f"a % b = {a % b}") # Modulo (remainder)
|
||||||
|
print(f"a ** b = {a ** b}") # Exponentiation
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1.2 Floating-Point Numbers (float)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Floating-Point Numbers (float) ---")
|
||||||
|
|
||||||
|
# Basic floats
|
||||||
|
pi = 3.14159
|
||||||
|
negative_float = -2.5
|
||||||
|
scientific = 2.5e10 # Scientific notation (2.5 × 10^10)
|
||||||
|
|
||||||
|
print(f"Pi: {pi}")
|
||||||
|
print(f"Negative: {negative_float}")
|
||||||
|
print(f"Scientific 2.5e10: {scientific}")
|
||||||
|
print(f"Type: {type(pi)}")
|
||||||
|
|
||||||
|
# Float precision limitations
|
||||||
|
print(f"\n0.1 + 0.2 = {0.1 + 0.2}") # Not exactly 0.3!
|
||||||
|
print(f"0.1 + 0.2 == 0.3: {0.1 + 0.2 == 0.3}") # False!
|
||||||
|
|
||||||
|
# For precise decimal calculations, use the decimal module
|
||||||
|
from decimal import Decimal
|
||||||
|
d1 = Decimal('0.1')
|
||||||
|
d2 = Decimal('0.2')
|
||||||
|
print(f"Decimal: {d1} + {d2} = {d1 + d2}")
|
||||||
|
|
||||||
|
# Special float values
|
||||||
|
infinity = float('inf')
|
||||||
|
neg_infinity = float('-inf')
|
||||||
|
not_a_number = float('nan')
|
||||||
|
|
||||||
|
print(f"\nInfinity: {infinity}")
|
||||||
|
print(f"Negative infinity: {neg_infinity}")
|
||||||
|
print(f"NaN: {not_a_number}")
|
||||||
|
print(f"1000 < infinity: {1000 < infinity}")
|
||||||
|
|
||||||
|
# Float methods
|
||||||
|
f = 3.7
|
||||||
|
print(f"\n{f}.is_integer(): {f.is_integer()}")
|
||||||
|
print(f"4.0.is_integer(): {(4.0).is_integer()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1.3 Complex Numbers (complex)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Complex Numbers (complex) ---")
|
||||||
|
|
||||||
|
# Creating complex numbers
|
||||||
|
c1 = 3 + 4j
|
||||||
|
c2 = complex(2, -1)
|
||||||
|
|
||||||
|
print(f"c1 = {c1}")
|
||||||
|
print(f"c2 = {c2}")
|
||||||
|
print(f"Type: {type(c1)}")
|
||||||
|
|
||||||
|
# Accessing parts
|
||||||
|
print(f"\nc1.real = {c1.real}")
|
||||||
|
print(f"c1.imag = {c1.imag}")
|
||||||
|
print(f"Conjugate of c1: {c1.conjugate()}")
|
||||||
|
|
||||||
|
# Complex arithmetic
|
||||||
|
print(f"\nc1 + c2 = {c1 + c2}")
|
||||||
|
print(f"c1 * c2 = {c1 * c2}")
|
||||||
|
print(f"abs(c1) = {abs(c1)}") # Magnitude
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Text Type (str)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TEXT TYPE - STRINGS")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Creating strings
|
||||||
|
single_quotes = 'Hello, World!'
|
||||||
|
double_quotes = "Python is awesome"
|
||||||
|
multi_line = """This is a
|
||||||
|
multi-line string"""
|
||||||
|
|
||||||
|
print(f"Single quotes: {single_quotes}")
|
||||||
|
print(f"Double quotes: {double_quotes}")
|
||||||
|
print(f"Multi-line:\n{multi_line}")
|
||||||
|
|
||||||
|
# String operations
|
||||||
|
s = "Hello, Python!"
|
||||||
|
print(f"\nString: '{s}'")
|
||||||
|
print(f"Length: {len(s)}")
|
||||||
|
print(f"Uppercase: {s.upper()}")
|
||||||
|
print(f"Lowercase: {s.lower()}")
|
||||||
|
print(f"Title case: {s.title()}")
|
||||||
|
print(f"Replace: {s.replace('Python', 'World')}")
|
||||||
|
print(f"Split: {s.split(', ')}")
|
||||||
|
print(f"Strip: {' hello '.strip()}")
|
||||||
|
|
||||||
|
# String indexing and slicing
|
||||||
|
print(f"\nIndexing: s[0] = '{s[0]}', s[-1] = '{s[-1]}'")
|
||||||
|
print(f"Slicing: s[0:5] = '{s[0:5]}'")
|
||||||
|
print(f"Step: s[::2] = '{s[::2]}'")
|
||||||
|
print(f"Reverse: s[::-1] = '{s[::-1]}'")
|
||||||
|
|
||||||
|
# String methods for checking
|
||||||
|
text = "Python3"
|
||||||
|
print(f"\n'{text}'.isalnum(): {text.isalnum()}")
|
||||||
|
print(f"'python'.isalpha(): {'python'.isalpha()}")
|
||||||
|
print(f"'12345'.isdigit(): {'12345'.isdigit()}")
|
||||||
|
print(f"'hello'.islower(): {'hello'.islower()}")
|
||||||
|
print(f"'HELLO'.isupper(): {'HELLO'.isupper()}")
|
||||||
|
|
||||||
|
# String formatting
|
||||||
|
name = "Baraa"
|
||||||
|
age = 25
|
||||||
|
print(f"\nf-string: {name} is {age} years old")
|
||||||
|
print("format(): {} is {} years old".format(name, age))
|
||||||
|
print("%-formatting: %s is %d years old" % (name, age))
|
||||||
|
|
||||||
|
# Escape characters
|
||||||
|
print(f"\nNewline: Hello\\nWorld → Hello\nWorld")
|
||||||
|
print(f"Tab: Hello\\tWorld → Hello\tWorld")
|
||||||
|
print("Quote: She said \"Hi!\"")
|
||||||
|
|
||||||
|
# Raw strings
|
||||||
|
print(f"\nRaw string: r'C:\\Users\\Name' → {r'C:\Users\Name'}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Boolean Type (bool)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("BOOLEAN TYPE")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Boolean values
|
||||||
|
is_active = True
|
||||||
|
is_deleted = False
|
||||||
|
|
||||||
|
print(f"is_active: {is_active}, type: {type(is_active)}")
|
||||||
|
print(f"is_deleted: {is_deleted}")
|
||||||
|
|
||||||
|
# Boolean as integers
|
||||||
|
print(f"\nTrue as int: {int(True)}") # 1
|
||||||
|
print(f"False as int: {int(False)}") # 0
|
||||||
|
print(f"True + True = {True + True}") # 2
|
||||||
|
|
||||||
|
# Comparison operators return booleans
|
||||||
|
x, y = 10, 5
|
||||||
|
print(f"\nx = {x}, y = {y}")
|
||||||
|
print(f"x > y: {x > y}")
|
||||||
|
print(f"x < y: {x < y}")
|
||||||
|
print(f"x == y: {x == y}")
|
||||||
|
print(f"x != y: {x != y}")
|
||||||
|
print(f"x >= y: {x >= y}")
|
||||||
|
|
||||||
|
# Logical operators
|
||||||
|
print(f"\nTrue and False: {True and False}")
|
||||||
|
print(f"True or False: {True or False}")
|
||||||
|
print(f"not True: {not True}")
|
||||||
|
|
||||||
|
# Truthy and Falsy values
|
||||||
|
print("\nFalsy values (evaluate to False):")
|
||||||
|
falsy_values = [False, 0, 0.0, "", [], {}, set(), None]
|
||||||
|
for val in falsy_values:
|
||||||
|
print(f" bool({repr(val)}) = {bool(val)}")
|
||||||
|
|
||||||
|
print("\nTruthy values (evaluate to True):")
|
||||||
|
truthy_values = [True, 1, -1, 3.14, "hello", [1, 2], {"a": 1}]
|
||||||
|
for val in truthy_values:
|
||||||
|
print(f" bool({repr(val)}) = {bool(val)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Sequence Types
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("SEQUENCE TYPES")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4.1 Lists
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Lists (Mutable Sequences) ---")
|
||||||
|
|
||||||
|
# Creating lists
|
||||||
|
empty_list = []
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
mixed = [1, "two", 3.0, True, None]
|
||||||
|
nested = [[1, 2], [3, 4], [5, 6]]
|
||||||
|
|
||||||
|
print(f"Numbers: {numbers}")
|
||||||
|
print(f"Mixed types: {mixed}")
|
||||||
|
print(f"Nested: {nested}")
|
||||||
|
|
||||||
|
# List operations
|
||||||
|
fruits = ["apple", "banana", "cherry"]
|
||||||
|
print(f"\nFruits: {fruits}")
|
||||||
|
print(f"Length: {len(fruits)}")
|
||||||
|
print(f"First: {fruits[0]}, Last: {fruits[-1]}")
|
||||||
|
print(f"Slice: {fruits[1:3]}")
|
||||||
|
|
||||||
|
# Modifying lists
|
||||||
|
fruits.append("date")
|
||||||
|
print(f"After append: {fruits}")
|
||||||
|
|
||||||
|
fruits.insert(1, "blueberry")
|
||||||
|
print(f"After insert at 1: {fruits}")
|
||||||
|
|
||||||
|
fruits.remove("banana")
|
||||||
|
print(f"After remove 'banana': {fruits}")
|
||||||
|
|
||||||
|
popped = fruits.pop()
|
||||||
|
print(f"Popped: {popped}, List: {fruits}")
|
||||||
|
|
||||||
|
# List comprehension
|
||||||
|
squares = [x**2 for x in range(1, 6)]
|
||||||
|
print(f"\nSquares (comprehension): {squares}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4.2 Tuples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Tuples (Immutable Sequences) ---")
|
||||||
|
|
||||||
|
# Creating tuples
|
||||||
|
empty_tuple = ()
|
||||||
|
single = (1,) # Note the comma
|
||||||
|
coordinates = (10, 20, 30)
|
||||||
|
mixed_tuple = (1, "two", 3.0)
|
||||||
|
|
||||||
|
print(f"Coordinates: {coordinates}")
|
||||||
|
print(f"Type: {type(coordinates)}")
|
||||||
|
|
||||||
|
# Tuple unpacking
|
||||||
|
x, y, z = coordinates
|
||||||
|
print(f"Unpacked: x={x}, y={y}, z={z}")
|
||||||
|
|
||||||
|
# Tuples are immutable
|
||||||
|
# coordinates[0] = 100 # This would raise an error
|
||||||
|
|
||||||
|
# Named tuples
|
||||||
|
from collections import namedtuple
|
||||||
|
Point = namedtuple('Point', ['x', 'y'])
|
||||||
|
p = Point(10, 20)
|
||||||
|
print(f"\nNamed tuple: {p}")
|
||||||
|
print(f"p.x = {p.x}, p.y = {p.y}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4.3 Range
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Range ---")
|
||||||
|
|
||||||
|
# Creating ranges
|
||||||
|
r1 = range(5) # 0-4
|
||||||
|
r2 = range(1, 6) # 1-5
|
||||||
|
r3 = range(0, 10, 2) # 0, 2, 4, 6, 8
|
||||||
|
|
||||||
|
print(f"range(5): {list(r1)}")
|
||||||
|
print(f"range(1, 6): {list(r2)}")
|
||||||
|
print(f"range(0, 10, 2): {list(r3)}")
|
||||||
|
print(f"Type: {type(r1)}")
|
||||||
|
|
||||||
|
# Range is memory efficient
|
||||||
|
big_range = range(1000000)
|
||||||
|
print(f"\nrange(1000000) - Length: {len(big_range)}")
|
||||||
|
print(f"500000 in big_range: {500000 in big_range}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Mapping Type (dict)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("MAPPING TYPE - DICTIONARY")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Creating dictionaries
|
||||||
|
empty_dict = {}
|
||||||
|
person = {"name": "Baraa", "age": 25, "city": "Gaza"}
|
||||||
|
using_dict = dict(a=1, b=2, c=3)
|
||||||
|
|
||||||
|
print(f"Person: {person}")
|
||||||
|
print(f"Type: {type(person)}")
|
||||||
|
|
||||||
|
# Accessing values
|
||||||
|
print(f"\nName: {person['name']}")
|
||||||
|
print(f"Age (get): {person.get('age')}")
|
||||||
|
print(f"Country (get with default): {person.get('country', 'Unknown')}")
|
||||||
|
|
||||||
|
# Modifying dictionaries
|
||||||
|
person["email"] = "baraa@example.com" # Add
|
||||||
|
person["age"] = 26 # Update
|
||||||
|
print(f"\nAfter modifications: {person}")
|
||||||
|
|
||||||
|
# Dictionary methods
|
||||||
|
print(f"\nKeys: {list(person.keys())}")
|
||||||
|
print(f"Values: {list(person.values())}")
|
||||||
|
print(f"Items: {list(person.items())}")
|
||||||
|
|
||||||
|
# Dictionary comprehension
|
||||||
|
squares_dict = {x: x**2 for x in range(1, 6)}
|
||||||
|
print(f"\nSquares dict: {squares_dict}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Set Types
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("SET TYPES")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Creating sets (mutable, unordered, unique elements)
|
||||||
|
empty_set = set() # Note: {} creates an empty dict, not set
|
||||||
|
numbers_set = {1, 2, 3, 4, 5}
|
||||||
|
from_list = set([1, 2, 2, 3, 3, 3]) # Duplicates removed
|
||||||
|
|
||||||
|
print(f"Numbers set: {numbers_set}")
|
||||||
|
print(f"From list with duplicates: {from_list}")
|
||||||
|
print(f"Type: {type(numbers_set)}")
|
||||||
|
|
||||||
|
# Set operations
|
||||||
|
a = {1, 2, 3, 4}
|
||||||
|
b = {3, 4, 5, 6}
|
||||||
|
|
||||||
|
print(f"\nSet A: {a}")
|
||||||
|
print(f"Set B: {b}")
|
||||||
|
print(f"Union (A | B): {a | b}")
|
||||||
|
print(f"Intersection (A & B): {a & b}")
|
||||||
|
print(f"Difference (A - B): {a - b}")
|
||||||
|
print(f"Symmetric difference (A ^ B): {a ^ b}")
|
||||||
|
|
||||||
|
# Set methods
|
||||||
|
s = {1, 2, 3}
|
||||||
|
s.add(4)
|
||||||
|
print(f"\nAfter add(4): {s}")
|
||||||
|
s.discard(2)
|
||||||
|
print(f"After discard(2): {s}")
|
||||||
|
|
||||||
|
# Frozenset (immutable set)
|
||||||
|
frozen = frozenset([1, 2, 3])
|
||||||
|
print(f"\nFrozenset: {frozen}")
|
||||||
|
# frozen.add(4) # This would raise an error
|
||||||
|
|
||||||
|
# Sets are useful for membership testing
|
||||||
|
valid_statuses = {"active", "pending", "completed"}
|
||||||
|
user_status = "active"
|
||||||
|
print(f"\n'{user_status}' is valid: {user_status in valid_statuses}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. None Type
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("NONE TYPE")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# None represents absence of a value
|
||||||
|
result = None
|
||||||
|
|
||||||
|
print(f"result = {result}")
|
||||||
|
print(f"Type: {type(result)}")
|
||||||
|
print(f"result is None: {result is None}") # Use 'is' not '=='
|
||||||
|
|
||||||
|
# Common uses of None
|
||||||
|
def greet(name=None):
|
||||||
|
"""Function with optional parameter"""
|
||||||
|
if name is None:
|
||||||
|
return "Hello, Guest!"
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
print(f"\ngreet(): {greet()}")
|
||||||
|
print(f"greet('Baraa'): {greet('Baraa')}")
|
||||||
|
|
||||||
|
# None as placeholder
|
||||||
|
data = None
|
||||||
|
# ... later in code ...
|
||||||
|
data = fetch_data() if False else [] # Simulated
|
||||||
|
print(f"Data initialized: {data}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Type Checking and Conversion
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("TYPE CHECKING AND CONVERSION")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Using type()
|
||||||
|
values = [42, 3.14, "hello", True, [1, 2], {"a": 1}, None]
|
||||||
|
print("Type checking with type():")
|
||||||
|
for val in values:
|
||||||
|
print(f" {repr(val):15} -> {type(val).__name__}")
|
||||||
|
|
||||||
|
# Using isinstance() (preferred for type checking)
|
||||||
|
print("\nType checking with isinstance():")
|
||||||
|
x = 42
|
||||||
|
print(f"isinstance(42, int): {isinstance(x, int)}")
|
||||||
|
print(f"isinstance(42, (int, float)): {isinstance(x, (int, float))}")
|
||||||
|
|
||||||
|
# Type conversion summary
|
||||||
|
print("\nType Conversion Examples:")
|
||||||
|
print(f"int('42') = {int('42')}")
|
||||||
|
print(f"float('3.14') = {float('3.14')}")
|
||||||
|
print(f"str(42) = {str(42)}")
|
||||||
|
print(f"bool(1) = {bool(1)}")
|
||||||
|
print(f"list('abc') = {list('abc')}")
|
||||||
|
print(f"tuple([1,2,3]) = {tuple([1, 2, 3])}")
|
||||||
|
print(f"set([1,2,2,3]) = {set([1, 2, 2, 3])}")
|
||||||
|
print(f"dict([('a',1),('b',2)]) = {dict([('a', 1), ('b', 2)])}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Data Type Comparison Table
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("DATA TYPE COMPARISON TABLE")
|
||||||
|
print("=" * 60)
|
||||||
|
print("""
|
||||||
|
| Type | Mutable | Ordered | Duplicates | Example |
|
||||||
|
|-----------|---------|---------|------------|----------------------|
|
||||||
|
| int | No | N/A | N/A | 42 |
|
||||||
|
| float | No | N/A | N/A | 3.14 |
|
||||||
|
| complex | No | N/A | N/A | 3+4j |
|
||||||
|
| str | No | Yes | Yes | "hello" |
|
||||||
|
| bool | No | N/A | N/A | True |
|
||||||
|
| list | Yes | Yes | Yes | [1, 2, 3] |
|
||||||
|
| tuple | No | Yes | Yes | (1, 2, 3) |
|
||||||
|
| range | No | Yes | No | range(5) |
|
||||||
|
| dict | Yes | Yes* | Keys: No | {"a": 1} |
|
||||||
|
| set | Yes | No | No | {1, 2, 3} |
|
||||||
|
| frozenset | No | No | No | frozenset({1, 2, 3}) |
|
||||||
|
| NoneType | No | N/A | N/A | None |
|
||||||
|
|
||||||
|
* Dicts preserve insertion order in Python 3.7+
|
||||||
|
""")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("DATA TYPES SUMMARY")
|
||||||
|
print("=" * 60)
|
||||||
|
print("""
|
||||||
|
Numeric: int, float, complex
|
||||||
|
Text: str (immutable)
|
||||||
|
Boolean: bool (True/False)
|
||||||
|
Sequences: list (mutable), tuple (immutable), range
|
||||||
|
Mapping: dict (key-value pairs)
|
||||||
|
Sets: set (mutable), frozenset (immutable)
|
||||||
|
Special: None (absence of value)
|
||||||
|
|
||||||
|
Key Points:
|
||||||
|
1. Python is dynamically typed
|
||||||
|
2. Use type() or isinstance() to check types
|
||||||
|
3. Use appropriate type for your data
|
||||||
|
4. Mutable types can be changed, immutable cannot
|
||||||
|
5. Choose between list/tuple based on mutability needs
|
||||||
|
6. Use dict for key-value mappings
|
||||||
|
7. Use set for unique elements and fast membership tests
|
||||||
|
""")
|
||||||
BIN
01_basics/__pycache__/04_data_types.cpython-313.pyc
Normal file
BIN
01_basics/__pycache__/04_data_types.cpython-313.pyc
Normal file
Binary file not shown.
145
02_control_flow/01_if_else.py
Normal file
145
02_control_flow/01_if_else.py
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_if_else.py
|
||||||
|
Topic: Conditional Statements - if/else
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates the fundamentals of conditional statements in Python.
|
||||||
|
Conditional statements allow your program to make decisions and execute
|
||||||
|
different code blocks based on whether conditions are True or False.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- if statement: Executes code block if condition is True
|
||||||
|
- else statement: Executes code block if condition is False
|
||||||
|
- Comparison operators: ==, !=, <, >, <=, >=
|
||||||
|
- Logical operators: and, or, not
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Simple if Statement
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# The most basic form - executes code only if condition is True
|
||||||
|
|
||||||
|
age = 18
|
||||||
|
|
||||||
|
if age >= 18:
|
||||||
|
print("You are an adult.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. if-else Statement
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Provides an alternative when the condition is False
|
||||||
|
|
||||||
|
temperature = 15
|
||||||
|
|
||||||
|
if temperature > 25:
|
||||||
|
print("It's a hot day!")
|
||||||
|
else:
|
||||||
|
print("It's not that hot today.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Comparison Operators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Used to compare values and return True or False
|
||||||
|
|
||||||
|
x = 10
|
||||||
|
y = 20
|
||||||
|
|
||||||
|
print("\n--- Comparison Operators ---")
|
||||||
|
print(f"x == y: {x == y}") # Equal to
|
||||||
|
print(f"x != y: {x != y}") # Not equal to
|
||||||
|
print(f"x < y: {x < y}") # Less than
|
||||||
|
print(f"x > y: {x > y}") # Greater than
|
||||||
|
print(f"x <= y: {x <= y}") # Less than or equal to
|
||||||
|
print(f"x >= y: {x >= y}") # Greater than or equal to
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Logical Operators (and, or, not)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Combine multiple conditions
|
||||||
|
|
||||||
|
age = 25
|
||||||
|
has_license = True
|
||||||
|
|
||||||
|
print("\n--- Logical Operators ---")
|
||||||
|
|
||||||
|
# 'and' - Both conditions must be True
|
||||||
|
if age >= 18 and has_license:
|
||||||
|
print("You can drive a car.")
|
||||||
|
|
||||||
|
# 'or' - At least one condition must be True
|
||||||
|
is_weekend = True
|
||||||
|
is_holiday = False
|
||||||
|
|
||||||
|
if is_weekend or is_holiday:
|
||||||
|
print("You can relax today!")
|
||||||
|
|
||||||
|
# 'not' - Reverses the boolean value
|
||||||
|
is_raining = False
|
||||||
|
|
||||||
|
if not is_raining:
|
||||||
|
print("You don't need an umbrella.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Nested if Statements
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# You can place if statements inside other if statements
|
||||||
|
|
||||||
|
print("\n--- Nested if Statements ---")
|
||||||
|
|
||||||
|
score = 85
|
||||||
|
attendance = 90
|
||||||
|
|
||||||
|
if score >= 60:
|
||||||
|
print("You passed the exam!")
|
||||||
|
if attendance >= 80:
|
||||||
|
print("And you have excellent attendance!")
|
||||||
|
else:
|
||||||
|
print("But try to improve your attendance.")
|
||||||
|
else:
|
||||||
|
print("You need to study harder.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Truthy and Falsy Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# In Python, some values are considered False: 0, "", [], {}, None, False
|
||||||
|
# Everything else is considered True
|
||||||
|
|
||||||
|
print("\n--- Truthy and Falsy Values ---")
|
||||||
|
|
||||||
|
empty_list = []
|
||||||
|
full_list = [1, 2, 3]
|
||||||
|
|
||||||
|
if full_list:
|
||||||
|
print("The list has items.")
|
||||||
|
|
||||||
|
if not empty_list:
|
||||||
|
print("The list is empty.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Ternary Operator (One-line if-else)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A compact way to write simple if-else statements
|
||||||
|
|
||||||
|
age = 20
|
||||||
|
status = "adult" if age >= 18 else "minor"
|
||||||
|
print(f"\nTernary result: You are an {status}.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Practical Example: Simple Login Check
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example: Login Check ---")
|
||||||
|
|
||||||
|
username = "admin"
|
||||||
|
password = "secret123"
|
||||||
|
|
||||||
|
input_user = "admin"
|
||||||
|
input_pass = "secret123"
|
||||||
|
|
||||||
|
if input_user == username and input_pass == password:
|
||||||
|
print("Login successful! Welcome back.")
|
||||||
|
else:
|
||||||
|
print("Invalid username or password.")
|
||||||
213
02_control_flow/02_elif.py
Normal file
213
02_control_flow/02_elif.py
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_elif.py
|
||||||
|
Topic: Multiple Conditions with elif
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates how to handle multiple conditions using 'elif' (else if).
|
||||||
|
When you have more than two possible outcomes, elif allows you to check
|
||||||
|
multiple conditions in sequence.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- elif chains for multiple conditions
|
||||||
|
- Order matters - first True condition wins
|
||||||
|
- Default case with else
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic elif Structure
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Check multiple conditions in sequence
|
||||||
|
|
||||||
|
score = 85
|
||||||
|
|
||||||
|
print("--- Grade Calculator ---")
|
||||||
|
|
||||||
|
if score >= 90:
|
||||||
|
grade = "A"
|
||||||
|
print("Excellent work!")
|
||||||
|
elif score >= 80:
|
||||||
|
grade = "B"
|
||||||
|
print("Good job!")
|
||||||
|
elif score >= 70:
|
||||||
|
grade = "C"
|
||||||
|
print("Satisfactory.")
|
||||||
|
elif score >= 60:
|
||||||
|
grade = "D"
|
||||||
|
print("You passed, but need improvement.")
|
||||||
|
else:
|
||||||
|
grade = "F"
|
||||||
|
print("You need to study harder.")
|
||||||
|
|
||||||
|
print(f"Your grade: {grade}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Order Matters in elif
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# The first condition that evaluates to True will be executed
|
||||||
|
|
||||||
|
print("\n--- Order Matters Example ---")
|
||||||
|
|
||||||
|
number = 15
|
||||||
|
|
||||||
|
# Correct order (most specific first)
|
||||||
|
if number > 20:
|
||||||
|
print("Greater than 20")
|
||||||
|
elif number > 10:
|
||||||
|
print("Greater than 10") # This will execute
|
||||||
|
elif number > 5:
|
||||||
|
print("Greater than 5")
|
||||||
|
else:
|
||||||
|
print("5 or less")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Day of the Week Example
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Classic example showing multiple discrete options
|
||||||
|
|
||||||
|
print("\n--- Day of the Week ---")
|
||||||
|
|
||||||
|
day = 3 # 1=Monday, 2=Tuesday, etc.
|
||||||
|
|
||||||
|
if day == 1:
|
||||||
|
day_name = "Monday"
|
||||||
|
elif day == 2:
|
||||||
|
day_name = "Tuesday"
|
||||||
|
elif day == 3:
|
||||||
|
day_name = "Wednesday"
|
||||||
|
elif day == 4:
|
||||||
|
day_name = "Thursday"
|
||||||
|
elif day == 5:
|
||||||
|
day_name = "Friday"
|
||||||
|
elif day == 6:
|
||||||
|
day_name = "Saturday"
|
||||||
|
elif day == 7:
|
||||||
|
day_name = "Sunday"
|
||||||
|
else:
|
||||||
|
day_name = "Invalid day"
|
||||||
|
|
||||||
|
print(f"Day {day} is {day_name}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Temperature Categories
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Real-world example with ranges
|
||||||
|
|
||||||
|
print("\n--- Temperature Categories ---")
|
||||||
|
|
||||||
|
celsius = 22
|
||||||
|
|
||||||
|
if celsius < 0:
|
||||||
|
category = "Freezing"
|
||||||
|
advice = "Stay indoors if possible!"
|
||||||
|
elif celsius < 10:
|
||||||
|
category = "Cold"
|
||||||
|
advice = "Wear a warm jacket."
|
||||||
|
elif celsius < 20:
|
||||||
|
category = "Cool"
|
||||||
|
advice = "A light jacket is recommended."
|
||||||
|
elif celsius < 30:
|
||||||
|
category = "Warm"
|
||||||
|
advice = "Perfect weather for outdoor activities."
|
||||||
|
else:
|
||||||
|
category = "Hot"
|
||||||
|
advice = "Stay hydrated!"
|
||||||
|
|
||||||
|
print(f"Temperature: {celsius}°C")
|
||||||
|
print(f"Category: {category}")
|
||||||
|
print(f"Advice: {advice}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. BMI Calculator Example
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Practical health-related example
|
||||||
|
|
||||||
|
print("\n--- BMI Calculator ---")
|
||||||
|
|
||||||
|
weight = 70 # kg
|
||||||
|
height = 1.75 # meters
|
||||||
|
|
||||||
|
bmi = weight / (height ** 2)
|
||||||
|
|
||||||
|
if bmi < 18.5:
|
||||||
|
category = "Underweight"
|
||||||
|
elif bmi < 25:
|
||||||
|
category = "Normal weight"
|
||||||
|
elif bmi < 30:
|
||||||
|
category = "Overweight"
|
||||||
|
else:
|
||||||
|
category = "Obese"
|
||||||
|
|
||||||
|
print(f"BMI: {bmi:.1f}")
|
||||||
|
print(f"Category: {category}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Age Group Classification
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Using elif for demographic categories
|
||||||
|
|
||||||
|
print("\n--- Age Group Classification ---")
|
||||||
|
|
||||||
|
age = 25
|
||||||
|
|
||||||
|
if age < 0:
|
||||||
|
group = "Invalid age"
|
||||||
|
elif age < 13:
|
||||||
|
group = "Child"
|
||||||
|
elif age < 20:
|
||||||
|
group = "Teenager"
|
||||||
|
elif age < 30:
|
||||||
|
group = "Young Adult"
|
||||||
|
elif age < 60:
|
||||||
|
group = "Adult"
|
||||||
|
else:
|
||||||
|
group = "Senior"
|
||||||
|
|
||||||
|
print(f"Age {age} belongs to: {group}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Combined Conditions with elif
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Using logical operators within elif
|
||||||
|
|
||||||
|
print("\n--- Combined Conditions ---")
|
||||||
|
|
||||||
|
hour = 14 # 24-hour format
|
||||||
|
is_weekend = False
|
||||||
|
|
||||||
|
if hour < 6:
|
||||||
|
greeting = "It's very early!"
|
||||||
|
elif hour < 12 and not is_weekend:
|
||||||
|
greeting = "Good morning, time to work!"
|
||||||
|
elif hour < 12 and is_weekend:
|
||||||
|
greeting = "Good morning, enjoy your day off!"
|
||||||
|
elif hour < 18:
|
||||||
|
greeting = "Good afternoon!"
|
||||||
|
elif hour < 22:
|
||||||
|
greeting = "Good evening!"
|
||||||
|
else:
|
||||||
|
greeting = "Good night!"
|
||||||
|
|
||||||
|
print(f"Hour: {hour}:00 - {greeting}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Handling User Input Example
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Menu Selection Example ---")
|
||||||
|
|
||||||
|
# Simulating user choice (in real code, you'd use input())
|
||||||
|
choice = "B"
|
||||||
|
|
||||||
|
if choice == "A" or choice == "a":
|
||||||
|
print("You selected: Start Game")
|
||||||
|
elif choice == "B" or choice == "b":
|
||||||
|
print("You selected: Load Game")
|
||||||
|
elif choice == "C" or choice == "c":
|
||||||
|
print("You selected: Settings")
|
||||||
|
elif choice == "Q" or choice == "q":
|
||||||
|
print("Goodbye!")
|
||||||
|
else:
|
||||||
|
print("Invalid choice. Please try again.")
|
||||||
243
02_control_flow/03_match_case.py
Normal file
243
02_control_flow/03_match_case.py
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_match_case.py
|
||||||
|
Topic: Pattern Matching with match-case (Python 3.10+)
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates Python's structural pattern matching introduced in
|
||||||
|
Python 3.10. It's similar to switch/case in other languages but much more
|
||||||
|
powerful with pattern matching capabilities.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Basic match-case syntax
|
||||||
|
- The underscore (_) as wildcard/default case
|
||||||
|
- Pattern matching with guards
|
||||||
|
- Matching sequences and mappings
|
||||||
|
- Capturing values in patterns
|
||||||
|
|
||||||
|
Note: This requires Python 3.10 or later!
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic match-case (Similar to switch/case)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# The simplest form of pattern matching
|
||||||
|
|
||||||
|
def get_day_name(day_number: int) -> str:
|
||||||
|
"""Convert day number to day name."""
|
||||||
|
match day_number:
|
||||||
|
case 1:
|
||||||
|
return "Monday"
|
||||||
|
case 2:
|
||||||
|
return "Tuesday"
|
||||||
|
case 3:
|
||||||
|
return "Wednesday"
|
||||||
|
case 4:
|
||||||
|
return "Thursday"
|
||||||
|
case 5:
|
||||||
|
return "Friday"
|
||||||
|
case 6:
|
||||||
|
return "Saturday"
|
||||||
|
case 7:
|
||||||
|
return "Sunday"
|
||||||
|
case _: # Default case (underscore is wildcard)
|
||||||
|
return "Invalid day"
|
||||||
|
|
||||||
|
print("--- Basic match-case ---")
|
||||||
|
print(f"Day 1: {get_day_name(1)}")
|
||||||
|
print(f"Day 5: {get_day_name(5)}")
|
||||||
|
print(f"Day 9: {get_day_name(9)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Multiple Patterns in One Case
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use the | operator to match multiple values
|
||||||
|
|
||||||
|
def categorize_day(day: str) -> str:
|
||||||
|
"""Categorize day as weekday or weekend."""
|
||||||
|
match day.lower():
|
||||||
|
case "saturday" | "sunday":
|
||||||
|
return "Weekend - Time to relax!"
|
||||||
|
case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
|
||||||
|
return "Weekday - Time to work!"
|
||||||
|
case _:
|
||||||
|
return "Unknown day"
|
||||||
|
|
||||||
|
print("\n--- Multiple Patterns ---")
|
||||||
|
print(f"Saturday: {categorize_day('Saturday')}")
|
||||||
|
print(f"Monday: {categorize_day('Monday')}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Pattern Matching with Guards
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Add conditions using 'if' after the pattern
|
||||||
|
|
||||||
|
def evaluate_score(score: int) -> str:
|
||||||
|
"""Evaluate score with guards."""
|
||||||
|
match score:
|
||||||
|
case n if n < 0:
|
||||||
|
return "Invalid score (negative)"
|
||||||
|
case n if n > 100:
|
||||||
|
return "Invalid score (over 100)"
|
||||||
|
case n if n >= 90:
|
||||||
|
return f"Grade A - Excellent! ({n}%)"
|
||||||
|
case n if n >= 80:
|
||||||
|
return f"Grade B - Good! ({n}%)"
|
||||||
|
case n if n >= 70:
|
||||||
|
return f"Grade C - Satisfactory ({n}%)"
|
||||||
|
case n if n >= 60:
|
||||||
|
return f"Grade D - Pass ({n}%)"
|
||||||
|
case _:
|
||||||
|
return f"Grade F - Fail ({score}%)"
|
||||||
|
|
||||||
|
print("\n--- Pattern with Guards ---")
|
||||||
|
print(evaluate_score(95))
|
||||||
|
print(evaluate_score(75))
|
||||||
|
print(evaluate_score(45))
|
||||||
|
print(evaluate_score(-10))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Matching Sequences (Tuples/Lists)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Match against list or tuple patterns
|
||||||
|
|
||||||
|
def describe_point(point):
|
||||||
|
"""Describe a point based on its coordinates."""
|
||||||
|
match point:
|
||||||
|
case (0, 0):
|
||||||
|
return "Origin"
|
||||||
|
case (0, y):
|
||||||
|
return f"On Y-axis at y={y}"
|
||||||
|
case (x, 0):
|
||||||
|
return f"On X-axis at x={x}"
|
||||||
|
case (x, y) if x == y:
|
||||||
|
return f"On diagonal at ({x}, {y})"
|
||||||
|
case (x, y):
|
||||||
|
return f"Point at ({x}, {y})"
|
||||||
|
case _:
|
||||||
|
return "Not a valid point"
|
||||||
|
|
||||||
|
print("\n--- Matching Sequences ---")
|
||||||
|
print(describe_point((0, 0)))
|
||||||
|
print(describe_point((0, 5)))
|
||||||
|
print(describe_point((3, 0)))
|
||||||
|
print(describe_point((4, 4)))
|
||||||
|
print(describe_point((3, 7)))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Matching with Variable Length Sequences
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use * to capture remaining elements
|
||||||
|
|
||||||
|
def analyze_list(data):
|
||||||
|
"""Analyze list structure."""
|
||||||
|
match data:
|
||||||
|
case []:
|
||||||
|
return "Empty list"
|
||||||
|
case [single]:
|
||||||
|
return f"Single element: {single}"
|
||||||
|
case [first, second]:
|
||||||
|
return f"Two elements: {first} and {second}"
|
||||||
|
case [first, *middle, last]:
|
||||||
|
return f"First: {first}, Last: {last}, Middle count: {len(middle)}"
|
||||||
|
case _:
|
||||||
|
return "Not a list"
|
||||||
|
|
||||||
|
print("\n--- Variable Length Matching ---")
|
||||||
|
print(analyze_list([]))
|
||||||
|
print(analyze_list([42]))
|
||||||
|
print(analyze_list([1, 2]))
|
||||||
|
print(analyze_list([1, 2, 3, 4, 5]))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Matching Dictionaries
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Match against dictionary patterns
|
||||||
|
|
||||||
|
def process_request(request):
|
||||||
|
"""Process different types of requests."""
|
||||||
|
match request:
|
||||||
|
case {"type": "login", "user": username}:
|
||||||
|
return f"User '{username}' is logging in"
|
||||||
|
case {"type": "logout", "user": username}:
|
||||||
|
return f"User '{username}' is logging out"
|
||||||
|
case {"type": "message", "user": username, "content": content}:
|
||||||
|
return f"Message from '{username}': {content}"
|
||||||
|
case {"type": action}:
|
||||||
|
return f"Unknown action: {action}"
|
||||||
|
case _:
|
||||||
|
return "Invalid request format"
|
||||||
|
|
||||||
|
print("\n--- Matching Dictionaries ---")
|
||||||
|
print(process_request({"type": "login", "user": "Baraa"}))
|
||||||
|
print(process_request({"type": "message", "user": "Ali", "content": "Hello!"}))
|
||||||
|
print(process_request({"type": "logout", "user": "Sara"}))
|
||||||
|
print(process_request({"type": "unknown_action"}))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Matching Class Instances
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Match against object attributes
|
||||||
|
|
||||||
|
class Point:
|
||||||
|
"""Simple point class for demonstration."""
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def classify_point(point):
|
||||||
|
"""Classify point using class matching."""
|
||||||
|
match point:
|
||||||
|
case Point(x=0, y=0):
|
||||||
|
return "At origin"
|
||||||
|
case Point(x=0, y=y):
|
||||||
|
return f"On Y-axis at {y}"
|
||||||
|
case Point(x=x, y=0):
|
||||||
|
return f"On X-axis at {x}"
|
||||||
|
case Point(x=x, y=y):
|
||||||
|
return f"At ({x}, {y})"
|
||||||
|
case _:
|
||||||
|
return "Not a Point"
|
||||||
|
|
||||||
|
print("\n--- Matching Class Instances ---")
|
||||||
|
print(classify_point(Point(0, 0)))
|
||||||
|
print(classify_point(Point(0, 7)))
|
||||||
|
print(classify_point(Point(5, 0)))
|
||||||
|
print(classify_point(Point(3, 4)))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Practical Example: Command Parser
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Real-world use case for a simple command interpreter
|
||||||
|
|
||||||
|
def execute_command(command):
|
||||||
|
"""Parse and execute simple commands."""
|
||||||
|
parts = command.split()
|
||||||
|
|
||||||
|
match parts:
|
||||||
|
case ["quit"] | ["exit"] | ["q"]:
|
||||||
|
return "Goodbye!"
|
||||||
|
case ["hello"]:
|
||||||
|
return "Hello there!"
|
||||||
|
case ["hello", name]:
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
case ["add", x, y]:
|
||||||
|
return f"Result: {int(x) + int(y)}"
|
||||||
|
case ["repeat", count, *words]:
|
||||||
|
return " ".join(words) * int(count)
|
||||||
|
case ["help"]:
|
||||||
|
return "Available: quit, hello [name], add x y, repeat n words..."
|
||||||
|
case [unknown, *_]:
|
||||||
|
return f"Unknown command: {unknown}. Type 'help' for assistance."
|
||||||
|
case _:
|
||||||
|
return "Please enter a command"
|
||||||
|
|
||||||
|
print("\n--- Command Parser Example ---")
|
||||||
|
print(f"'hello': {execute_command('hello')}")
|
||||||
|
print(f"'hello Baraa': {execute_command('hello Baraa')}")
|
||||||
|
print(f"'add 5 3': {execute_command('add 5 3')}")
|
||||||
|
print(f"'repeat 3 Hi': {execute_command('repeat 3 Hi ')}")
|
||||||
|
print(f"'help': {execute_command('help')}")
|
||||||
|
print(f"'xyz': {execute_command('xyz')}")
|
||||||
228
03_loops/01_for_loop.py
Normal file
228
03_loops/01_for_loop.py
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_for_loop.py
|
||||||
|
Topic: For Loops in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates the 'for' loop in Python, which is used to iterate
|
||||||
|
over sequences (lists, tuples, strings, ranges, etc.) or any iterable object.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Basic for loop syntax
|
||||||
|
- range() function for numeric iterations
|
||||||
|
- Iterating over different data types
|
||||||
|
- enumerate() for index and value
|
||||||
|
- zip() for parallel iteration
|
||||||
|
- Nested for loops
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic For Loop
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Iterate over items in a sequence
|
||||||
|
|
||||||
|
print("--- Basic For Loop ---")
|
||||||
|
|
||||||
|
fruits = ["apple", "banana", "cherry", "date"]
|
||||||
|
|
||||||
|
for fruit in fruits:
|
||||||
|
print(f"I like {fruit}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Using range() Function
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# range(start, stop, step) - generates numbers from start to stop-1
|
||||||
|
|
||||||
|
print("\n--- Using range() ---")
|
||||||
|
|
||||||
|
# range(5) generates 0, 1, 2, 3, 4
|
||||||
|
print("range(5):")
|
||||||
|
for i in range(5):
|
||||||
|
print(i, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# range(2, 8) generates 2, 3, 4, 5, 6, 7
|
||||||
|
print("\nrange(2, 8):")
|
||||||
|
for i in range(2, 8):
|
||||||
|
print(i, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# range(0, 10, 2) generates 0, 2, 4, 6, 8 (step of 2)
|
||||||
|
print("\nrange(0, 10, 2):")
|
||||||
|
for i in range(0, 10, 2):
|
||||||
|
print(i, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Counting backwards: range(10, 0, -1)
|
||||||
|
print("\nrange(10, 0, -1) - Countdown:")
|
||||||
|
for i in range(10, 0, -1):
|
||||||
|
print(i, end=" ")
|
||||||
|
print("Blastoff!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Iterating Over Strings
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Each character in a string is an item
|
||||||
|
|
||||||
|
print("\n--- Iterating Over Strings ---")
|
||||||
|
|
||||||
|
word = "Python"
|
||||||
|
print(f"Characters in '{word}':")
|
||||||
|
|
||||||
|
for char in word:
|
||||||
|
print(f" {char}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Using enumerate() - Get Index and Value
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# enumerate() provides both the index and the item
|
||||||
|
|
||||||
|
print("\n--- Using enumerate() ---")
|
||||||
|
|
||||||
|
languages = ["Python", "JavaScript", "C++", "Rust"]
|
||||||
|
|
||||||
|
for index, language in enumerate(languages):
|
||||||
|
print(f"{index + 1}. {language}")
|
||||||
|
|
||||||
|
# Start from a custom number
|
||||||
|
print("\nWith custom start:")
|
||||||
|
for rank, language in enumerate(languages, start=1):
|
||||||
|
print(f"Rank {rank}: {language}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Using zip() - Parallel Iteration
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# zip() combines multiple iterables and iterates over them together
|
||||||
|
|
||||||
|
print("\n--- Using zip() ---")
|
||||||
|
|
||||||
|
names = ["Alice", "Bob", "Charlie"]
|
||||||
|
ages = [25, 30, 35]
|
||||||
|
cities = ["New York", "London", "Tokyo"]
|
||||||
|
|
||||||
|
# Iterate over multiple lists simultaneously
|
||||||
|
for name, age, city in zip(names, ages, cities):
|
||||||
|
print(f"{name} is {age} years old and lives in {city}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Iterating Over Dictionaries
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Different ways to loop through dictionary data
|
||||||
|
|
||||||
|
print("\n--- Iterating Over Dictionaries ---")
|
||||||
|
|
||||||
|
person = {
|
||||||
|
"name": "Baraa",
|
||||||
|
"age": 25,
|
||||||
|
"city": "Gaza",
|
||||||
|
"profession": "Developer"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keys only (default)
|
||||||
|
print("Keys:")
|
||||||
|
for key in person:
|
||||||
|
print(f" {key}")
|
||||||
|
|
||||||
|
# Values only
|
||||||
|
print("\nValues:")
|
||||||
|
for value in person.values():
|
||||||
|
print(f" {value}")
|
||||||
|
|
||||||
|
# Both keys and values
|
||||||
|
print("\nKey-Value pairs:")
|
||||||
|
for key, value in person.items():
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Nested For Loops
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A loop inside another loop
|
||||||
|
|
||||||
|
print("\n--- Nested For Loops ---")
|
||||||
|
|
||||||
|
# Multiplication table
|
||||||
|
print("Multiplication Table (1-5):")
|
||||||
|
for i in range(1, 6):
|
||||||
|
for j in range(1, 6):
|
||||||
|
print(f"{i * j:3}", end=" ")
|
||||||
|
print() # New line after each row
|
||||||
|
|
||||||
|
# Working with 2D lists (matrices)
|
||||||
|
print("\n2D Matrix:")
|
||||||
|
matrix = [
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
[7, 8, 9]
|
||||||
|
]
|
||||||
|
|
||||||
|
for row in matrix:
|
||||||
|
for cell in row:
|
||||||
|
print(f"{cell}", end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Else Clause in For Loop
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# The else block executes when the loop completes without break
|
||||||
|
|
||||||
|
print("\n--- For-Else Clause ---")
|
||||||
|
|
||||||
|
# Search for a number
|
||||||
|
numbers = [1, 3, 5, 7, 9]
|
||||||
|
search_for = 5
|
||||||
|
|
||||||
|
for num in numbers:
|
||||||
|
if num == search_for:
|
||||||
|
print(f"Found {search_for}!")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(f"{search_for} was not found.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. List Comprehension (Related to For Loops)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A concise way to create lists using for loops
|
||||||
|
|
||||||
|
print("\n--- List Comprehension Preview ---")
|
||||||
|
|
||||||
|
# Traditional way
|
||||||
|
squares_traditional = []
|
||||||
|
for x in range(1, 6):
|
||||||
|
squares_traditional.append(x ** 2)
|
||||||
|
print(f"Traditional: {squares_traditional}")
|
||||||
|
|
||||||
|
# List comprehension way
|
||||||
|
squares_comprehension = [x ** 2 for x in range(1, 6)]
|
||||||
|
print(f"Comprehension: {squares_comprehension}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Sum of numbers
|
||||||
|
numbers = [10, 20, 30, 40, 50]
|
||||||
|
total = 0
|
||||||
|
for num in numbers:
|
||||||
|
total += num
|
||||||
|
print(f"Sum of {numbers} = {total}")
|
||||||
|
|
||||||
|
# Finding maximum
|
||||||
|
values = [23, 45, 12, 78, 34]
|
||||||
|
maximum = values[0]
|
||||||
|
for value in values:
|
||||||
|
if value > maximum:
|
||||||
|
maximum = value
|
||||||
|
print(f"Maximum in {values} = {maximum}")
|
||||||
|
|
||||||
|
# Counting vowels in a string
|
||||||
|
text = "Hello, World!"
|
||||||
|
vowels = "aeiouAEIOU"
|
||||||
|
vowel_count = 0
|
||||||
|
for char in text:
|
||||||
|
if char in vowels:
|
||||||
|
vowel_count += 1
|
||||||
|
print(f"Vowels in '{text}': {vowel_count}")
|
||||||
263
03_loops/02_while_loop.py
Normal file
263
03_loops/02_while_loop.py
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_while_loop.py
|
||||||
|
Topic: While Loops in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates the 'while' loop in Python, which repeatedly executes
|
||||||
|
a block of code as long as a condition remains True. Unlike for loops,
|
||||||
|
while loops are typically used when the number of iterations is not known
|
||||||
|
in advance.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Basic while loop syntax
|
||||||
|
- Counter-controlled loops
|
||||||
|
- Sentinel-controlled loops (input validation)
|
||||||
|
- Infinite loops and how to handle them
|
||||||
|
- While-else construct
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic While Loop
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Executes as long as condition is True
|
||||||
|
|
||||||
|
print("--- Basic While Loop ---")
|
||||||
|
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
while count <= 5:
|
||||||
|
print(f"Count: {count}")
|
||||||
|
count += 1 # Don't forget to update the condition variable!
|
||||||
|
|
||||||
|
print("Loop finished!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Counter-Controlled While Loop
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# When you know how many iterations you need
|
||||||
|
|
||||||
|
print("\n--- Counter-Controlled Loop ---")
|
||||||
|
|
||||||
|
# Countdown
|
||||||
|
countdown = 5
|
||||||
|
|
||||||
|
print("Rocket Launch Countdown:")
|
||||||
|
while countdown > 0:
|
||||||
|
print(f"T-minus {countdown}...")
|
||||||
|
countdown -= 1
|
||||||
|
print("Liftoff! 🚀")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Summing Numbers with While
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Accumulator pattern
|
||||||
|
|
||||||
|
print("\n--- Sum of Numbers ---")
|
||||||
|
|
||||||
|
# Sum numbers from 1 to 10
|
||||||
|
n = 1
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
while n <= 10:
|
||||||
|
total += n
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
print(f"Sum of 1 to 10 = {total}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Sentinel-Controlled Loop (Input Validation)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Loop until a specific condition is met
|
||||||
|
|
||||||
|
print("\n--- Sentinel-Controlled Loop ---")
|
||||||
|
|
||||||
|
# Simulating password validation (in practice, use input())
|
||||||
|
attempts = 0
|
||||||
|
max_attempts = 3
|
||||||
|
correct_password = "secret123"
|
||||||
|
|
||||||
|
# Simulated user inputs
|
||||||
|
user_inputs = ["wrong1", "wrong2", "secret123"]
|
||||||
|
|
||||||
|
while attempts < max_attempts:
|
||||||
|
# In real code: password = input("Enter password: ")
|
||||||
|
password = user_inputs[attempts]
|
||||||
|
print(f"Attempt {attempts + 1}: Entered '{password}'")
|
||||||
|
|
||||||
|
if password == correct_password:
|
||||||
|
print("Access granted! ✓")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("Incorrect password.")
|
||||||
|
attempts += 1
|
||||||
|
else:
|
||||||
|
# This runs if the loop completes without break
|
||||||
|
print("Too many failed attempts. Account locked.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. While Loop with User Simulation
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Menu-driven program example
|
||||||
|
|
||||||
|
print("\n--- Menu-Driven Example ---")
|
||||||
|
|
||||||
|
# Simulated menu choices
|
||||||
|
menu_selections = [1, 2, 4, 3]
|
||||||
|
selection_index = 0
|
||||||
|
|
||||||
|
running = True
|
||||||
|
while running:
|
||||||
|
# Simulating menu selection
|
||||||
|
choice = menu_selections[selection_index]
|
||||||
|
selection_index += 1
|
||||||
|
|
||||||
|
print(f"\nMenu: 1=Add, 2=View, 3=Exit | Choice: {choice}")
|
||||||
|
|
||||||
|
if choice == 1:
|
||||||
|
print(" ➤ Adding item...")
|
||||||
|
elif choice == 2:
|
||||||
|
print(" ➤ Viewing items...")
|
||||||
|
elif choice == 3:
|
||||||
|
print(" ➤ Exiting program...")
|
||||||
|
running = False
|
||||||
|
else:
|
||||||
|
print(" ➤ Invalid choice, try again.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Infinite Loops (and how to avoid them)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A loop that never ends - usually a bug
|
||||||
|
|
||||||
|
print("\n--- Avoiding Infinite Loops ---")
|
||||||
|
|
||||||
|
# BAD: This would run forever (commented out)
|
||||||
|
# i = 0
|
||||||
|
# while i < 5:
|
||||||
|
# print(i)
|
||||||
|
# # Missing: i += 1
|
||||||
|
|
||||||
|
# GOOD: Always update the condition variable
|
||||||
|
i = 0
|
||||||
|
while i < 5:
|
||||||
|
print(f"i = {i}")
|
||||||
|
i += 1 # This is crucial!
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. While-Else Construct
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# The else block executes if the loop completes normally (no break)
|
||||||
|
|
||||||
|
print("\n--- While-Else Construct ---")
|
||||||
|
|
||||||
|
# Example 1: Loop completes normally
|
||||||
|
n = 5
|
||||||
|
while n > 0:
|
||||||
|
print(n, end=" ")
|
||||||
|
n -= 1
|
||||||
|
else:
|
||||||
|
print("\n ↳ Countdown complete! (else block executed)")
|
||||||
|
|
||||||
|
# Example 2: Loop exits via break
|
||||||
|
print("\nSearching in list:")
|
||||||
|
items = ["a", "b", "c", "d"]
|
||||||
|
target = "b"
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
while index < len(items):
|
||||||
|
if items[index] == target:
|
||||||
|
print(f" Found '{target}' at index {index}")
|
||||||
|
break
|
||||||
|
index += 1
|
||||||
|
else:
|
||||||
|
print(f" '{target}' not found")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Nested While Loops
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A while inside another while
|
||||||
|
|
||||||
|
print("\n--- Nested While Loops ---")
|
||||||
|
|
||||||
|
# Create a simple pattern
|
||||||
|
row = 1
|
||||||
|
while row <= 4:
|
||||||
|
col = 1
|
||||||
|
while col <= row:
|
||||||
|
print("*", end=" ")
|
||||||
|
col += 1
|
||||||
|
print() # New line
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. While with Break and Continue
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Control flow within loops (preview - detailed in next file)
|
||||||
|
|
||||||
|
print("\n--- Break and Continue in While ---")
|
||||||
|
|
||||||
|
# Break example
|
||||||
|
print("Break example (stop at 5):")
|
||||||
|
i = 0
|
||||||
|
while True: # Intentional infinite loop
|
||||||
|
i += 1
|
||||||
|
if i > 5:
|
||||||
|
break
|
||||||
|
print(i, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Continue example
|
||||||
|
print("\nContinue example (skip even numbers):")
|
||||||
|
i = 0
|
||||||
|
while i < 10:
|
||||||
|
i += 1
|
||||||
|
if i % 2 == 0:
|
||||||
|
continue # Skip even numbers
|
||||||
|
print(i, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Fibonacci sequence
|
||||||
|
print("Fibonacci sequence (first 10 numbers):")
|
||||||
|
a, b = 0, 1
|
||||||
|
count = 0
|
||||||
|
while count < 10:
|
||||||
|
print(a, end=" ")
|
||||||
|
a, b = b, a + b
|
||||||
|
count += 1
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Finding digits in a number
|
||||||
|
print("\nDigits of 12345:")
|
||||||
|
number = 12345
|
||||||
|
while number > 0:
|
||||||
|
digit = number % 10
|
||||||
|
print(f" Digit: {digit}")
|
||||||
|
number //= 10
|
||||||
|
|
||||||
|
# Guessing game logic
|
||||||
|
print("\n Guess the Number Game Logic:")
|
||||||
|
secret = 7
|
||||||
|
guesses = [3, 8, 5, 7]
|
||||||
|
guess_index = 0
|
||||||
|
|
||||||
|
while guess_index < len(guesses):
|
||||||
|
guess = guesses[guess_index]
|
||||||
|
print(f" Guess: {guess}", end=" ")
|
||||||
|
|
||||||
|
if guess < secret:
|
||||||
|
print("- Too low!")
|
||||||
|
elif guess > secret:
|
||||||
|
print("- Too high!")
|
||||||
|
else:
|
||||||
|
print("- Correct! 🎉")
|
||||||
|
break
|
||||||
|
|
||||||
|
guess_index += 1
|
||||||
286
03_loops/03_break_continue.py
Normal file
286
03_loops/03_break_continue.py
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_break_continue.py
|
||||||
|
Topic: Loop Control Statements - break, continue, pass
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates loop control statements that alter the normal flow
|
||||||
|
of loop execution. These are essential tools for writing efficient and
|
||||||
|
readable loops.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- break: Exit the loop immediately
|
||||||
|
- continue: Skip to the next iteration
|
||||||
|
- pass: Do nothing (placeholder)
|
||||||
|
- Practical use cases for each
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. The break Statement
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Immediately exits the loop, skipping any remaining iterations
|
||||||
|
|
||||||
|
print("--- The break Statement ---")
|
||||||
|
|
||||||
|
# Example 1: Exit when target is found
|
||||||
|
print("Finding first even number:")
|
||||||
|
numbers = [1, 3, 5, 8, 9, 10]
|
||||||
|
|
||||||
|
for num in numbers:
|
||||||
|
print(f" Checking {num}...", end=" ")
|
||||||
|
if num % 2 == 0:
|
||||||
|
print(f"Found! {num} is even.")
|
||||||
|
break
|
||||||
|
print("Odd, continuing...")
|
||||||
|
|
||||||
|
# Example 2: Exit on specific condition
|
||||||
|
print("\nSearching for name 'Charlie':")
|
||||||
|
names = ["Alice", "Bob", "Charlie", "David", "Eve"]
|
||||||
|
|
||||||
|
for name in names:
|
||||||
|
if name == "Charlie":
|
||||||
|
print(f" ✓ Found '{name}'!")
|
||||||
|
break
|
||||||
|
print(f" Checking '{name}'...")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. The continue Statement
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Skips the rest of the current iteration and moves to the next
|
||||||
|
|
||||||
|
print("\n--- The continue Statement ---")
|
||||||
|
|
||||||
|
# Example 1: Skip negative numbers
|
||||||
|
print("Processing only positive numbers:")
|
||||||
|
values = [5, -2, 8, -1, 10, -3, 7]
|
||||||
|
|
||||||
|
for value in values:
|
||||||
|
if value < 0:
|
||||||
|
print(f" Skipping {value} (negative)")
|
||||||
|
continue
|
||||||
|
print(f" Processing {value}")
|
||||||
|
|
||||||
|
# Example 2: Skip specific items
|
||||||
|
print("\nPrinting all fruits except banana:")
|
||||||
|
fruits = ["apple", "banana", "cherry", "banana", "date"]
|
||||||
|
|
||||||
|
for fruit in fruits:
|
||||||
|
if fruit == "banana":
|
||||||
|
continue
|
||||||
|
print(f" {fruit}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. The pass Statement
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Does nothing - used as a placeholder
|
||||||
|
|
||||||
|
print("\n--- The pass Statement ---")
|
||||||
|
|
||||||
|
# Example 1: Placeholder in empty function/class
|
||||||
|
class FutureFeature:
|
||||||
|
pass # Will implement later
|
||||||
|
|
||||||
|
def not_implemented_yet():
|
||||||
|
pass # Placeholder for future code
|
||||||
|
|
||||||
|
# Example 2: Explicit "do nothing" in conditionals
|
||||||
|
print("Processing numbers (ignoring zeros for now):")
|
||||||
|
numbers = [1, 0, 2, 0, 3]
|
||||||
|
|
||||||
|
for num in numbers:
|
||||||
|
if num == 0:
|
||||||
|
pass # Explicitly do nothing (could be a TODO)
|
||||||
|
else:
|
||||||
|
print(f" Number: {num}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. break vs continue - Side by Side Comparison
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- break vs continue Comparison ---")
|
||||||
|
|
||||||
|
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
# With break - stops at 5
|
||||||
|
print("With break (stop at 5):")
|
||||||
|
for num in data:
|
||||||
|
if num == 5:
|
||||||
|
break
|
||||||
|
print(num, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# With continue - skips 5
|
||||||
|
print("With continue (skip 5):")
|
||||||
|
for num in data:
|
||||||
|
if num == 5:
|
||||||
|
continue
|
||||||
|
print(num, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. break with while Loops
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- break with while Loops ---")
|
||||||
|
|
||||||
|
# Breaking out of an infinite loop
|
||||||
|
print("Processing until 'quit' command:")
|
||||||
|
commands = ["start", "process", "load", "quit", "save"]
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
command = commands[index]
|
||||||
|
print(f" Command: {command}")
|
||||||
|
|
||||||
|
if command == "quit":
|
||||||
|
print(" ↳ Exiting loop...")
|
||||||
|
break
|
||||||
|
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. continue with while Loops
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- continue with while Loops ---")
|
||||||
|
|
||||||
|
# Skip multiples of 3
|
||||||
|
print("Numbers 1-10 (skipping multiples of 3):")
|
||||||
|
i = 0
|
||||||
|
while i < 10:
|
||||||
|
i += 1
|
||||||
|
if i % 3 == 0:
|
||||||
|
continue
|
||||||
|
print(i, end=" ")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Nested Loops with break
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# break only exits the innermost loop
|
||||||
|
|
||||||
|
print("\n--- Nested Loops with break ---")
|
||||||
|
|
||||||
|
print("Breaking inner loop only:")
|
||||||
|
for i in range(1, 4):
|
||||||
|
print(f" Outer loop: i = {i}")
|
||||||
|
for j in range(1, 4):
|
||||||
|
if j == 2:
|
||||||
|
print(" ↳ Breaking inner loop at j = 2")
|
||||||
|
break
|
||||||
|
print(f" Inner loop: j = {j}")
|
||||||
|
|
||||||
|
# Using a flag to break outer loop
|
||||||
|
print("\nBreaking outer loop with flag:")
|
||||||
|
stop_outer = False
|
||||||
|
|
||||||
|
for i in range(1, 4):
|
||||||
|
if stop_outer:
|
||||||
|
break
|
||||||
|
for j in range(1, 4):
|
||||||
|
print(f" i={i}, j={j}")
|
||||||
|
if i == 2 and j == 2:
|
||||||
|
print(" ↳ Breaking both loops!")
|
||||||
|
stop_outer = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. for-else with break
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# else block runs only if loop completes without break
|
||||||
|
|
||||||
|
print("\n--- for-else with break ---")
|
||||||
|
|
||||||
|
# Example: Searching for a prime number
|
||||||
|
def is_prime(n):
|
||||||
|
"""Check if n is prime."""
|
||||||
|
if n < 2:
|
||||||
|
return False
|
||||||
|
for i in range(2, int(n ** 0.5) + 1):
|
||||||
|
if n % i == 0:
|
||||||
|
return False # Found a divisor, not prime
|
||||||
|
return True # No divisors found, is prime
|
||||||
|
|
||||||
|
# Search for first prime in list
|
||||||
|
numbers = [4, 6, 8, 9, 11, 12]
|
||||||
|
print(f"Searching for prime in {numbers}:")
|
||||||
|
|
||||||
|
for num in numbers:
|
||||||
|
if is_prime(num):
|
||||||
|
print(f" → First prime found: {num}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(" → No primes found in the list")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Practical Example: Input Validation
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example: Input Validation ---")
|
||||||
|
|
||||||
|
# Simulating user input validation
|
||||||
|
test_inputs = ["abc", "-5", "150", "42"]
|
||||||
|
print("Validating inputs (must be 1-100):")
|
||||||
|
|
||||||
|
for input_str in test_inputs:
|
||||||
|
print(f"\n Input: '{input_str}'")
|
||||||
|
|
||||||
|
# Check if it's a number
|
||||||
|
if not input_str.lstrip('-').isdigit():
|
||||||
|
print(" ✗ Error: Not a valid number")
|
||||||
|
continue
|
||||||
|
|
||||||
|
value = int(input_str)
|
||||||
|
|
||||||
|
# Check range
|
||||||
|
if value < 1:
|
||||||
|
print(" ✗ Error: Too low (must be >= 1)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if value > 100:
|
||||||
|
print(" ✗ Error: Too high (must be <= 100)")
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(f" ✓ Valid input: {value}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("\n No valid input found!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Example: Skip Processing on Error
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example: Error Handling ---")
|
||||||
|
|
||||||
|
# Processing a list of files (simulated)
|
||||||
|
files = [
|
||||||
|
{"name": "data1.csv", "readable": True},
|
||||||
|
{"name": "data2.csv", "readable": False}, # Error
|
||||||
|
{"name": "data3.csv", "readable": True},
|
||||||
|
{"name": "corrupt.csv", "readable": True, "corrupt": True}, # Error
|
||||||
|
{"name": "data4.csv", "readable": True},
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Processing files:")
|
||||||
|
processed_count = 0
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
name = file["name"]
|
||||||
|
|
||||||
|
# Skip unreadable files
|
||||||
|
if not file.get("readable", False):
|
||||||
|
print(f" ✗ {name}: Cannot read file, skipping...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip corrupt files
|
||||||
|
if file.get("corrupt", False):
|
||||||
|
print(f" ✗ {name}: File is corrupt, skipping...")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Process the file
|
||||||
|
print(f" ✓ {name}: Processing complete")
|
||||||
|
processed_count += 1
|
||||||
|
|
||||||
|
print(f"\nTotal files processed: {processed_count}/{len(files)}")
|
||||||
287
04_data_structures/01_lists.py
Normal file
287
04_data_structures/01_lists.py
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_lists.py
|
||||||
|
Topic: Python Lists - Ordered, Mutable Collections
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates Python lists, which are ordered, mutable collections
|
||||||
|
that can store elements of any type. Lists are one of the most versatile
|
||||||
|
and commonly used data structures in Python.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Creating and accessing lists
|
||||||
|
- List methods (append, insert, remove, pop, etc.)
|
||||||
|
- Slicing and indexing
|
||||||
|
- List operations (concatenation, repetition)
|
||||||
|
- Nested lists
|
||||||
|
- List comprehensions
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Creating Lists
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Lists are created with square brackets []
|
||||||
|
|
||||||
|
print("--- Creating Lists ---")
|
||||||
|
|
||||||
|
# Empty list
|
||||||
|
empty_list = []
|
||||||
|
print(f"Empty list: {empty_list}")
|
||||||
|
|
||||||
|
# List with elements
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
fruits = ["apple", "banana", "cherry"]
|
||||||
|
mixed = [1, "hello", 3.14, True, None]
|
||||||
|
|
||||||
|
print(f"Numbers: {numbers}")
|
||||||
|
print(f"Fruits: {fruits}")
|
||||||
|
print(f"Mixed types: {mixed}")
|
||||||
|
|
||||||
|
# Using list() constructor
|
||||||
|
chars = list("Python")
|
||||||
|
print(f"From string: {chars}")
|
||||||
|
|
||||||
|
# List from range
|
||||||
|
range_list = list(range(1, 6))
|
||||||
|
print(f"From range: {range_list}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Accessing Elements (Indexing)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Lists are zero-indexed; negative indices count from the end
|
||||||
|
|
||||||
|
print("\n--- Indexing ---")
|
||||||
|
|
||||||
|
colors = ["red", "green", "blue", "yellow", "purple"]
|
||||||
|
|
||||||
|
print(f"List: {colors}")
|
||||||
|
print(f"First element (index 0): {colors[0]}")
|
||||||
|
print(f"Third element (index 2): {colors[2]}")
|
||||||
|
print(f"Last element (index -1): {colors[-1]}")
|
||||||
|
print(f"Second to last (index -2): {colors[-2]}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Slicing Lists
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Syntax: list[start:stop:step]
|
||||||
|
|
||||||
|
print("\n--- Slicing ---")
|
||||||
|
|
||||||
|
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||||
|
print(f"Original: {nums}")
|
||||||
|
|
||||||
|
print(f"nums[2:5]: {nums[2:5]}") # Elements 2, 3, 4
|
||||||
|
print(f"nums[:4]: {nums[:4]}") # First 4 elements
|
||||||
|
print(f"nums[6:]: {nums[6:]}") # From index 6 to end
|
||||||
|
print(f"nums[::2]: {nums[::2]}") # Every 2nd element
|
||||||
|
print(f"nums[::-1]: {nums[::-1]}") # Reversed list
|
||||||
|
print(f"nums[1:8:2]: {nums[1:8:2]}") # Odd indices from 1 to 7
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Modifying Lists
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Lists are mutable - you can change their contents
|
||||||
|
|
||||||
|
print("\n--- Modifying Lists ---")
|
||||||
|
|
||||||
|
languages = ["Python", "Java", "C++"]
|
||||||
|
print(f"Original: {languages}")
|
||||||
|
|
||||||
|
# Change single element
|
||||||
|
languages[1] = "JavaScript"
|
||||||
|
print(f"After changing index 1: {languages}")
|
||||||
|
|
||||||
|
# Change multiple elements with slicing
|
||||||
|
languages[0:2] = ["Rust", "Go"]
|
||||||
|
print(f"After slice replacement: {languages}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. List Methods
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Built-in methods to manipulate lists
|
||||||
|
|
||||||
|
print("\n--- List Methods ---")
|
||||||
|
|
||||||
|
# append() - Add element to end
|
||||||
|
items = [1, 2, 3]
|
||||||
|
items.append(4)
|
||||||
|
print(f"After append(4): {items}")
|
||||||
|
|
||||||
|
# extend() - Add multiple elements
|
||||||
|
items.extend([5, 6])
|
||||||
|
print(f"After extend([5, 6]): {items}")
|
||||||
|
|
||||||
|
# insert() - Add element at specific position
|
||||||
|
items.insert(0, 0) # Insert 0 at index 0
|
||||||
|
print(f"After insert(0, 0): {items}")
|
||||||
|
|
||||||
|
# remove() - Remove first occurrence of value
|
||||||
|
items.remove(3)
|
||||||
|
print(f"After remove(3): {items}")
|
||||||
|
|
||||||
|
# pop() - Remove and return element at index (default: last)
|
||||||
|
popped = items.pop()
|
||||||
|
print(f"Popped: {popped}, List now: {items}")
|
||||||
|
|
||||||
|
popped = items.pop(0)
|
||||||
|
print(f"Popped at 0: {popped}, List now: {items}")
|
||||||
|
|
||||||
|
# index() - Find index of first occurrence
|
||||||
|
fruits = ["apple", "banana", "cherry", "banana"]
|
||||||
|
print(f"\nfruits = {fruits}")
|
||||||
|
print(f"Index of 'banana': {fruits.index('banana')}")
|
||||||
|
|
||||||
|
# count() - Count occurrences
|
||||||
|
print(f"Count of 'banana': {fruits.count('banana')}")
|
||||||
|
|
||||||
|
# sort() - Sort in place
|
||||||
|
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
|
||||||
|
numbers.sort()
|
||||||
|
print(f"\nAfter sort(): {numbers}")
|
||||||
|
|
||||||
|
numbers.sort(reverse=True)
|
||||||
|
print(f"After sort(reverse=True): {numbers}")
|
||||||
|
|
||||||
|
# reverse() - Reverse in place
|
||||||
|
numbers.reverse()
|
||||||
|
print(f"After reverse(): {numbers}")
|
||||||
|
|
||||||
|
# copy() - Create a shallow copy
|
||||||
|
original = [1, 2, 3]
|
||||||
|
copied = original.copy()
|
||||||
|
print(f"\nOriginal: {original}, Copy: {copied}")
|
||||||
|
|
||||||
|
# clear() - Remove all elements
|
||||||
|
copied.clear()
|
||||||
|
print(f"After clear(): {copied}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. List Operations
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- List Operations ---")
|
||||||
|
|
||||||
|
# Concatenation (+)
|
||||||
|
list1 = [1, 2, 3]
|
||||||
|
list2 = [4, 5, 6]
|
||||||
|
combined = list1 + list2
|
||||||
|
print(f"{list1} + {list2} = {combined}")
|
||||||
|
|
||||||
|
# Repetition (*)
|
||||||
|
repeated = [0] * 5
|
||||||
|
print(f"[0] * 5 = {repeated}")
|
||||||
|
|
||||||
|
# Membership (in)
|
||||||
|
fruits = ["apple", "banana", "cherry"]
|
||||||
|
print(f"'banana' in fruits: {'banana' in fruits}")
|
||||||
|
print(f"'grape' in fruits: {'grape' in fruits}")
|
||||||
|
|
||||||
|
# Length
|
||||||
|
print(f"len(fruits): {len(fruits)}")
|
||||||
|
|
||||||
|
# Min and Max
|
||||||
|
numbers = [5, 2, 8, 1, 9]
|
||||||
|
print(f"min({numbers}): {min(numbers)}")
|
||||||
|
print(f"max({numbers}): {max(numbers)}")
|
||||||
|
print(f"sum({numbers}): {sum(numbers)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Nested Lists (2D Lists)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Nested Lists ---")
|
||||||
|
|
||||||
|
# Creating a 2D list (matrix)
|
||||||
|
matrix = [
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
[7, 8, 9]
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Matrix:")
|
||||||
|
for row in matrix:
|
||||||
|
print(f" {row}")
|
||||||
|
|
||||||
|
# Accessing elements
|
||||||
|
print(f"\nElement at [1][2]: {matrix[1][2]}") # Row 1, Column 2 = 6
|
||||||
|
print(f"Second row: {matrix[1]}")
|
||||||
|
|
||||||
|
# Modifying nested element
|
||||||
|
matrix[0][0] = 100
|
||||||
|
print(f"After matrix[0][0] = 100: {matrix[0]}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. List Comprehensions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Concise way to create lists
|
||||||
|
|
||||||
|
print("\n--- List Comprehensions ---")
|
||||||
|
|
||||||
|
# Basic list comprehension
|
||||||
|
squares = [x**2 for x in range(1, 6)]
|
||||||
|
print(f"Squares 1-5: {squares}")
|
||||||
|
|
||||||
|
# With condition
|
||||||
|
even_squares = [x**2 for x in range(1, 11) if x % 2 == 0]
|
||||||
|
print(f"Even squares: {even_squares}")
|
||||||
|
|
||||||
|
# With expression
|
||||||
|
words = ["hello", "world", "python"]
|
||||||
|
upper_words = [word.upper() for word in words]
|
||||||
|
print(f"Uppercase: {upper_words}")
|
||||||
|
|
||||||
|
# Nested comprehension (flattening)
|
||||||
|
matrix = [[1, 2], [3, 4], [5, 6]]
|
||||||
|
flattened = [num for row in matrix for num in row]
|
||||||
|
print(f"Flattened: {flattened}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Copying Lists - Shallow vs Deep
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Copying Lists ---")
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
# Shallow copy - nested objects share reference
|
||||||
|
original = [[1, 2], [3, 4]]
|
||||||
|
shallow = original.copy()
|
||||||
|
shallow[0][0] = 999 # Affects both!
|
||||||
|
print(f"Shallow copy issue:")
|
||||||
|
print(f" Original: {original}")
|
||||||
|
print(f" Shallow: {shallow}")
|
||||||
|
|
||||||
|
# Deep copy - completely independent
|
||||||
|
original = [[1, 2], [3, 4]]
|
||||||
|
deep = copy.deepcopy(original)
|
||||||
|
deep[0][0] = 999 # Only affects copy
|
||||||
|
print(f"\nDeep copy:")
|
||||||
|
print(f" Original: {original}")
|
||||||
|
print(f" Deep: {deep}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Finding unique elements while preserving order
|
||||||
|
items = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
|
||||||
|
unique = []
|
||||||
|
for item in items:
|
||||||
|
if item not in unique:
|
||||||
|
unique.append(item)
|
||||||
|
print(f"Unique elements: {unique}")
|
||||||
|
|
||||||
|
# Filtering with list comprehension
|
||||||
|
scores = [85, 42, 91, 78, 55, 99, 66]
|
||||||
|
passing = [score for score in scores if score >= 60]
|
||||||
|
print(f"Passing scores: {passing}")
|
||||||
|
|
||||||
|
# Transforming data
|
||||||
|
temperatures_c = [0, 10, 20, 30, 40]
|
||||||
|
temperatures_f = [(c * 9/5) + 32 for c in temperatures_c]
|
||||||
|
print(f"Celsius: {temperatures_c}")
|
||||||
|
print(f"Fahrenheit: {temperatures_f}")
|
||||||
286
04_data_structures/02_tuples.py
Normal file
286
04_data_structures/02_tuples.py
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_tuples.py
|
||||||
|
Topic: Python Tuples - Ordered, Immutable Collections
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates Python tuples, which are ordered, immutable collections.
|
||||||
|
Unlike lists, tuples cannot be modified after creation, making them useful
|
||||||
|
for data that should not change.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Creating tuples
|
||||||
|
- Tuple immutability
|
||||||
|
- Accessing elements
|
||||||
|
- Tuple unpacking
|
||||||
|
- When to use tuples vs lists
|
||||||
|
- Named tuples for clarity
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Creating Tuples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Tuples are created with parentheses () or just commas
|
||||||
|
|
||||||
|
print("--- Creating Tuples ---")
|
||||||
|
|
||||||
|
# Empty tuple
|
||||||
|
empty_tuple = ()
|
||||||
|
print(f"Empty tuple: {empty_tuple}")
|
||||||
|
|
||||||
|
# Tuple with elements
|
||||||
|
coordinates = (10, 20)
|
||||||
|
colors = ("red", "green", "blue")
|
||||||
|
mixed = (1, "hello", 3.14, True)
|
||||||
|
|
||||||
|
print(f"Coordinates: {coordinates}")
|
||||||
|
print(f"Colors: {colors}")
|
||||||
|
print(f"Mixed types: {mixed}")
|
||||||
|
|
||||||
|
# Single element tuple - needs trailing comma!
|
||||||
|
single = (42,) # This is a tuple
|
||||||
|
not_tuple = (42) # This is just an integer!
|
||||||
|
print(f"\nSingle element tuple: {single}, type: {type(single)}")
|
||||||
|
print(f"Without comma: {not_tuple}, type: {type(not_tuple)}")
|
||||||
|
|
||||||
|
# Tuple without parentheses (packing)
|
||||||
|
packed = 1, 2, 3
|
||||||
|
print(f"Packed tuple: {packed}, type: {type(packed)}")
|
||||||
|
|
||||||
|
# Using tuple() constructor
|
||||||
|
from_list = tuple([1, 2, 3])
|
||||||
|
from_string = tuple("Python")
|
||||||
|
print(f"From list: {from_list}")
|
||||||
|
print(f"From string: {from_string}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Accessing Elements
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Same indexing and slicing as lists
|
||||||
|
|
||||||
|
print("\n--- Accessing Elements ---")
|
||||||
|
|
||||||
|
fruits = ("apple", "banana", "cherry", "date", "elderberry")
|
||||||
|
print(f"Tuple: {fruits}")
|
||||||
|
|
||||||
|
print(f"First element: {fruits[0]}")
|
||||||
|
print(f"Last element: {fruits[-1]}")
|
||||||
|
print(f"Slice [1:4]: {fruits[1:4]}")
|
||||||
|
print(f"Every other: {fruits[::2]}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Tuple Immutability
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Tuples cannot be modified after creation
|
||||||
|
|
||||||
|
print("\n--- Immutability ---")
|
||||||
|
|
||||||
|
point = (10, 20, 30)
|
||||||
|
print(f"Original point: {point}")
|
||||||
|
|
||||||
|
# This would raise an error:
|
||||||
|
# point[0] = 100 # TypeError: 'tuple' object does not support item assignment
|
||||||
|
|
||||||
|
# However, you can create a new tuple
|
||||||
|
point = (100,) + point[1:] # Create new tuple
|
||||||
|
print(f"New point: {point}")
|
||||||
|
|
||||||
|
# Note: Mutable objects inside tuples can still be modified
|
||||||
|
mutable_inside = ([1, 2], [3, 4])
|
||||||
|
mutable_inside[0].append(3) # This works!
|
||||||
|
print(f"Mutable inside tuple: {mutable_inside}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Tuple Unpacking
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Assign tuple elements to multiple variables
|
||||||
|
|
||||||
|
print("\n--- Tuple Unpacking ---")
|
||||||
|
|
||||||
|
# Basic unpacking
|
||||||
|
coordinates = (100, 200, 300)
|
||||||
|
x, y, z = coordinates
|
||||||
|
print(f"Unpacked: x={x}, y={y}, z={z}")
|
||||||
|
|
||||||
|
# Swap values using tuple unpacking
|
||||||
|
a, b = 1, 2
|
||||||
|
print(f"Before swap: a={a}, b={b}")
|
||||||
|
a, b = b, a # Elegant swap!
|
||||||
|
print(f"After swap: a={a}, b={b}")
|
||||||
|
|
||||||
|
# Unpacking with * (extended unpacking)
|
||||||
|
numbers = (1, 2, 3, 4, 5)
|
||||||
|
first, *middle, last = numbers
|
||||||
|
print(f"\nExtended unpacking:")
|
||||||
|
print(f" first: {first}")
|
||||||
|
print(f" middle: {middle}") # This becomes a list!
|
||||||
|
print(f" last: {last}")
|
||||||
|
|
||||||
|
# Ignoring values with underscore
|
||||||
|
data = ("John", 25, "Developer", "NYC")
|
||||||
|
name, _, job, _ = data # Ignore age and city
|
||||||
|
print(f"Name: {name}, Job: {job}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Tuple Methods
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Tuples have only two methods (because they're immutable)
|
||||||
|
|
||||||
|
print("\n--- Tuple Methods ---")
|
||||||
|
|
||||||
|
numbers = (1, 2, 3, 2, 4, 2, 5)
|
||||||
|
print(f"Tuple: {numbers}")
|
||||||
|
|
||||||
|
# count() - Count occurrences
|
||||||
|
print(f"Count of 2: {numbers.count(2)}")
|
||||||
|
|
||||||
|
# index() - Find index of first occurrence
|
||||||
|
print(f"Index of 4: {numbers.index(4)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Tuple Operations
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Tuple Operations ---")
|
||||||
|
|
||||||
|
# Concatenation
|
||||||
|
t1 = (1, 2, 3)
|
||||||
|
t2 = (4, 5, 6)
|
||||||
|
combined = t1 + t2
|
||||||
|
print(f"{t1} + {t2} = {combined}")
|
||||||
|
|
||||||
|
# Repetition
|
||||||
|
repeated = (0,) * 5
|
||||||
|
print(f"(0,) * 5 = {repeated}")
|
||||||
|
|
||||||
|
# Membership
|
||||||
|
colors = ("red", "green", "blue")
|
||||||
|
print(f"'green' in colors: {'green' in colors}")
|
||||||
|
|
||||||
|
# Length
|
||||||
|
print(f"len(colors): {len(colors)}")
|
||||||
|
|
||||||
|
# Min, Max, Sum
|
||||||
|
nums = (5, 2, 8, 1, 9)
|
||||||
|
print(f"min: {min(nums)}, max: {max(nums)}, sum: {sum(nums)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Tuples as Dictionary Keys
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Tuples can be used as dictionary keys (lists cannot)
|
||||||
|
|
||||||
|
print("\n--- Tuples as Dictionary Keys ---")
|
||||||
|
|
||||||
|
# Mapping (x, y) coordinates to location names
|
||||||
|
locations = {
|
||||||
|
(40.7128, -74.0060): "New York City",
|
||||||
|
(34.0522, -118.2437): "Los Angeles",
|
||||||
|
(51.5074, -0.1278): "London"
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Locations dictionary:")
|
||||||
|
for coords, city in locations.items():
|
||||||
|
print(f" {coords}: {city}")
|
||||||
|
|
||||||
|
# Access by tuple key
|
||||||
|
coord = (40.7128, -74.0060)
|
||||||
|
print(f"\nLocation at {coord}: {locations[coord]}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Returning Multiple Values from Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions can return tuples
|
||||||
|
|
||||||
|
print("\n--- Functions Returning Tuples ---")
|
||||||
|
|
||||||
|
def get_stats(numbers):
|
||||||
|
"""Return multiple statistics as a tuple."""
|
||||||
|
return min(numbers), max(numbers), sum(numbers) / len(numbers)
|
||||||
|
|
||||||
|
data = [10, 20, 30, 40, 50]
|
||||||
|
minimum, maximum, average = get_stats(data)
|
||||||
|
print(f"Stats for {data}:")
|
||||||
|
print(f" Min: {minimum}, Max: {maximum}, Avg: {average}")
|
||||||
|
|
||||||
|
def divide_with_remainder(a, b):
|
||||||
|
"""Return quotient and remainder."""
|
||||||
|
return a // b, a % b
|
||||||
|
|
||||||
|
quotient, remainder = divide_with_remainder(17, 5)
|
||||||
|
print(f"\n17 ÷ 5 = {quotient} remainder {remainder}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Named Tuples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Tuples with named fields for clarity
|
||||||
|
|
||||||
|
print("\n--- Named Tuples ---")
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
# Define a named tuple type
|
||||||
|
Person = namedtuple("Person", ["name", "age", "city"])
|
||||||
|
|
||||||
|
# Create instances
|
||||||
|
person1 = Person("Baraa", 25, "Gaza")
|
||||||
|
person2 = Person(name="Sara", age=30, city="Cairo")
|
||||||
|
|
||||||
|
print(f"Person 1: {person1}")
|
||||||
|
print(f"Person 2: {person2}")
|
||||||
|
|
||||||
|
# Access by name or index
|
||||||
|
print(f"\nAccess by name: {person1.name}")
|
||||||
|
print(f"Access by index: {person1[0]}")
|
||||||
|
|
||||||
|
# Named tuple is still immutable
|
||||||
|
# person1.age = 26 # This would raise an error
|
||||||
|
|
||||||
|
# Convert to dictionary
|
||||||
|
print(f"As dict: {person1._asdict()}")
|
||||||
|
|
||||||
|
# Create from dictionary
|
||||||
|
data = {"name": "Ali", "age": 28, "city": "Dubai"}
|
||||||
|
person3 = Person(**data)
|
||||||
|
print(f"From dict: {person3}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. When to Use Tuples vs Lists
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Tuples vs Lists ---")
|
||||||
|
|
||||||
|
# Use TUPLES when:
|
||||||
|
# 1. Data should not change
|
||||||
|
rgb_red = (255, 0, 0) # Color values shouldn't change
|
||||||
|
|
||||||
|
# 2. Using as dictionary keys
|
||||||
|
grid_positions = {(0, 0): "start", (9, 9): "end"}
|
||||||
|
|
||||||
|
# 3. Returning multiple values from functions
|
||||||
|
def get_point(): return (10, 20)
|
||||||
|
|
||||||
|
# 4. Heterogeneous data (different types, specific meaning)
|
||||||
|
person = ("John", 30, "Engineer") # name, age, job
|
||||||
|
|
||||||
|
# Use LISTS when:
|
||||||
|
# 1. Data will be modified
|
||||||
|
shopping_cart = ["apple", "bread"] # Items will be added/removed
|
||||||
|
|
||||||
|
# 2. Homogeneous collection
|
||||||
|
scores = [85, 90, 78, 92] # All same type, will be processed together
|
||||||
|
|
||||||
|
# 3. Order matters and you need to sort/shuffle
|
||||||
|
rankings = [3, 1, 4, 1, 5]
|
||||||
|
rankings.sort() # Lists are sortable in place
|
||||||
|
|
||||||
|
print("Summary:")
|
||||||
|
print(" Tuples: Immutable, hashable, for fixed data")
|
||||||
|
print(" Lists: Mutable, flexible, for changing data")
|
||||||
|
|
||||||
|
# Performance note: Tuples are slightly faster and use less memory
|
||||||
|
import sys
|
||||||
|
list_size = sys.getsizeof([1, 2, 3, 4, 5])
|
||||||
|
tuple_size = sys.getsizeof((1, 2, 3, 4, 5))
|
||||||
|
print(f"\nMemory: List={list_size} bytes, Tuple={tuple_size} bytes")
|
||||||
299
04_data_structures/03_sets.py
Normal file
299
04_data_structures/03_sets.py
Normal file
|
|
@ -0,0 +1,299 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_sets.py
|
||||||
|
Topic: Python Sets - Unordered Collections of Unique Elements
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates Python sets, which are unordered collections that
|
||||||
|
store only unique elements. Sets are highly optimized for membership testing
|
||||||
|
and mathematical set operations.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Creating sets
|
||||||
|
- Adding and removing elements
|
||||||
|
- Set operations (union, intersection, difference)
|
||||||
|
- Frozen sets (immutable sets)
|
||||||
|
- Use cases for sets
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Creating Sets
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Sets are created with curly braces {} or set()
|
||||||
|
|
||||||
|
print("--- Creating Sets ---")
|
||||||
|
|
||||||
|
# Using curly braces
|
||||||
|
fruits = {"apple", "banana", "cherry"}
|
||||||
|
print(f"Fruits set: {fruits}")
|
||||||
|
|
||||||
|
# Duplicates are automatically removed
|
||||||
|
numbers = {1, 2, 2, 3, 3, 3, 4}
|
||||||
|
print(f"Numbers (duplicates removed): {numbers}")
|
||||||
|
|
||||||
|
# Empty set - must use set(), not {}
|
||||||
|
empty_set = set() # {} creates an empty dictionary!
|
||||||
|
print(f"Empty set: {empty_set}, type: {type(empty_set)}")
|
||||||
|
print(f"Empty dict: {{}}, type: {type({})}")
|
||||||
|
|
||||||
|
# From other iterables
|
||||||
|
from_list = set([1, 2, 3, 4, 5])
|
||||||
|
from_string = set("hello") # Unique characters
|
||||||
|
from_tuple = set((10, 20, 30))
|
||||||
|
print(f"\nFrom list: {from_list}")
|
||||||
|
print(f"From string 'hello': {from_string}")
|
||||||
|
print(f"From tuple: {from_tuple}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Set Characteristics
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Unordered, no duplicates, no indexing
|
||||||
|
|
||||||
|
print("\n--- Set Characteristics ---")
|
||||||
|
|
||||||
|
colors = {"red", "green", "blue"}
|
||||||
|
|
||||||
|
# Sets are unordered - no indexing
|
||||||
|
# colors[0] # This would raise an error!
|
||||||
|
|
||||||
|
# Check membership (very fast - O(1))
|
||||||
|
print(f"'red' in colors: {'red' in colors}")
|
||||||
|
print(f"'yellow' in colors: {'yellow' in colors}")
|
||||||
|
|
||||||
|
# Length
|
||||||
|
print(f"Number of colors: {len(colors)}")
|
||||||
|
|
||||||
|
# Sets can only contain immutable (hashable) elements
|
||||||
|
valid_set = {1, "hello", (1, 2), 3.14} # OK
|
||||||
|
# invalid_set = {1, [2, 3]} # Error! Lists are not hashable
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Adding and Removing Elements
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Adding and Removing Elements ---")
|
||||||
|
|
||||||
|
languages = {"Python", "Java"}
|
||||||
|
print(f"Initial: {languages}")
|
||||||
|
|
||||||
|
# add() - Add single element
|
||||||
|
languages.add("JavaScript")
|
||||||
|
print(f"After add('JavaScript'): {languages}")
|
||||||
|
|
||||||
|
# Adding duplicate has no effect
|
||||||
|
languages.add("Python")
|
||||||
|
print(f"After add('Python'): {languages}")
|
||||||
|
|
||||||
|
# update() - Add multiple elements
|
||||||
|
languages.update(["C++", "Rust"])
|
||||||
|
print(f"After update(['C++', 'Rust']): {languages}")
|
||||||
|
|
||||||
|
# remove() - Remove element (raises error if not found)
|
||||||
|
languages.remove("Java")
|
||||||
|
print(f"After remove('Java'): {languages}")
|
||||||
|
|
||||||
|
# discard() - Remove element (no error if not found)
|
||||||
|
languages.discard("Go") # No error even though "Go" isn't in set
|
||||||
|
print(f"After discard('Go'): {languages}")
|
||||||
|
|
||||||
|
# pop() - Remove and return an arbitrary element
|
||||||
|
popped = languages.pop()
|
||||||
|
print(f"Popped: {popped}, Remaining: {languages}")
|
||||||
|
|
||||||
|
# clear() - Remove all elements
|
||||||
|
temp_set = {1, 2, 3}
|
||||||
|
temp_set.clear()
|
||||||
|
print(f"After clear(): {temp_set}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Set Operations - Mathematical Operations
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Set Operations ---")
|
||||||
|
|
||||||
|
a = {1, 2, 3, 4, 5}
|
||||||
|
b = {4, 5, 6, 7, 8}
|
||||||
|
|
||||||
|
print(f"Set A: {a}")
|
||||||
|
print(f"Set B: {b}")
|
||||||
|
|
||||||
|
# Union - Elements in A or B or both
|
||||||
|
union = a | b # or a.union(b)
|
||||||
|
print(f"\nUnion (A | B): {union}")
|
||||||
|
|
||||||
|
# Intersection - Elements in both A and B
|
||||||
|
intersection = a & b # or a.intersection(b)
|
||||||
|
print(f"Intersection (A & B): {intersection}")
|
||||||
|
|
||||||
|
# Difference - Elements in A but not in B
|
||||||
|
difference = a - b # or a.difference(b)
|
||||||
|
print(f"Difference (A - B): {difference}")
|
||||||
|
|
||||||
|
difference_ba = b - a
|
||||||
|
print(f"Difference (B - A): {difference_ba}")
|
||||||
|
|
||||||
|
# Symmetric Difference - Elements in A or B but not both
|
||||||
|
sym_diff = a ^ b # or a.symmetric_difference(b)
|
||||||
|
print(f"Symmetric Difference (A ^ B): {sym_diff}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Set Comparison Operations
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Set Comparisons ---")
|
||||||
|
|
||||||
|
numbers = {1, 2, 3, 4, 5}
|
||||||
|
subset = {2, 3}
|
||||||
|
same = {1, 2, 3, 4, 5}
|
||||||
|
different = {6, 7, 8}
|
||||||
|
|
||||||
|
print(f"numbers: {numbers}")
|
||||||
|
print(f"subset: {subset}")
|
||||||
|
|
||||||
|
# issubset() - Is subset contained in numbers?
|
||||||
|
print(f"\nsubset.issubset(numbers): {subset.issubset(numbers)}")
|
||||||
|
print(f"subset <= numbers: {subset <= numbers}")
|
||||||
|
|
||||||
|
# issuperset() - Does numbers contain subset?
|
||||||
|
print(f"numbers.issuperset(subset): {numbers.issuperset(subset)}")
|
||||||
|
print(f"numbers >= subset: {numbers >= subset}")
|
||||||
|
|
||||||
|
# isdisjoint() - Do they share any elements?
|
||||||
|
print(f"\nnumbers.isdisjoint(different): {numbers.isdisjoint(different)}")
|
||||||
|
print(f"numbers.isdisjoint(subset): {numbers.isdisjoint(subset)}")
|
||||||
|
|
||||||
|
# Equality
|
||||||
|
print(f"\nnumbers == same: {numbers == same}")
|
||||||
|
print(f"numbers == subset: {numbers == subset}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Update Operations (Modify in Place)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Update Operations ---")
|
||||||
|
|
||||||
|
# These modify the set in place instead of creating new ones
|
||||||
|
|
||||||
|
x = {1, 2, 3}
|
||||||
|
y = {3, 4, 5}
|
||||||
|
|
||||||
|
# update() - Union in place (|=)
|
||||||
|
x_copy = x.copy()
|
||||||
|
x_copy.update(y) # or x_copy |= y
|
||||||
|
print(f"After update (union): {x_copy}")
|
||||||
|
|
||||||
|
# intersection_update() - Intersection in place (&=)
|
||||||
|
x_copy = x.copy()
|
||||||
|
x_copy.intersection_update(y) # or x_copy &= y
|
||||||
|
print(f"After intersection_update: {x_copy}")
|
||||||
|
|
||||||
|
# difference_update() - Difference in place (-=)
|
||||||
|
x_copy = x.copy()
|
||||||
|
x_copy.difference_update(y) # or x_copy -= y
|
||||||
|
print(f"After difference_update: {x_copy}")
|
||||||
|
|
||||||
|
# symmetric_difference_update() - Symmetric difference in place (^=)
|
||||||
|
x_copy = x.copy()
|
||||||
|
x_copy.symmetric_difference_update(y) # or x_copy ^= y
|
||||||
|
print(f"After symmetric_difference_update: {x_copy}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Frozen Sets (Immutable Sets)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Frozen Sets ---")
|
||||||
|
|
||||||
|
# Frozen sets are immutable versions of sets
|
||||||
|
frozen = frozenset([1, 2, 3, 4, 5])
|
||||||
|
print(f"Frozen set: {frozen}")
|
||||||
|
|
||||||
|
# Can perform operations but not modify
|
||||||
|
print(f"3 in frozen: {3 in frozen}")
|
||||||
|
print(f"len(frozen): {len(frozen)}")
|
||||||
|
|
||||||
|
# These would raise errors:
|
||||||
|
# frozen.add(6)
|
||||||
|
# frozen.remove(1)
|
||||||
|
|
||||||
|
# Frozen sets can be used as dictionary keys or set elements
|
||||||
|
nested = {frozenset([1, 2]), frozenset([3, 4])}
|
||||||
|
print(f"Set of frozensets: {nested}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Set Comprehensions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Set Comprehensions ---")
|
||||||
|
|
||||||
|
# Basic set comprehension
|
||||||
|
squares = {x**2 for x in range(1, 6)}
|
||||||
|
print(f"Squares: {squares}")
|
||||||
|
|
||||||
|
# With condition
|
||||||
|
even_squares = {x**2 for x in range(1, 11) if x % 2 == 0}
|
||||||
|
print(f"Even squares: {even_squares}")
|
||||||
|
|
||||||
|
# From string - unique vowels
|
||||||
|
text = "Hello, World!"
|
||||||
|
vowels = {char.lower() for char in text if char.lower() in "aeiou"}
|
||||||
|
print(f"Unique vowels in '{text}': {vowels}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Practical Use Cases
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Use Cases ---")
|
||||||
|
|
||||||
|
# 1. Remove duplicates from a list
|
||||||
|
my_list = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
|
||||||
|
unique_list = list(set(my_list))
|
||||||
|
print(f"Remove duplicates: {my_list} → {unique_list}")
|
||||||
|
|
||||||
|
# 2. Find common elements
|
||||||
|
list1 = [1, 2, 3, 4, 5]
|
||||||
|
list2 = [4, 5, 6, 7, 8]
|
||||||
|
common = set(list1) & set(list2)
|
||||||
|
print(f"Common elements: {common}")
|
||||||
|
|
||||||
|
# 3. Find unique elements across lists
|
||||||
|
all_elements = set(list1) | set(list2)
|
||||||
|
print(f"All unique elements: {all_elements}")
|
||||||
|
|
||||||
|
# 4. Check if all required items exist
|
||||||
|
required_skills = {"Python", "SQL", "Git"}
|
||||||
|
candidate_skills = {"Python", "SQL", "Git", "Docker", "AWS"}
|
||||||
|
has_all_required = required_skills.issubset(candidate_skills)
|
||||||
|
print(f"\nCandidate has all required skills: {has_all_required}")
|
||||||
|
|
||||||
|
# 5. Find missing items
|
||||||
|
available_items = {"apple", "banana", "orange"}
|
||||||
|
shopping_list = {"apple", "milk", "bread", "banana"}
|
||||||
|
need_to_buy = shopping_list - available_items
|
||||||
|
print(f"Need to buy: {need_to_buy}")
|
||||||
|
|
||||||
|
# 6. Count unique words
|
||||||
|
text = "the quick brown fox jumps over the lazy dog"
|
||||||
|
unique_words = set(text.split())
|
||||||
|
print(f"\nUnique words in text: {len(unique_words)}")
|
||||||
|
print(f"Words: {unique_words}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Performance: Sets vs Lists for Membership Testing
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Performance Note ---")
|
||||||
|
|
||||||
|
# Sets use hash tables - O(1) lookup
|
||||||
|
# Lists use linear search - O(n) lookup
|
||||||
|
|
||||||
|
big_list = list(range(10000))
|
||||||
|
big_set = set(range(10000))
|
||||||
|
|
||||||
|
# For membership testing:
|
||||||
|
# 9999 in big_list # Slow - checks up to 10000 elements
|
||||||
|
# 9999 in big_set # Fast - direct hash lookup
|
||||||
|
|
||||||
|
print("For membership testing, sets are MUCH faster than lists!")
|
||||||
|
print("Use sets when you frequently check if items exist in a collection.")
|
||||||
351
04_data_structures/04_dictionaries.py
Normal file
351
04_data_structures/04_dictionaries.py
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 04_dictionaries.py
|
||||||
|
Topic: Python Dictionaries - Key-Value Pair Collections
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates Python dictionaries, which store data as key-value pairs.
|
||||||
|
Dictionaries are extremely versatile and provide fast access to values using
|
||||||
|
their associated keys.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Creating and accessing dictionaries
|
||||||
|
- Adding, modifying, and removing key-value pairs
|
||||||
|
- Dictionary methods
|
||||||
|
- Iterating over dictionaries
|
||||||
|
- Nested dictionaries
|
||||||
|
- Dictionary comprehensions
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Creating Dictionaries
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Dictionaries use curly braces with key: value syntax
|
||||||
|
|
||||||
|
print("--- Creating Dictionaries ---")
|
||||||
|
|
||||||
|
# Empty dictionary
|
||||||
|
empty_dict = {}
|
||||||
|
also_empty = dict()
|
||||||
|
print(f"Empty dict: {empty_dict}")
|
||||||
|
|
||||||
|
# Dictionary with elements
|
||||||
|
person = {
|
||||||
|
"name": "Baraa",
|
||||||
|
"age": 25,
|
||||||
|
"city": "Gaza",
|
||||||
|
"is_developer": True
|
||||||
|
}
|
||||||
|
print(f"Person: {person}")
|
||||||
|
|
||||||
|
# Using dict() constructor
|
||||||
|
from_pairs = dict([("a", 1), ("b", 2), ("c", 3)])
|
||||||
|
from_kwargs = dict(x=10, y=20, z=30)
|
||||||
|
print(f"From pairs: {from_pairs}")
|
||||||
|
print(f"From kwargs: {from_kwargs}")
|
||||||
|
|
||||||
|
# Keys can be any immutable type
|
||||||
|
mixed_keys = {
|
||||||
|
"string_key": "value1",
|
||||||
|
42: "value2",
|
||||||
|
(1, 2): "value3", # Tuple as key
|
||||||
|
True: "value4"
|
||||||
|
}
|
||||||
|
print(f"Mixed keys: {mixed_keys}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Accessing Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Accessing Values ---")
|
||||||
|
|
||||||
|
student = {
|
||||||
|
"name": "Ali",
|
||||||
|
"age": 20,
|
||||||
|
"grades": [85, 90, 78],
|
||||||
|
"active": True
|
||||||
|
}
|
||||||
|
|
||||||
|
# Using square brackets
|
||||||
|
print(f"Name: {student['name']}")
|
||||||
|
print(f"Grades: {student['grades']}")
|
||||||
|
|
||||||
|
# Using get() - safer, returns None if key doesn't exist
|
||||||
|
print(f"Age: {student.get('age')}")
|
||||||
|
print(f"Email: {student.get('email')}") # Returns None
|
||||||
|
print(f"Email with default: {student.get('email', 'Not provided')}")
|
||||||
|
|
||||||
|
# Accessing nested values
|
||||||
|
print(f"First grade: {student['grades'][0]}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Modifying Dictionaries
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Modifying Dictionaries ---")
|
||||||
|
|
||||||
|
config = {"theme": "dark", "font_size": 14}
|
||||||
|
print(f"Original: {config}")
|
||||||
|
|
||||||
|
# Update existing key
|
||||||
|
config["font_size"] = 16
|
||||||
|
print(f"After update: {config}")
|
||||||
|
|
||||||
|
# Add new key
|
||||||
|
config["language"] = "English"
|
||||||
|
print(f"After adding key: {config}")
|
||||||
|
|
||||||
|
# Update multiple keys at once
|
||||||
|
config.update({"theme": "light", "auto_save": True})
|
||||||
|
print(f"After update(): {config}")
|
||||||
|
|
||||||
|
# Using setdefault() - only adds if key doesn't exist
|
||||||
|
config.setdefault("font_size", 20) # Won't change (key exists)
|
||||||
|
config.setdefault("new_key", "default_value") # Will add
|
||||||
|
print(f"After setdefault(): {config}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Removing Items
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Removing Items ---")
|
||||||
|
|
||||||
|
data = {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
|
||||||
|
print(f"Original: {data}")
|
||||||
|
|
||||||
|
# pop() - Remove and return value
|
||||||
|
removed = data.pop("c")
|
||||||
|
print(f"Popped 'c': {removed}, Dict: {data}")
|
||||||
|
|
||||||
|
# pop() with default - no error if key missing
|
||||||
|
removed = data.pop("z", "Not found")
|
||||||
|
print(f"Popped 'z': {removed}")
|
||||||
|
|
||||||
|
# popitem() - Remove and return last inserted pair
|
||||||
|
last_item = data.popitem()
|
||||||
|
print(f"Popitem: {last_item}, Dict: {data}")
|
||||||
|
|
||||||
|
# del - Delete specific key
|
||||||
|
del data["b"]
|
||||||
|
print(f"After del 'b': {data}")
|
||||||
|
|
||||||
|
# clear() - Remove all items
|
||||||
|
temp = {"x": 1, "y": 2}
|
||||||
|
temp.clear()
|
||||||
|
print(f"After clear(): {temp}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Dictionary Methods
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Dictionary Methods ---")
|
||||||
|
|
||||||
|
info = {"name": "Sara", "age": 28, "job": "Engineer"}
|
||||||
|
|
||||||
|
# keys() - Get all keys
|
||||||
|
print(f"Keys: {list(info.keys())}")
|
||||||
|
|
||||||
|
# values() - Get all values
|
||||||
|
print(f"Values: {list(info.values())}")
|
||||||
|
|
||||||
|
# items() - Get all key-value pairs
|
||||||
|
print(f"Items: {list(info.items())}")
|
||||||
|
|
||||||
|
# Check if key exists
|
||||||
|
print(f"\n'name' in info: {'name' in info}")
|
||||||
|
print(f"'email' in info: {'email' in info}")
|
||||||
|
|
||||||
|
# Copy
|
||||||
|
original = {"a": 1, "b": 2}
|
||||||
|
copied = original.copy()
|
||||||
|
copied["c"] = 3
|
||||||
|
print(f"\nOriginal: {original}")
|
||||||
|
print(f"Copy: {copied}")
|
||||||
|
|
||||||
|
# fromkeys() - Create dict with same value for all keys
|
||||||
|
keys = ["x", "y", "z"]
|
||||||
|
default_dict = dict.fromkeys(keys, 0)
|
||||||
|
print(f"From keys: {default_dict}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Iterating Over Dictionaries
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Iterating Over Dictionaries ---")
|
||||||
|
|
||||||
|
scores = {"Alice": 95, "Bob": 87, "Charlie": 92}
|
||||||
|
|
||||||
|
# Iterate over keys (default)
|
||||||
|
print("Keys:")
|
||||||
|
for key in scores:
|
||||||
|
print(f" {key}")
|
||||||
|
|
||||||
|
# Iterate over values
|
||||||
|
print("\nValues:")
|
||||||
|
for value in scores.values():
|
||||||
|
print(f" {value}")
|
||||||
|
|
||||||
|
# Iterate over key-value pairs
|
||||||
|
print("\nKey-Value pairs:")
|
||||||
|
for name, score in scores.items():
|
||||||
|
print(f" {name}: {score}")
|
||||||
|
|
||||||
|
# With enumerate (if you need index)
|
||||||
|
print("\nWith index:")
|
||||||
|
for idx, (name, score) in enumerate(scores.items(), 1):
|
||||||
|
print(f" {idx}. {name} scored {score}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Nested Dictionaries
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Nested Dictionaries ---")
|
||||||
|
|
||||||
|
# Dictionary containing dictionaries
|
||||||
|
company = {
|
||||||
|
"engineering": {
|
||||||
|
"lead": "Alice",
|
||||||
|
"members": ["Bob", "Charlie"],
|
||||||
|
"budget": 50000
|
||||||
|
},
|
||||||
|
"marketing": {
|
||||||
|
"lead": "David",
|
||||||
|
"members": ["Eve", "Frank"],
|
||||||
|
"budget": 30000
|
||||||
|
},
|
||||||
|
"hr": {
|
||||||
|
"lead": "Grace",
|
||||||
|
"members": ["Henry"],
|
||||||
|
"budget": 20000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Company structure:")
|
||||||
|
for dept, details in company.items():
|
||||||
|
print(f"\n {dept.title()} Department:")
|
||||||
|
print(f" Lead: {details['lead']}")
|
||||||
|
print(f" Members: {details['members']}")
|
||||||
|
print(f" Budget: ${details['budget']:,}")
|
||||||
|
|
||||||
|
# Access nested values
|
||||||
|
print(f"\nEngineering lead: {company['engineering']['lead']}")
|
||||||
|
print(f"Marketing members: {company['marketing']['members']}")
|
||||||
|
|
||||||
|
# Modify nested value
|
||||||
|
company['hr']['budget'] = 25000
|
||||||
|
print(f"Updated HR budget: {company['hr']['budget']}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Dictionary Comprehensions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Dictionary Comprehensions ---")
|
||||||
|
|
||||||
|
# Basic dictionary comprehension
|
||||||
|
squares = {x: x**2 for x in range(1, 6)}
|
||||||
|
print(f"Squares: {squares}")
|
||||||
|
|
||||||
|
# With condition
|
||||||
|
even_squares = {x: x**2 for x in range(1, 11) if x % 2 == 0}
|
||||||
|
print(f"Even squares: {even_squares}")
|
||||||
|
|
||||||
|
# Transform existing dictionary
|
||||||
|
original = {"a": 1, "b": 2, "c": 3}
|
||||||
|
doubled = {k: v * 2 for k, v in original.items()}
|
||||||
|
print(f"Doubled values: {doubled}")
|
||||||
|
|
||||||
|
# Swap keys and values
|
||||||
|
flipped = {v: k for k, v in original.items()}
|
||||||
|
print(f"Flipped: {flipped}")
|
||||||
|
|
||||||
|
# Filter dictionary
|
||||||
|
scores = {"Alice": 95, "Bob": 67, "Charlie": 82, "David": 55}
|
||||||
|
passing = {name: score for name, score in scores.items() if score >= 70}
|
||||||
|
print(f"Passing students: {passing}")
|
||||||
|
|
||||||
|
# From two lists
|
||||||
|
keys = ["name", "age", "city"]
|
||||||
|
values = ["John", 30, "NYC"]
|
||||||
|
combined = {k: v for k, v in zip(keys, values)}
|
||||||
|
print(f"Combined: {combined}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Merging Dictionaries
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Merging Dictionaries ---")
|
||||||
|
|
||||||
|
dict1 = {"a": 1, "b": 2}
|
||||||
|
dict2 = {"c": 3, "d": 4}
|
||||||
|
dict3 = {"b": 99, "e": 5} # Note: 'b' also exists in dict1
|
||||||
|
|
||||||
|
# Method 1: update() - modifies in place
|
||||||
|
merged = dict1.copy()
|
||||||
|
merged.update(dict2)
|
||||||
|
print(f"Using update(): {merged}")
|
||||||
|
|
||||||
|
# Method 2: ** unpacking (Python 3.5+)
|
||||||
|
merged = {**dict1, **dict2}
|
||||||
|
print(f"Using ** unpacking: {merged}")
|
||||||
|
|
||||||
|
# Method 3: | operator (Python 3.9+)
|
||||||
|
merged = dict1 | dict2
|
||||||
|
print(f"Using | operator: {merged}")
|
||||||
|
|
||||||
|
# Later values overwrite earlier ones
|
||||||
|
merged = {**dict1, **dict3} # 'b' will be 99
|
||||||
|
print(f"With overlap: {merged}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# 1. Word frequency counter
|
||||||
|
text = "the quick brown fox jumps over the lazy dog the fox"
|
||||||
|
words = text.split()
|
||||||
|
frequency = {}
|
||||||
|
for word in words:
|
||||||
|
frequency[word] = frequency.get(word, 0) + 1
|
||||||
|
print(f"Word frequency: {frequency}")
|
||||||
|
|
||||||
|
# 2. Grouping items
|
||||||
|
students = [
|
||||||
|
{"name": "Alice", "grade": "A"},
|
||||||
|
{"name": "Bob", "grade": "B"},
|
||||||
|
{"name": "Charlie", "grade": "A"},
|
||||||
|
{"name": "David", "grade": "B"},
|
||||||
|
{"name": "Eve", "grade": "A"}
|
||||||
|
]
|
||||||
|
|
||||||
|
by_grade = {}
|
||||||
|
for student in students:
|
||||||
|
grade = student["grade"]
|
||||||
|
if grade not in by_grade:
|
||||||
|
by_grade[grade] = []
|
||||||
|
by_grade[grade].append(student["name"])
|
||||||
|
print(f"\nStudents by grade: {by_grade}")
|
||||||
|
|
||||||
|
# 3. Using dict as simple cache/memo
|
||||||
|
cache = {}
|
||||||
|
|
||||||
|
def fibonacci(n):
|
||||||
|
if n in cache:
|
||||||
|
return cache[n]
|
||||||
|
if n <= 1:
|
||||||
|
return n
|
||||||
|
result = fibonacci(n-1) + fibonacci(n-2)
|
||||||
|
cache[n] = result
|
||||||
|
return result
|
||||||
|
|
||||||
|
print(f"\nFibonacci(10): {fibonacci(10)}")
|
||||||
|
print(f"Cache: {cache}")
|
||||||
|
|
||||||
|
# 4. Configuration settings
|
||||||
|
default_config = {"theme": "dark", "font": "Arial", "size": 12}
|
||||||
|
user_config = {"theme": "light", "size": 14}
|
||||||
|
final_config = {**default_config, **user_config}
|
||||||
|
print(f"\nFinal config: {final_config}")
|
||||||
337
05_functions/01_function_basics.py
Normal file
337
05_functions/01_function_basics.py
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_function_basics.py
|
||||||
|
Topic: Python Functions - Basic Concepts
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates the fundamentals of functions in Python. Functions
|
||||||
|
are reusable blocks of code that perform specific tasks, making your code
|
||||||
|
more organized, readable, and maintainable.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Defining functions with def
|
||||||
|
- Calling functions
|
||||||
|
- Function documentation (docstrings)
|
||||||
|
- Variable scope (local vs global)
|
||||||
|
- Basic parameters
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Defining and Calling Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use 'def' keyword to define a function
|
||||||
|
|
||||||
|
print("--- Defining and Calling Functions ---")
|
||||||
|
|
||||||
|
# Simple function with no parameters
|
||||||
|
def greet():
|
||||||
|
"""Print a greeting message."""
|
||||||
|
print("Hello, World!")
|
||||||
|
|
||||||
|
# Call the function
|
||||||
|
greet()
|
||||||
|
|
||||||
|
# Function with a parameter
|
||||||
|
def greet_person(name):
|
||||||
|
"""Greet a specific person."""
|
||||||
|
print(f"Hello, {name}!")
|
||||||
|
|
||||||
|
greet_person("Baraa")
|
||||||
|
greet_person("Sara")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Function with Multiple Parameters
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Multiple Parameters ---")
|
||||||
|
|
||||||
|
def introduce(name, age, city):
|
||||||
|
"""Introduce a person with their details."""
|
||||||
|
print(f"My name is {name}, I am {age} years old, and I live in {city}.")
|
||||||
|
|
||||||
|
introduce("Ali", 25, "Cairo")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Return Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions can return values using 'return'
|
||||||
|
|
||||||
|
print("\n--- Return Values ---")
|
||||||
|
|
||||||
|
def add(a, b):
|
||||||
|
"""Add two numbers and return the result."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
result = add(5, 3)
|
||||||
|
print(f"5 + 3 = {result}")
|
||||||
|
|
||||||
|
# Using returned value in expressions
|
||||||
|
total = add(10, 20) + add(5, 5)
|
||||||
|
print(f"(10+20) + (5+5) = {total}")
|
||||||
|
|
||||||
|
# Function without explicit return returns None
|
||||||
|
def print_message(msg):
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
result = print_message("Hello")
|
||||||
|
print(f"Function without return: {result}") # None
|
||||||
|
|
||||||
|
# Early return
|
||||||
|
def get_grade(score):
|
||||||
|
"""Return letter grade based on score."""
|
||||||
|
if score >= 90:
|
||||||
|
return "A"
|
||||||
|
if score >= 80:
|
||||||
|
return "B"
|
||||||
|
if score >= 70:
|
||||||
|
return "C"
|
||||||
|
if score >= 60:
|
||||||
|
return "D"
|
||||||
|
return "F"
|
||||||
|
|
||||||
|
print(f"\nScore 85 → Grade {get_grade(85)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Docstrings - Function Documentation
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use triple quotes to document what a function does
|
||||||
|
|
||||||
|
print("\n--- Docstrings ---")
|
||||||
|
|
||||||
|
def calculate_area(length, width):
|
||||||
|
"""
|
||||||
|
Calculate the area of a rectangle.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
length: The length of the rectangle (positive number)
|
||||||
|
width: The width of the rectangle (positive number)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The area of the rectangle (length * width)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> calculate_area(5, 3)
|
||||||
|
15
|
||||||
|
"""
|
||||||
|
return length * width
|
||||||
|
|
||||||
|
# Access docstring
|
||||||
|
print(f"Function docstring:\n{calculate_area.__doc__}")
|
||||||
|
|
||||||
|
# Using help()
|
||||||
|
# help(calculate_area) # Uncomment to see full help
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Variable Scope - Local vs Global
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Variables inside functions are local by default
|
||||||
|
|
||||||
|
print("\n--- Variable Scope ---")
|
||||||
|
|
||||||
|
global_var = "I am global"
|
||||||
|
|
||||||
|
def demonstrate_scope():
|
||||||
|
"""Demonstrate variable scope."""
|
||||||
|
local_var = "I am local"
|
||||||
|
print(f" Inside function - global_var: {global_var}")
|
||||||
|
print(f" Inside function - local_var: {local_var}")
|
||||||
|
|
||||||
|
demonstrate_scope()
|
||||||
|
print(f"Outside function - global_var: {global_var}")
|
||||||
|
# print(local_var) # This would cause an error!
|
||||||
|
|
||||||
|
# Modifying global variables inside functions
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
def increment_counter():
|
||||||
|
"""Increment the global counter."""
|
||||||
|
global counter # Declare we want to modify global variable
|
||||||
|
counter += 1
|
||||||
|
print(f" Counter inside function: {counter}")
|
||||||
|
|
||||||
|
print(f"\nCounter before: {counter}")
|
||||||
|
increment_counter()
|
||||||
|
increment_counter()
|
||||||
|
print(f"Counter after: {counter}")
|
||||||
|
|
||||||
|
# Local variable shadows global
|
||||||
|
value = 100
|
||||||
|
|
||||||
|
def shadow_example():
|
||||||
|
"""Local variable shadows global."""
|
||||||
|
value = 200 # This is a new local variable, not the global one
|
||||||
|
print(f" Inside function: {value}")
|
||||||
|
|
||||||
|
print(f"\nGlobal value: {value}")
|
||||||
|
shadow_example()
|
||||||
|
print(f"Global value unchanged: {value}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Multiple Return Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions can return multiple values as a tuple
|
||||||
|
|
||||||
|
print("\n--- Multiple Return Values ---")
|
||||||
|
|
||||||
|
def get_min_max(numbers):
|
||||||
|
"""Return both minimum and maximum of a list."""
|
||||||
|
return min(numbers), max(numbers)
|
||||||
|
|
||||||
|
data = [5, 2, 8, 1, 9, 3]
|
||||||
|
minimum, maximum = get_min_max(data)
|
||||||
|
print(f"List: {data}")
|
||||||
|
print(f"Min: {minimum}, Max: {maximum}")
|
||||||
|
|
||||||
|
# Return multiple named values using dictionary
|
||||||
|
def analyze_text(text):
|
||||||
|
"""Analyze text and return statistics."""
|
||||||
|
return {
|
||||||
|
"length": len(text),
|
||||||
|
"words": len(text.split()),
|
||||||
|
"uppercase": sum(1 for c in text if c.isupper())
|
||||||
|
}
|
||||||
|
|
||||||
|
stats = analyze_text("Hello World! How Are You?")
|
||||||
|
print(f"\nText stats: {stats}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Pass Statement - Placeholder Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use pass to create empty function bodies
|
||||||
|
|
||||||
|
print("\n--- Placeholder Functions ---")
|
||||||
|
|
||||||
|
def future_feature():
|
||||||
|
"""This will be implemented later."""
|
||||||
|
pass # Placeholder - does nothing
|
||||||
|
|
||||||
|
def another_placeholder():
|
||||||
|
"""Placeholder with ellipsis (also valid)."""
|
||||||
|
... # Alternative to pass
|
||||||
|
|
||||||
|
future_feature() # Can be called, just does nothing
|
||||||
|
print("Placeholder functions work!")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Nested Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions can be defined inside other functions
|
||||||
|
|
||||||
|
print("\n--- Nested Functions ---")
|
||||||
|
|
||||||
|
def outer_function(message):
|
||||||
|
"""Outer function that contains an inner function."""
|
||||||
|
|
||||||
|
def inner_function():
|
||||||
|
"""Inner function that uses outer's variable."""
|
||||||
|
print(f" Inner says: {message}")
|
||||||
|
|
||||||
|
print("Outer function called")
|
||||||
|
inner_function()
|
||||||
|
|
||||||
|
outer_function("Hello from outer!")
|
||||||
|
|
||||||
|
# Inner function not accessible outside
|
||||||
|
# inner_function() # This would cause an error!
|
||||||
|
|
||||||
|
# Practical example: Helper function
|
||||||
|
def calculate_statistics(numbers):
|
||||||
|
"""Calculate various statistics using helper functions."""
|
||||||
|
|
||||||
|
def mean(nums):
|
||||||
|
return sum(nums) / len(nums)
|
||||||
|
|
||||||
|
def variance(nums):
|
||||||
|
avg = mean(nums)
|
||||||
|
return sum((x - avg) ** 2 for x in nums) / len(nums)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"mean": mean(numbers),
|
||||||
|
"variance": variance(numbers),
|
||||||
|
"std_dev": variance(numbers) ** 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
data = [2, 4, 4, 4, 5, 5, 7, 9]
|
||||||
|
stats = calculate_statistics(data)
|
||||||
|
print(f"\nStatistics for {data}:")
|
||||||
|
for key, value in stats.items():
|
||||||
|
print(f" {key}: {value:.2f}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Functions as Objects
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# In Python, functions are first-class objects
|
||||||
|
|
||||||
|
print("\n--- Functions as Objects ---")
|
||||||
|
|
||||||
|
def say_hello(name):
|
||||||
|
"""Just say hello."""
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
# Assign function to variable
|
||||||
|
greeting_func = say_hello
|
||||||
|
print(greeting_func("World"))
|
||||||
|
|
||||||
|
# Store functions in a list
|
||||||
|
def add_one(x): return x + 1
|
||||||
|
def double(x): return x * 2
|
||||||
|
def square(x): return x * x
|
||||||
|
|
||||||
|
operations = [add_one, double, square]
|
||||||
|
value = 5
|
||||||
|
|
||||||
|
print(f"\nApplying operations to {value}:")
|
||||||
|
for func in operations:
|
||||||
|
print(f" {func.__name__}({value}) = {func(value)}")
|
||||||
|
|
||||||
|
# Pass function as argument
|
||||||
|
def apply_operation(func, value):
|
||||||
|
"""Apply a function to a value."""
|
||||||
|
return func(value)
|
||||||
|
|
||||||
|
print(f"\napply_operation(double, 10) = {apply_operation(double, 10)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Temperature converter
|
||||||
|
def celsius_to_fahrenheit(celsius):
|
||||||
|
"""Convert Celsius to Fahrenheit."""
|
||||||
|
return (celsius * 9/5) + 32
|
||||||
|
|
||||||
|
def fahrenheit_to_celsius(fahrenheit):
|
||||||
|
"""Convert Fahrenheit to Celsius."""
|
||||||
|
return (fahrenheit - 32) * 5/9
|
||||||
|
|
||||||
|
print(f"20°C = {celsius_to_fahrenheit(20):.1f}°F")
|
||||||
|
print(f"68°F = {fahrenheit_to_celsius(68):.1f}°C")
|
||||||
|
|
||||||
|
# Password validator
|
||||||
|
def is_valid_password(password):
|
||||||
|
"""
|
||||||
|
Check if password meets requirements:
|
||||||
|
- At least 8 characters
|
||||||
|
- Contains uppercase letter
|
||||||
|
- Contains lowercase letter
|
||||||
|
- Contains digit
|
||||||
|
"""
|
||||||
|
if len(password) < 8:
|
||||||
|
return False, "Too short"
|
||||||
|
if not any(c.isupper() for c in password):
|
||||||
|
return False, "No uppercase letter"
|
||||||
|
if not any(c.islower() for c in password):
|
||||||
|
return False, "No lowercase letter"
|
||||||
|
if not any(c.isdigit() for c in password):
|
||||||
|
return False, "No digit"
|
||||||
|
return True, "Valid password"
|
||||||
|
|
||||||
|
test_passwords = ["short", "alllowercase", "ALLUPPERCASE", "ValidPass123"]
|
||||||
|
print("\nPassword validation:")
|
||||||
|
for pwd in test_passwords:
|
||||||
|
is_valid, message = is_valid_password(pwd)
|
||||||
|
print(f" '{pwd}': {message}")
|
||||||
308
05_functions/02_arguments.py
Normal file
308
05_functions/02_arguments.py
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_arguments.py
|
||||||
|
Topic: Function Arguments in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates the different ways to pass arguments to functions
|
||||||
|
in Python. Understanding these patterns is essential for writing flexible
|
||||||
|
and reusable functions.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Positional arguments
|
||||||
|
- Keyword arguments
|
||||||
|
- Default parameter values
|
||||||
|
- *args (variable positional arguments)
|
||||||
|
- **kwargs (variable keyword arguments)
|
||||||
|
- Argument unpacking
|
||||||
|
- Keyword-only and positional-only arguments
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Positional Arguments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Arguments passed in order; position matters
|
||||||
|
|
||||||
|
print("--- Positional Arguments ---")
|
||||||
|
|
||||||
|
def greet(first_name, last_name):
|
||||||
|
"""Greet using positional arguments."""
|
||||||
|
print(f"Hello, {first_name} {last_name}!")
|
||||||
|
|
||||||
|
greet("John", "Doe") # Correct order
|
||||||
|
greet("Jane", "Smith") # Correct order
|
||||||
|
|
||||||
|
# Order matters!
|
||||||
|
greet("Doe", "John") # Wrong order gives wrong result
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Keyword Arguments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Arguments passed by name; order doesn't matter
|
||||||
|
|
||||||
|
print("\n--- Keyword Arguments ---")
|
||||||
|
|
||||||
|
def create_profile(name, age, city):
|
||||||
|
"""Create a profile using keyword arguments."""
|
||||||
|
print(f"Name: {name}, Age: {age}, City: {city}")
|
||||||
|
|
||||||
|
# Using keyword arguments (order doesn't matter)
|
||||||
|
create_profile(name="Alice", age=25, city="NYC")
|
||||||
|
create_profile(city="Tokyo", name="Bob", age=30)
|
||||||
|
|
||||||
|
# Mix positional and keyword (positional must come first!)
|
||||||
|
create_profile("Charlie", city="London", age=28)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Default Parameter Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Parameters can have default values
|
||||||
|
|
||||||
|
print("\n--- Default Parameter Values ---")
|
||||||
|
|
||||||
|
def greet_with_title(name, title="Mr."):
|
||||||
|
"""Greet with an optional title."""
|
||||||
|
print(f"Hello, {title} {name}!")
|
||||||
|
|
||||||
|
greet_with_title("Smith") # Uses default title
|
||||||
|
greet_with_title("Johnson", "Dr.") # Overrides default
|
||||||
|
|
||||||
|
# Multiple defaults
|
||||||
|
def create_user(username, email, role="user", active=True):
|
||||||
|
"""Create a user with defaults."""
|
||||||
|
print(f"User: {username}, Email: {email}, Role: {role}, Active: {active}")
|
||||||
|
|
||||||
|
create_user("john", "john@example.com")
|
||||||
|
create_user("admin", "admin@example.com", role="admin")
|
||||||
|
create_user("test", "test@example.com", active=False)
|
||||||
|
|
||||||
|
# CAUTION: Mutable default arguments
|
||||||
|
# DON'T DO THIS:
|
||||||
|
def bad_function(items=[]): # Bad! List is shared across calls
|
||||||
|
items.append(1)
|
||||||
|
return items
|
||||||
|
|
||||||
|
# DO THIS INSTEAD:
|
||||||
|
def good_function(items=None):
|
||||||
|
"""Safe way to handle mutable defaults."""
|
||||||
|
if items is None:
|
||||||
|
items = []
|
||||||
|
items.append(1)
|
||||||
|
return items
|
||||||
|
|
||||||
|
print(f"\nBad function calls: {bad_function()}, {bad_function()}, {bad_function()}")
|
||||||
|
print(f"Good function calls: {good_function()}, {good_function()}, {good_function()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. *args - Variable Positional Arguments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Accept any number of positional arguments
|
||||||
|
|
||||||
|
print("\n--- *args (Variable Positional Arguments) ---")
|
||||||
|
|
||||||
|
def sum_all(*args):
|
||||||
|
"""Sum all provided numbers."""
|
||||||
|
print(f" Received args: {args} (type: {type(args).__name__})")
|
||||||
|
return sum(args)
|
||||||
|
|
||||||
|
print(f"sum_all(1, 2): {sum_all(1, 2)}")
|
||||||
|
print(f"sum_all(1, 2, 3, 4, 5): {sum_all(1, 2, 3, 4, 5)}")
|
||||||
|
print(f"sum_all(): {sum_all()}")
|
||||||
|
|
||||||
|
# Combining regular parameters with *args
|
||||||
|
def greet_people(greeting, *names):
|
||||||
|
"""Greet multiple people with the same greeting."""
|
||||||
|
for name in names:
|
||||||
|
print(f" {greeting}, {name}!")
|
||||||
|
|
||||||
|
print("\nGreeting people:")
|
||||||
|
greet_people("Hello", "Alice", "Bob", "Charlie")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. **kwargs - Variable Keyword Arguments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Accept any number of keyword arguments
|
||||||
|
|
||||||
|
print("\n--- **kwargs (Variable Keyword Arguments) ---")
|
||||||
|
|
||||||
|
def print_info(**kwargs):
|
||||||
|
"""Print all keyword arguments."""
|
||||||
|
print(f" Received kwargs: {kwargs} (type: {type(kwargs).__name__})")
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
print(f" {key}: {value}")
|
||||||
|
|
||||||
|
print_info(name="Baraa", age=25, city="Gaza")
|
||||||
|
print_info(language="Python", level="Expert")
|
||||||
|
|
||||||
|
# Combining all parameter types
|
||||||
|
def complete_example(required, *args, default="value", **kwargs):
|
||||||
|
"""Demonstrate all parameter types."""
|
||||||
|
print(f" Required: {required}")
|
||||||
|
print(f" *args: {args}")
|
||||||
|
print(f" Default: {default}")
|
||||||
|
print(f" **kwargs: {kwargs}")
|
||||||
|
|
||||||
|
print("\nComplete example:")
|
||||||
|
complete_example("must_have", 1, 2, 3, default="custom", extra="data", more="stuff")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Argument Unpacking
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use * and ** to unpack sequences and dictionaries into arguments
|
||||||
|
|
||||||
|
print("\n--- Argument Unpacking ---")
|
||||||
|
|
||||||
|
def introduce(name, age, city):
|
||||||
|
"""Introduce a person."""
|
||||||
|
print(f"I'm {name}, {age} years old, from {city}")
|
||||||
|
|
||||||
|
# Unpack list/tuple with *
|
||||||
|
person_list = ["Alice", 30, "Paris"]
|
||||||
|
introduce(*person_list) # Same as: introduce("Alice", 30, "Paris")
|
||||||
|
|
||||||
|
# Unpack dictionary with **
|
||||||
|
person_dict = {"name": "Bob", "age": 25, "city": "Berlin"}
|
||||||
|
introduce(**person_dict) # Same as: introduce(name="Bob", age=25, city="Berlin")
|
||||||
|
|
||||||
|
# Combine unpacking
|
||||||
|
def multiply(a, b, c):
|
||||||
|
return a * b * c
|
||||||
|
|
||||||
|
nums = [2, 3]
|
||||||
|
print(f"\nMultiply with unpacking: {multiply(*nums, 4)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Keyword-Only Arguments (Python 3+)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Arguments after * must be passed as keywords
|
||||||
|
|
||||||
|
print("\n--- Keyword-Only Arguments ---")
|
||||||
|
|
||||||
|
def format_name(first, last, *, upper=False, reverse=False):
|
||||||
|
"""
|
||||||
|
Format a name. 'upper' and 'reverse' are keyword-only.
|
||||||
|
"""
|
||||||
|
name = f"{first} {last}"
|
||||||
|
if reverse:
|
||||||
|
name = f"{last}, {first}"
|
||||||
|
if upper:
|
||||||
|
name = name.upper()
|
||||||
|
return name
|
||||||
|
|
||||||
|
# These work:
|
||||||
|
print(format_name("John", "Doe"))
|
||||||
|
print(format_name("John", "Doe", upper=True))
|
||||||
|
print(format_name("John", "Doe", reverse=True, upper=True))
|
||||||
|
|
||||||
|
# This would fail:
|
||||||
|
# format_name("John", "Doe", True) # Error! upper must be keyword
|
||||||
|
|
||||||
|
# Using bare * for keyword-only
|
||||||
|
def connect(*, host, port, timeout=30):
|
||||||
|
"""All parameters are keyword-only."""
|
||||||
|
print(f" Connecting to {host}:{port} (timeout: {timeout}s)")
|
||||||
|
|
||||||
|
connect(host="localhost", port=8080)
|
||||||
|
connect(host="db.example.com", port=5432, timeout=60)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Positional-Only Arguments (Python 3.8+)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Arguments before / must be passed positionally
|
||||||
|
|
||||||
|
print("\n--- Positional-Only Arguments ---")
|
||||||
|
|
||||||
|
def divide(x, y, /):
|
||||||
|
"""x and y must be passed positionally."""
|
||||||
|
return x / y
|
||||||
|
|
||||||
|
print(f"divide(10, 2): {divide(10, 2)}")
|
||||||
|
# divide(x=10, y=2) # Error! x and y are positional-only
|
||||||
|
|
||||||
|
# Combining positional-only, regular, and keyword-only
|
||||||
|
def complex_function(pos_only1, pos_only2, /, regular1, regular2, *, kw_only1, kw_only2="default"):
|
||||||
|
"""
|
||||||
|
pos_only1, pos_only2: positional-only (before /)
|
||||||
|
regular1, regular2: can be either
|
||||||
|
kw_only1, kw_only2: keyword-only (after *)
|
||||||
|
"""
|
||||||
|
print(f" pos_only: {pos_only1}, {pos_only2}")
|
||||||
|
print(f" regular: {regular1}, {regular2}")
|
||||||
|
print(f" kw_only: {kw_only1}, {kw_only2}")
|
||||||
|
|
||||||
|
print("\nComplex function:")
|
||||||
|
complex_function(1, 2, 3, regular2=4, kw_only1="required")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Type Hints for Arguments (Preview)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Adding type information (doesn't enforce, just hints)
|
||||||
|
|
||||||
|
print("\n--- Type Hints ---")
|
||||||
|
|
||||||
|
def calculate_total(price: float, quantity: int, tax_rate: float = 0.1) -> float:
|
||||||
|
"""
|
||||||
|
Calculate total price with tax.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
price: Unit price (float)
|
||||||
|
quantity: Number of items (int)
|
||||||
|
tax_rate: Tax rate as decimal (default 0.1 = 10%)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Total price including tax
|
||||||
|
"""
|
||||||
|
subtotal = price * quantity
|
||||||
|
return subtotal * (1 + tax_rate)
|
||||||
|
|
||||||
|
total = calculate_total(19.99, 3)
|
||||||
|
print(f"Total: ${total:.2f}")
|
||||||
|
|
||||||
|
# Type hints with complex types
|
||||||
|
from typing import List, Optional, Dict
|
||||||
|
|
||||||
|
def process_scores(scores: List[int], name: Optional[str] = None) -> Dict[str, float]:
|
||||||
|
"""Process a list of scores and return statistics."""
|
||||||
|
return {
|
||||||
|
"name": name or "Unknown",
|
||||||
|
"average": sum(scores) / len(scores),
|
||||||
|
"highest": max(scores),
|
||||||
|
"lowest": min(scores)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = process_scores([85, 90, 78, 92], "Alice")
|
||||||
|
print(f"Score stats: {result}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Flexible logging function
|
||||||
|
def log(message, *tags, level="INFO", **metadata):
|
||||||
|
"""Log a message with optional tags and metadata."""
|
||||||
|
tag_str = " ".join(f"[{tag}]" for tag in tags)
|
||||||
|
meta_str = " | ".join(f"{k}={v}" for k, v in metadata.items())
|
||||||
|
|
||||||
|
output = f"[{level}] {tag_str} {message}"
|
||||||
|
if meta_str:
|
||||||
|
output += f" ({meta_str})"
|
||||||
|
print(output)
|
||||||
|
|
||||||
|
log("Server started", "server", "startup")
|
||||||
|
log("User logged in", "auth", level="DEBUG", user_id=123, ip="192.168.1.1")
|
||||||
|
|
||||||
|
# Builder pattern with kwargs
|
||||||
|
def create_html_element(tag, content="", **attributes):
|
||||||
|
"""Create an HTML element string."""
|
||||||
|
attrs = " ".join(f'{k}="{v}"' for k, v in attributes.items())
|
||||||
|
if attrs:
|
||||||
|
attrs = " " + attrs
|
||||||
|
return f"<{tag}{attrs}>{content}</{tag}>"
|
||||||
|
|
||||||
|
print("\n" + create_html_element("p", "Hello World"))
|
||||||
|
print(create_html_element("a", "Click me", href="https://example.com", target="_blank"))
|
||||||
|
print(create_html_element("input", type="text", placeholder="Enter name", id="name_input"))
|
||||||
399
05_functions/03_return_values.py
Normal file
399
05_functions/03_return_values.py
Normal file
|
|
@ -0,0 +1,399 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_return_values.py
|
||||||
|
Topic: Function Return Values in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates various ways functions can return values in Python,
|
||||||
|
including returning multiple values, early returns, and more advanced patterns.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Returning single and multiple values
|
||||||
|
- Returning None (explicit and implicit)
|
||||||
|
- Early returns for cleaner code
|
||||||
|
- Returning functions (closures)
|
||||||
|
- Return type annotations
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic Return Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use 'return' to send a value back to the caller
|
||||||
|
|
||||||
|
print("--- Basic Return Values ---")
|
||||||
|
|
||||||
|
def square(n):
|
||||||
|
"""Return the square of a number."""
|
||||||
|
return n * n
|
||||||
|
|
||||||
|
def add(a, b):
|
||||||
|
"""Return the sum of two numbers."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
result = square(5)
|
||||||
|
print(f"square(5) = {result}")
|
||||||
|
|
||||||
|
# Using return value directly
|
||||||
|
print(f"add(3, 4) = {add(3, 4)}")
|
||||||
|
|
||||||
|
# Chaining function calls
|
||||||
|
print(f"square(add(2, 3)) = {square(add(2, 3))}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Returning None
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions without explicit return (or with 'return' alone) return None
|
||||||
|
|
||||||
|
print("\n--- Returning None ---")
|
||||||
|
|
||||||
|
# Implicit None return
|
||||||
|
def greet(name):
|
||||||
|
"""Print a greeting - no explicit return."""
|
||||||
|
print(f" Hello, {name}!")
|
||||||
|
|
||||||
|
result = greet("Alice")
|
||||||
|
print(f"Return value: {result}")
|
||||||
|
|
||||||
|
# Explicit None return
|
||||||
|
def try_parse_int(value):
|
||||||
|
"""Try to parse string as int, return None on failure."""
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except ValueError:
|
||||||
|
return None # Explicit None
|
||||||
|
|
||||||
|
print(f"\ntry_parse_int('42'): {try_parse_int('42')}")
|
||||||
|
print(f"try_parse_int('abc'): {try_parse_int('abc')}")
|
||||||
|
|
||||||
|
# Using None as sentinel value
|
||||||
|
def find_item(items, target):
|
||||||
|
"""Find item in list, return index or None."""
|
||||||
|
for i, item in enumerate(items):
|
||||||
|
if item == target:
|
||||||
|
return i
|
||||||
|
return None # Not found
|
||||||
|
|
||||||
|
fruits = ["apple", "banana", "cherry"]
|
||||||
|
index = find_item(fruits, "banana")
|
||||||
|
if index is not None:
|
||||||
|
print(f"\nFound 'banana' at index {index}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Returning Multiple Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Python functions can return multiple values as tuples
|
||||||
|
|
||||||
|
print("\n--- Returning Multiple Values ---")
|
||||||
|
|
||||||
|
# Return as tuple (implicit)
|
||||||
|
def get_name_parts(full_name):
|
||||||
|
"""Split full name into first and last."""
|
||||||
|
parts = full_name.split()
|
||||||
|
return parts[0], parts[-1] # Returns tuple
|
||||||
|
|
||||||
|
first, last = get_name_parts("John William Doe")
|
||||||
|
print(f"First: {first}, Last: {last}")
|
||||||
|
|
||||||
|
# Return as tuple (explicit)
|
||||||
|
def min_max(numbers):
|
||||||
|
"""Return minimum and maximum as explicit tuple."""
|
||||||
|
return (min(numbers), max(numbers))
|
||||||
|
|
||||||
|
result = min_max([3, 1, 4, 1, 5, 9])
|
||||||
|
print(f"min_max result: {result}")
|
||||||
|
print(f"Type: {type(result)}")
|
||||||
|
|
||||||
|
# Return as dictionary (named values)
|
||||||
|
def analyze_string(text):
|
||||||
|
"""Analyze string and return statistics as dict."""
|
||||||
|
return {
|
||||||
|
"length": len(text),
|
||||||
|
"words": len(text.split()),
|
||||||
|
"chars_no_space": len(text.replace(" ", "")),
|
||||||
|
"upper_count": sum(1 for c in text if c.isupper()),
|
||||||
|
"lower_count": sum(1 for c in text if c.islower())
|
||||||
|
}
|
||||||
|
|
||||||
|
stats = analyze_string("Hello World")
|
||||||
|
print(f"\nString analysis: {stats}")
|
||||||
|
|
||||||
|
# Return as named tuple (best of both worlds)
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
Point = namedtuple("Point", ["x", "y", "z"])
|
||||||
|
|
||||||
|
def create_point(x, y, z):
|
||||||
|
"""Create a 3D point."""
|
||||||
|
return Point(x, y, z)
|
||||||
|
|
||||||
|
p = create_point(10, 20, 30)
|
||||||
|
print(f"\nNamed tuple: {p}")
|
||||||
|
print(f"Access by name: x={p.x}, y={p.y}, z={p.z}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Early Returns (Guard Clauses)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Return early to handle edge cases and improve readability
|
||||||
|
|
||||||
|
print("\n--- Early Returns ---")
|
||||||
|
|
||||||
|
# Without early returns (nested, harder to read)
|
||||||
|
def get_grade_nested(score):
|
||||||
|
if score >= 0 and score <= 100:
|
||||||
|
if score >= 90:
|
||||||
|
return "A"
|
||||||
|
else:
|
||||||
|
if score >= 80:
|
||||||
|
return "B"
|
||||||
|
else:
|
||||||
|
if score >= 70:
|
||||||
|
return "C"
|
||||||
|
else:
|
||||||
|
if score >= 60:
|
||||||
|
return "D"
|
||||||
|
else:
|
||||||
|
return "F"
|
||||||
|
else:
|
||||||
|
return "Invalid"
|
||||||
|
|
||||||
|
# With early returns (flat, easier to read)
|
||||||
|
def get_grade_early(score):
|
||||||
|
"""Get grade using early returns (guard clauses)."""
|
||||||
|
if score < 0 or score > 100:
|
||||||
|
return "Invalid"
|
||||||
|
if score >= 90:
|
||||||
|
return "A"
|
||||||
|
if score >= 80:
|
||||||
|
return "B"
|
||||||
|
if score >= 70:
|
||||||
|
return "C"
|
||||||
|
if score >= 60:
|
||||||
|
return "D"
|
||||||
|
return "F"
|
||||||
|
|
||||||
|
print(f"get_grade_early(85) = {get_grade_early(85)}")
|
||||||
|
print(f"get_grade_early(150) = {get_grade_early(150)}")
|
||||||
|
|
||||||
|
# Practical example: Input validation with early returns
|
||||||
|
def process_user_data(data):
|
||||||
|
"""Process user data with validation."""
|
||||||
|
# Guard clauses
|
||||||
|
if data is None:
|
||||||
|
return {"error": "No data provided"}
|
||||||
|
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
return {"error": "Data must be a dictionary"}
|
||||||
|
|
||||||
|
if "name" not in data:
|
||||||
|
return {"error": "Name is required"}
|
||||||
|
|
||||||
|
if "age" not in data:
|
||||||
|
return {"error": "Age is required"}
|
||||||
|
|
||||||
|
if data["age"] < 0:
|
||||||
|
return {"error": "Age must be positive"}
|
||||||
|
|
||||||
|
# Main logic (only runs if all validations pass)
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Processed user {data['name']}, age {data['age']}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\nData validation examples:")
|
||||||
|
print(f" None: {process_user_data(None)}")
|
||||||
|
print(f" Empty dict: {process_user_data({})}")
|
||||||
|
print(f" Valid: {process_user_data({'name': 'Alice', 'age': 25})}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Returning Functions (Closures)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions can return other functions
|
||||||
|
|
||||||
|
print("\n--- Returning Functions ---")
|
||||||
|
|
||||||
|
def make_multiplier(factor):
|
||||||
|
"""Return a function that multiplies by factor."""
|
||||||
|
def multiplier(x):
|
||||||
|
return x * factor
|
||||||
|
return multiplier
|
||||||
|
|
||||||
|
double = make_multiplier(2)
|
||||||
|
triple = make_multiplier(3)
|
||||||
|
|
||||||
|
print(f"double(5) = {double(5)}")
|
||||||
|
print(f"triple(5) = {triple(5)}")
|
||||||
|
|
||||||
|
# Practical: Create custom validators
|
||||||
|
def make_range_validator(min_val, max_val):
|
||||||
|
"""Create a validator for a specific range."""
|
||||||
|
def validate(value):
|
||||||
|
return min_val <= value <= max_val
|
||||||
|
return validate
|
||||||
|
|
||||||
|
age_validator = make_range_validator(0, 120)
|
||||||
|
percentage_validator = make_range_validator(0, 100)
|
||||||
|
|
||||||
|
print(f"\nage_validator(25) = {age_validator(25)}")
|
||||||
|
print(f"age_validator(150) = {age_validator(150)}")
|
||||||
|
print(f"percentage_validator(85.5) = {percentage_validator(85.5)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Return Type Annotations
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Add type hints for return values
|
||||||
|
|
||||||
|
print("\n--- Return Type Annotations ---")
|
||||||
|
|
||||||
|
from typing import List, Optional, Tuple, Dict, Union
|
||||||
|
|
||||||
|
def get_greeting(name: str) -> str:
|
||||||
|
"""Return a greeting string."""
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
def divide(a: float, b: float) -> Optional[float]:
|
||||||
|
"""Divide a by b, return None if b is zero."""
|
||||||
|
if b == 0:
|
||||||
|
return None
|
||||||
|
return a / b
|
||||||
|
|
||||||
|
def get_user_info(user_id: int) -> Dict[str, Union[str, int]]:
|
||||||
|
"""Return user info as dictionary."""
|
||||||
|
return {"id": user_id, "name": "Test User", "age": 25}
|
||||||
|
|
||||||
|
def process_numbers(nums: List[int]) -> Tuple[int, int, float]:
|
||||||
|
"""Return min, max, and average."""
|
||||||
|
return min(nums), max(nums), sum(nums) / len(nums)
|
||||||
|
|
||||||
|
print(f"get_greeting('World'): {get_greeting('World')}")
|
||||||
|
print(f"divide(10, 3): {divide(10, 3)}")
|
||||||
|
print(f"divide(10, 0): {divide(10, 0)}")
|
||||||
|
print(f"get_user_info(1): {get_user_info(1)}")
|
||||||
|
print(f"process_numbers([1,2,3,4,5]): {process_numbers([1,2,3,4,5])}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Generator Functions (yield vs return)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions that yield values instead of returning
|
||||||
|
|
||||||
|
print("\n--- Generators (yield) ---")
|
||||||
|
|
||||||
|
# Regular function - returns all at once
|
||||||
|
def get_squares_list(n):
|
||||||
|
"""Return list of squares."""
|
||||||
|
return [i ** 2 for i in range(n)]
|
||||||
|
|
||||||
|
# Generator function - yields one at a time
|
||||||
|
def get_squares_generator(n):
|
||||||
|
"""Generate squares one at a time."""
|
||||||
|
for i in range(n):
|
||||||
|
yield i ** 2
|
||||||
|
|
||||||
|
# Generator is memory-efficient for large sequences
|
||||||
|
squares_list = get_squares_list(5)
|
||||||
|
squares_gen = get_squares_generator(5)
|
||||||
|
|
||||||
|
print(f"List: {squares_list}")
|
||||||
|
print(f"Generator: {squares_gen}")
|
||||||
|
print(f"From generator: {list(squares_gen)}")
|
||||||
|
|
||||||
|
# Practical: Generator for large files (conceptual)
|
||||||
|
def read_large_file_lines(filename):
|
||||||
|
"""Yield lines one at a time (memory efficient)."""
|
||||||
|
# In real code: open(filename) and yield line by line
|
||||||
|
# This is just a demonstration
|
||||||
|
for i in range(5):
|
||||||
|
yield f"Line {i + 1} from {filename}"
|
||||||
|
|
||||||
|
print("\nGenerator example:")
|
||||||
|
for line in read_large_file_lines("data.txt"):
|
||||||
|
print(f" {line}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Conditional Returns
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Return different types or values based on conditions
|
||||||
|
|
||||||
|
print("\n--- Conditional Returns ---")
|
||||||
|
|
||||||
|
# Ternary return
|
||||||
|
def absolute_value(n):
|
||||||
|
"""Return absolute value using ternary."""
|
||||||
|
return n if n >= 0 else -n
|
||||||
|
|
||||||
|
print(f"absolute_value(-5) = {absolute_value(-5)}")
|
||||||
|
print(f"absolute_value(5) = {absolute_value(5)}")
|
||||||
|
|
||||||
|
# Short-circuit return with 'or'
|
||||||
|
def get_username(user):
|
||||||
|
"""Get username or default."""
|
||||||
|
return user.get("username") or "anonymous"
|
||||||
|
|
||||||
|
print(f"get_username({{}}): {get_username({})}")
|
||||||
|
print(f"get_username({{'username': 'john'}}): {get_username({'username': 'john'})}")
|
||||||
|
|
||||||
|
# Return type based on input
|
||||||
|
def smart_divide(a, b, as_float=True):
|
||||||
|
"""Return float or int based on parameter."""
|
||||||
|
if b == 0:
|
||||||
|
return None
|
||||||
|
return a / b if as_float else a // b
|
||||||
|
|
||||||
|
print(f"\nsmart_divide(10, 3, True): {smart_divide(10, 3, True)}")
|
||||||
|
print(f"smart_divide(10, 3, False): {smart_divide(10, 3, False)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Return vs Print
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Important distinction for beginners
|
||||||
|
|
||||||
|
print("\n--- Return vs Print ---")
|
||||||
|
|
||||||
|
def print_double(n):
|
||||||
|
"""Print double (returns None)."""
|
||||||
|
print(n * 2)
|
||||||
|
|
||||||
|
def return_double(n):
|
||||||
|
"""Return double."""
|
||||||
|
return n * 2
|
||||||
|
|
||||||
|
# print_double can't be used in expressions
|
||||||
|
result1 = print_double(5) # Prints 10
|
||||||
|
print(f"print_double(5) returns: {result1}")
|
||||||
|
|
||||||
|
# return_double can be used in expressions
|
||||||
|
result2 = return_double(5) # Returns 10
|
||||||
|
print(f"return_double(5) returns: {result2}")
|
||||||
|
print(f"return_double(5) + 1 = {return_double(5) + 1}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# API-style response
|
||||||
|
def api_response(success, data=None, error=None):
|
||||||
|
"""Create standardized API response."""
|
||||||
|
return {
|
||||||
|
"success": success,
|
||||||
|
"data": data,
|
||||||
|
"error": error
|
||||||
|
}
|
||||||
|
|
||||||
|
print("API responses:")
|
||||||
|
print(f" Success: {api_response(True, {'user': 'john', 'id': 1})}")
|
||||||
|
print(f" Error: {api_response(False, error='User not found')}")
|
||||||
|
|
||||||
|
# Chain-able operations
|
||||||
|
def chain_operation(value, operations):
|
||||||
|
"""Apply a chain of operations to a value."""
|
||||||
|
result = value
|
||||||
|
for op in operations:
|
||||||
|
result = op(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
ops = [lambda x: x + 10, lambda x: x * 2, lambda x: x - 5]
|
||||||
|
print(f"\nChained operations on 5: {chain_operation(5, ops)}")
|
||||||
|
# (5 + 10) * 2 - 5 = 25
|
||||||
340
05_functions/04_lambda_functions.py
Normal file
340
05_functions/04_lambda_functions.py
Normal file
|
|
@ -0,0 +1,340 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 04_lambda_functions.py
|
||||||
|
Topic: Lambda Functions in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates lambda functions (anonymous functions) in Python.
|
||||||
|
Lambda functions are small, one-line functions that can be defined inline
|
||||||
|
without using the 'def' keyword.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Lambda syntax
|
||||||
|
- When to use lambda functions
|
||||||
|
- Lambda with built-in functions (map, filter, sorted)
|
||||||
|
- Lambda vs regular functions
|
||||||
|
- Common use cases and patterns
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic Lambda Syntax
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# lambda arguments: expression
|
||||||
|
|
||||||
|
print("--- Basic Lambda Syntax ---")
|
||||||
|
|
||||||
|
# Regular function
|
||||||
|
def add_regular(a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
# Equivalent lambda function
|
||||||
|
add_lambda = lambda a, b: a + b
|
||||||
|
|
||||||
|
print(f"Regular function: {add_regular(3, 5)}")
|
||||||
|
print(f"Lambda function: {add_lambda(3, 5)}")
|
||||||
|
|
||||||
|
# More examples
|
||||||
|
square = lambda x: x ** 2
|
||||||
|
is_even = lambda x: x % 2 == 0
|
||||||
|
greet = lambda name: f"Hello, {name}!"
|
||||||
|
|
||||||
|
print(f"\nsquare(4) = {square(4)}")
|
||||||
|
print(f"is_even(7) = {is_even(7)}")
|
||||||
|
print(f"greet('Alice') = {greet('Alice')}")
|
||||||
|
|
||||||
|
# Lambda with no arguments
|
||||||
|
get_pi = lambda: 3.14159
|
||||||
|
print(f"get_pi() = {get_pi()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Lambda with Multiple Arguments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Multiple Arguments ---")
|
||||||
|
|
||||||
|
# Two arguments
|
||||||
|
multiply = lambda x, y: x * y
|
||||||
|
print(f"multiply(4, 5) = {multiply(4, 5)}")
|
||||||
|
|
||||||
|
# Three arguments
|
||||||
|
volume = lambda l, w, h: l * w * h
|
||||||
|
print(f"volume(2, 3, 4) = {volume(2, 3, 4)}")
|
||||||
|
|
||||||
|
# With default arguments
|
||||||
|
power = lambda base, exp=2: base ** exp
|
||||||
|
print(f"power(3) = {power(3)}") # 3^2 = 9
|
||||||
|
print(f"power(2, 3) = {power(2, 3)}") # 2^3 = 8
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Lambda with Conditional Expression
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Using ternary operator in lambda
|
||||||
|
|
||||||
|
print("\n--- Conditional Lambda ---")
|
||||||
|
|
||||||
|
# Simple conditional
|
||||||
|
get_sign = lambda x: "positive" if x > 0 else ("negative" if x < 0 else "zero")
|
||||||
|
|
||||||
|
print(f"get_sign(5) = {get_sign(5)}")
|
||||||
|
print(f"get_sign(-3) = {get_sign(-3)}")
|
||||||
|
print(f"get_sign(0) = {get_sign(0)}")
|
||||||
|
|
||||||
|
# Max of two numbers
|
||||||
|
max_of_two = lambda a, b: a if a > b else b
|
||||||
|
print(f"\nmax_of_two(10, 20) = {max_of_two(10, 20)}")
|
||||||
|
|
||||||
|
# Absolute value
|
||||||
|
absolute = lambda x: x if x >= 0 else -x
|
||||||
|
print(f"absolute(-7) = {absolute(-7)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Lambda with map()
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Apply function to each element of iterable
|
||||||
|
|
||||||
|
print("\n--- Lambda with map() ---")
|
||||||
|
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
# Square each number
|
||||||
|
squares = list(map(lambda x: x ** 2, numbers))
|
||||||
|
print(f"Original: {numbers}")
|
||||||
|
print(f"Squared: {squares}")
|
||||||
|
|
||||||
|
# Convert to strings
|
||||||
|
strings = list(map(lambda x: str(x), numbers))
|
||||||
|
print(f"As strings: {strings}")
|
||||||
|
|
||||||
|
# Multiple iterables with map
|
||||||
|
list1 = [1, 2, 3]
|
||||||
|
list2 = [10, 20, 30]
|
||||||
|
sums = list(map(lambda x, y: x + y, list1, list2))
|
||||||
|
print(f"\n{list1} + {list2} = {sums}")
|
||||||
|
|
||||||
|
# Processing strings
|
||||||
|
names = ["alice", "bob", "charlie"]
|
||||||
|
capitalized = list(map(lambda name: name.capitalize(), names))
|
||||||
|
print(f"Capitalized: {capitalized}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Lambda with filter()
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Filter elements based on condition
|
||||||
|
|
||||||
|
print("\n--- Lambda with filter() ---")
|
||||||
|
|
||||||
|
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
# Filter even numbers
|
||||||
|
evens = list(filter(lambda x: x % 2 == 0, numbers))
|
||||||
|
print(f"Original: {numbers}")
|
||||||
|
print(f"Even only: {evens}")
|
||||||
|
|
||||||
|
# Filter numbers greater than 5
|
||||||
|
greater_than_5 = list(filter(lambda x: x > 5, numbers))
|
||||||
|
print(f"Greater than 5: {greater_than_5}")
|
||||||
|
|
||||||
|
# Filter non-empty strings
|
||||||
|
strings = ["hello", "", "world", "", "python", ""]
|
||||||
|
non_empty = list(filter(lambda s: s, strings))
|
||||||
|
print(f"\nNon-empty strings: {non_empty}")
|
||||||
|
|
||||||
|
# Complex filtering
|
||||||
|
people = [
|
||||||
|
{"name": "Alice", "age": 25},
|
||||||
|
{"name": "Bob", "age": 17},
|
||||||
|
{"name": "Charlie", "age": 30},
|
||||||
|
{"name": "David", "age": 15}
|
||||||
|
]
|
||||||
|
|
||||||
|
adults = list(filter(lambda p: p["age"] >= 18, people))
|
||||||
|
print(f"Adults: {[p['name'] for p in adults]}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Lambda with sorted()
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Custom sorting with key function
|
||||||
|
|
||||||
|
print("\n--- Lambda with sorted() ---")
|
||||||
|
|
||||||
|
# Sort by absolute value
|
||||||
|
numbers = [-5, 2, -1, 7, -3, 4]
|
||||||
|
by_absolute = sorted(numbers, key=lambda x: abs(x))
|
||||||
|
print(f"Original: {numbers}")
|
||||||
|
print(f"Sorted by absolute value: {by_absolute}")
|
||||||
|
|
||||||
|
# Sort strings by length
|
||||||
|
words = ["python", "is", "a", "programming", "language"]
|
||||||
|
by_length = sorted(words, key=lambda w: len(w))
|
||||||
|
print(f"\nSorted by length: {by_length}")
|
||||||
|
|
||||||
|
# Sort objects by attribute
|
||||||
|
students = [
|
||||||
|
{"name": "Alice", "grade": 85},
|
||||||
|
{"name": "Bob", "grade": 92},
|
||||||
|
{"name": "Charlie", "grade": 78}
|
||||||
|
]
|
||||||
|
|
||||||
|
by_grade = sorted(students, key=lambda s: s["grade"], reverse=True)
|
||||||
|
print(f"\nBy grade (highest first):")
|
||||||
|
for s in by_grade:
|
||||||
|
print(f" {s['name']}: {s['grade']}")
|
||||||
|
|
||||||
|
# Sort by multiple criteria
|
||||||
|
items = [("apple", 3), ("banana", 1), ("cherry", 2), ("apple", 1)]
|
||||||
|
# Sort by name, then by number
|
||||||
|
sorted_items = sorted(items, key=lambda x: (x[0], x[1]))
|
||||||
|
print(f"\nSorted by name, then number: {sorted_items}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Lambda with reduce()
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Reduce iterable to single value
|
||||||
|
|
||||||
|
print("\n--- Lambda with reduce() ---")
|
||||||
|
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
# Sum all numbers
|
||||||
|
total = reduce(lambda acc, x: acc + x, numbers)
|
||||||
|
print(f"Sum of {numbers} = {total}")
|
||||||
|
|
||||||
|
# Product of all numbers
|
||||||
|
product = reduce(lambda acc, x: acc * x, numbers)
|
||||||
|
print(f"Product of {numbers} = {product}")
|
||||||
|
|
||||||
|
# Find maximum
|
||||||
|
maximum = reduce(lambda a, b: a if a > b else b, numbers)
|
||||||
|
print(f"Maximum of {numbers} = {maximum}")
|
||||||
|
|
||||||
|
# Concatenate strings
|
||||||
|
words = ["Hello", " ", "World", "!"]
|
||||||
|
sentence = reduce(lambda a, b: a + b, words)
|
||||||
|
print(f"Concatenated: '{sentence}'")
|
||||||
|
|
||||||
|
# With initial value
|
||||||
|
numbers = [1, 2, 3]
|
||||||
|
sum_with_initial = reduce(lambda acc, x: acc + x, numbers, 100)
|
||||||
|
print(f"\nSum with initial 100: {sum_with_initial}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Lambda in Data Processing
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Data Processing Example ---")
|
||||||
|
|
||||||
|
# Sample data
|
||||||
|
transactions = [
|
||||||
|
{"id": 1, "type": "credit", "amount": 100},
|
||||||
|
{"id": 2, "type": "debit", "amount": 50},
|
||||||
|
{"id": 3, "type": "credit", "amount": 200},
|
||||||
|
{"id": 4, "type": "debit", "amount": 75},
|
||||||
|
{"id": 5, "type": "credit", "amount": 150}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Filter credits only
|
||||||
|
credits = list(filter(lambda t: t["type"] == "credit", transactions))
|
||||||
|
print(f"Credit transactions: {len(credits)}")
|
||||||
|
|
||||||
|
# Extract amounts from credits
|
||||||
|
credit_amounts = list(map(lambda t: t["amount"], credits))
|
||||||
|
print(f"Credit amounts: {credit_amounts}")
|
||||||
|
|
||||||
|
# Total credits
|
||||||
|
total_credits = reduce(lambda acc, t: acc + t["amount"], credits, 0)
|
||||||
|
print(f"Total credits: ${total_credits}")
|
||||||
|
|
||||||
|
# Combined: total debits in one expression
|
||||||
|
total_debits = reduce(
|
||||||
|
lambda acc, x: acc + x,
|
||||||
|
map(
|
||||||
|
lambda t: t["amount"],
|
||||||
|
filter(lambda t: t["type"] == "debit", transactions)
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
print(f"Total debits: ${total_debits}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Lambda vs Regular Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Lambda vs Regular Functions ---")
|
||||||
|
|
||||||
|
# Lambda: one expression, implicit return
|
||||||
|
# Regular: multiple statements, explicit return
|
||||||
|
|
||||||
|
# When to use LAMBDA:
|
||||||
|
# - Simple, one-line operations
|
||||||
|
# - As arguments to higher-order functions
|
||||||
|
# - When function won't be reused
|
||||||
|
|
||||||
|
# When to use REGULAR FUNCTIONS:
|
||||||
|
# - Multiple expressions/statements needed
|
||||||
|
# - Need docstrings
|
||||||
|
# - Function will be reused or tested
|
||||||
|
# - Complex logic
|
||||||
|
|
||||||
|
# Example: Complex logic needs regular function
|
||||||
|
def process_value(x):
|
||||||
|
"""Process value with multiple steps."""
|
||||||
|
# Step 1: Validate
|
||||||
|
if x < 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Step 2: Transform
|
||||||
|
result = x ** 2
|
||||||
|
|
||||||
|
# Step 3: Apply ceiling
|
||||||
|
if result > 100:
|
||||||
|
result = 100
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# This CAN'T be easily done with lambda
|
||||||
|
# lambda x: (None if x < 0 else min(x ** 2, 100)) # Gets messy
|
||||||
|
|
||||||
|
print(f"process_value(-5) = {process_value(-5)}")
|
||||||
|
print(f"process_value(5) = {process_value(5)}")
|
||||||
|
print(f"process_value(15) = {process_value(15)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Common Lambda Patterns
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Common Lambda Patterns ---")
|
||||||
|
|
||||||
|
# 1. Default value getter
|
||||||
|
get_value = lambda d, key, default=None: d.get(key, default)
|
||||||
|
data = {"name": "John", "age": 30}
|
||||||
|
print(f"get_value: {get_value(data, 'name')}, {get_value(data, 'email', 'N/A')}")
|
||||||
|
|
||||||
|
# 2. Compose functions
|
||||||
|
compose = lambda f, g: lambda x: f(g(x))
|
||||||
|
add_one = lambda x: x + 1
|
||||||
|
double = lambda x: x * 2
|
||||||
|
add_then_double = compose(double, add_one)
|
||||||
|
print(f"add_then_double(5) = {add_then_double(5)}") # (5+1)*2 = 12
|
||||||
|
|
||||||
|
# 3. Partial application simulation
|
||||||
|
multiply_by = lambda n: lambda x: x * n
|
||||||
|
times_three = multiply_by(3)
|
||||||
|
print(f"times_three(7) = {times_three(7)}")
|
||||||
|
|
||||||
|
# 4. Key extractors for sorting
|
||||||
|
by_key = lambda key: lambda x: x[key]
|
||||||
|
products = [
|
||||||
|
{"name": "Apple", "price": 1.50},
|
||||||
|
{"name": "Banana", "price": 0.75},
|
||||||
|
{"name": "Cherry", "price": 2.00}
|
||||||
|
]
|
||||||
|
sorted_by_price = sorted(products, key=by_key("price"))
|
||||||
|
print(f"\nSorted by price: {[p['name'] for p in sorted_by_price]}")
|
||||||
|
|
||||||
|
# 5. Immediate invocation (IIFE-style)
|
||||||
|
result = (lambda x, y: x ** y)(2, 10)
|
||||||
|
print(f"\nImmediately invoked: 2^10 = {result}")
|
||||||
273
06_modules_packages/01_imports.py
Normal file
273
06_modules_packages/01_imports.py
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_imports.py
|
||||||
|
Topic: Importing Modules and Packages in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates how to import and use modules in Python. Modules are
|
||||||
|
files containing Python code that can be reused across different programs.
|
||||||
|
Python's extensive standard library and third-party packages make imports
|
||||||
|
essential for productive development.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- import statement
|
||||||
|
- from ... import ...
|
||||||
|
- Aliasing with 'as'
|
||||||
|
- Built-in and standard library modules
|
||||||
|
- Relative imports
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic Import Statement
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Import entire module
|
||||||
|
|
||||||
|
print("--- Basic Import ---")
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
# Access functions using module.function
|
||||||
|
print(f"math.pi = {math.pi}")
|
||||||
|
print(f"math.sqrt(16) = {math.sqrt(16)}")
|
||||||
|
print(f"math.ceil(3.2) = {math.ceil(3.2)}")
|
||||||
|
print(f"math.floor(3.8) = {math.floor(3.8)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Import Specific Items
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Import only what you need
|
||||||
|
|
||||||
|
print("\n--- From Import ---")
|
||||||
|
|
||||||
|
from math import pi, sqrt, pow
|
||||||
|
|
||||||
|
# Use directly without module prefix
|
||||||
|
print(f"pi = {pi}")
|
||||||
|
print(f"sqrt(25) = {sqrt(25)}")
|
||||||
|
print(f"pow(2, 8) = {pow(2, 8)}")
|
||||||
|
|
||||||
|
# Import multiple items
|
||||||
|
from datetime import date, time, datetime
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
print(f"\nToday's date: {today}")
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
print(f"Current datetime: {now}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Import with Alias
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Rename modules or items for convenience
|
||||||
|
|
||||||
|
print("\n--- Import with Alias ---")
|
||||||
|
|
||||||
|
# Module alias
|
||||||
|
import random as rnd
|
||||||
|
|
||||||
|
print(f"Random number: {rnd.randint(1, 100)}")
|
||||||
|
print(f"Random choice: {rnd.choice(['apple', 'banana', 'cherry'])}")
|
||||||
|
|
||||||
|
# Common conventions
|
||||||
|
import collections as col
|
||||||
|
import json as json # Usually kept as is
|
||||||
|
import os as os # Usually kept as is
|
||||||
|
|
||||||
|
# Item alias
|
||||||
|
from math import factorial as fact
|
||||||
|
print(f"\nfact(5) = {fact(5)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Import All (Use Sparingly)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Import everything from a module
|
||||||
|
|
||||||
|
print("\n--- Import All (Not Recommended) ---")
|
||||||
|
|
||||||
|
# from math import *
|
||||||
|
# This imports everything, but:
|
||||||
|
# - Can cause naming conflicts
|
||||||
|
# - Makes it unclear where functions come from
|
||||||
|
# - Only use in interactive sessions if at all
|
||||||
|
|
||||||
|
# Better to be explicit about what you import
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Useful Standard Library Modules
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Standard Library Examples ---")
|
||||||
|
|
||||||
|
# os - Operating system interface
|
||||||
|
import os
|
||||||
|
print(f"Current directory: {os.getcwd()}")
|
||||||
|
print(f"Directory separator: {os.sep}")
|
||||||
|
|
||||||
|
# sys - System-specific parameters
|
||||||
|
import sys
|
||||||
|
print(f"\nPython version: {sys.version_info.major}.{sys.version_info.minor}")
|
||||||
|
|
||||||
|
# collections - Specialized containers
|
||||||
|
from collections import Counter, defaultdict
|
||||||
|
|
||||||
|
word_counts = Counter("mississippi")
|
||||||
|
print(f"\nCharacter counts: {dict(word_counts)}")
|
||||||
|
|
||||||
|
# Default dictionary
|
||||||
|
dd = defaultdict(list)
|
||||||
|
dd["fruits"].append("apple")
|
||||||
|
dd["fruits"].append("banana")
|
||||||
|
dd["vegetables"].append("carrot")
|
||||||
|
print(f"defaultdict: {dict(dd)}")
|
||||||
|
|
||||||
|
# json - JSON encoding/decoding
|
||||||
|
import json
|
||||||
|
|
||||||
|
data = {"name": "Alice", "age": 30}
|
||||||
|
json_string = json.dumps(data, indent=2)
|
||||||
|
print(f"\nJSON string:\n{json_string}")
|
||||||
|
|
||||||
|
# re - Regular expressions
|
||||||
|
import re
|
||||||
|
|
||||||
|
text = "Contact: john@email.com or jane@company.org"
|
||||||
|
emails = re.findall(r'\b[\w.-]+@[\w.-]+\.\w+\b', text)
|
||||||
|
print(f"Found emails: {emails}")
|
||||||
|
|
||||||
|
# itertools - Iteration utilities
|
||||||
|
from itertools import combinations, permutations
|
||||||
|
|
||||||
|
items = ['A', 'B', 'C']
|
||||||
|
print(f"\nCombinations of 2: {list(combinations(items, 2))}")
|
||||||
|
print(f"Permutations of 2: {list(permutations(items, 2))}")
|
||||||
|
|
||||||
|
# functools - Higher-order functions
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
@lru_cache(maxsize=100)
|
||||||
|
def fibonacci(n):
|
||||||
|
"""Cached fibonacci for performance."""
|
||||||
|
if n < 2:
|
||||||
|
return n
|
||||||
|
return fibonacci(n-1) + fibonacci(n-2)
|
||||||
|
|
||||||
|
print(f"\nFibonacci(30): {fibonacci(30)}")
|
||||||
|
print(f"Cache info: {fibonacci.cache_info()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Checking Module Attributes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Module Attributes ---")
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
# List all attributes/functions in a module
|
||||||
|
print("Math module functions (first 10):")
|
||||||
|
for name in dir(math)[:10]:
|
||||||
|
print(f" {name}")
|
||||||
|
|
||||||
|
# Module docstring
|
||||||
|
print(f"\nMath module doc: {math.__doc__[:50]}...")
|
||||||
|
|
||||||
|
# Module file location
|
||||||
|
print(f"Math module file: {math.__file__}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Conditional Imports
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Conditional Imports ---")
|
||||||
|
|
||||||
|
# Try to import, use fallback if not available
|
||||||
|
try:
|
||||||
|
import numpy as np
|
||||||
|
HAS_NUMPY = True
|
||||||
|
print("NumPy is available")
|
||||||
|
except ImportError:
|
||||||
|
HAS_NUMPY = False
|
||||||
|
print("NumPy is NOT available")
|
||||||
|
|
||||||
|
# Another pattern
|
||||||
|
import sys
|
||||||
|
if sys.version_info >= (3, 9):
|
||||||
|
from typing import Annotated # Python 3.9+
|
||||||
|
else:
|
||||||
|
print("Annotated not available (Python < 3.9)")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Module Search Path
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Module Search Path ---")
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
print("Python searches for modules in:")
|
||||||
|
for i, path in enumerate(sys.path[:5]): # First 5 paths
|
||||||
|
print(f" {i+1}. {path}")
|
||||||
|
print(f" ... and {len(sys.path) - 5} more locations")
|
||||||
|
|
||||||
|
# Adding custom path (temporarily)
|
||||||
|
# sys.path.append('/my/custom/modules')
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Import Patterns in Projects
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Import Best Practices ---")
|
||||||
|
|
||||||
|
# Standard order for imports:
|
||||||
|
# 1. Standard library imports
|
||||||
|
# 2. Third-party imports
|
||||||
|
# 3. Local/project imports
|
||||||
|
|
||||||
|
# Example of well-organized imports:
|
||||||
|
"""
|
||||||
|
# Standard library
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
|
# Third-party (installed via pip)
|
||||||
|
import requests
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
# Local/project modules
|
||||||
|
from myproject.utils import helper
|
||||||
|
from myproject.models import User
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Import order: Standard -> Third-party -> Local")
|
||||||
|
print("Group imports with blank lines between groups")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Example: Building a Utility
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example ---")
|
||||||
|
|
||||||
|
# Using multiple modules together
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def get_system_info():
|
||||||
|
"""Gather system information using various modules."""
|
||||||
|
return {
|
||||||
|
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
||||||
|
"platform": sys.platform,
|
||||||
|
"cwd": os.getcwd(),
|
||||||
|
"home_dir": str(Path.home()),
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"path_separator": os.sep
|
||||||
|
}
|
||||||
|
|
||||||
|
info = get_system_info()
|
||||||
|
print("System Info:")
|
||||||
|
print(json.dumps(info, indent=2))
|
||||||
443
06_modules_packages/02_custom_modules.py
Normal file
443
06_modules_packages/02_custom_modules.py
Normal file
|
|
@ -0,0 +1,443 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_custom_modules.py
|
||||||
|
Topic: Creating and Using Custom Modules
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates how to create your own modules and packages in Python.
|
||||||
|
Custom modules help organize code, promote reusability, and maintain clean
|
||||||
|
project structures.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Creating module files
|
||||||
|
- The __name__ variable
|
||||||
|
- __init__.py for packages
|
||||||
|
- Relative imports
|
||||||
|
- Module documentation
|
||||||
|
- Publishing modules
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. What is a Module?
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A module is simply a .py file containing Python code
|
||||||
|
|
||||||
|
print("--- What is a Module? ---")
|
||||||
|
|
||||||
|
# Any Python file is a module!
|
||||||
|
# If you have: my_utils.py
|
||||||
|
# You can: import my_utils
|
||||||
|
|
||||||
|
# Example module content (imagine this is saved as 'my_math.py'):
|
||||||
|
"""
|
||||||
|
# my_math.py
|
||||||
|
|
||||||
|
def add(a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def subtract(a, b):
|
||||||
|
return a - b
|
||||||
|
|
||||||
|
PI = 3.14159
|
||||||
|
|
||||||
|
class Calculator:
|
||||||
|
def multiply(self, a, b):
|
||||||
|
return a * b
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("A module is just a Python file that can be imported.")
|
||||||
|
print("It can contain functions, classes, and variables.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. The __name__ Variable
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# __name__ tells you how the module is being used
|
||||||
|
|
||||||
|
print("\n--- The __name__ Variable ---")
|
||||||
|
|
||||||
|
# When a file is run directly: __name__ == "__main__"
|
||||||
|
# When a file is imported: __name__ == module_name
|
||||||
|
|
||||||
|
print(f"Current __name__: {__name__}")
|
||||||
|
|
||||||
|
# Common pattern for executable modules:
|
||||||
|
"""
|
||||||
|
# utils.py
|
||||||
|
|
||||||
|
def useful_function():
|
||||||
|
return "I'm useful!"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Running as main program")
|
||||||
|
print(useful_function())
|
||||||
|
|
||||||
|
# This block only runs when file is executed directly
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Demo of the pattern
|
||||||
|
def demo_function():
|
||||||
|
"""A function that would be in a module."""
|
||||||
|
return "Hello from demo!"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function that runs when executed directly."""
|
||||||
|
print("This module is being run directly!")
|
||||||
|
print(demo_function())
|
||||||
|
|
||||||
|
# This is the __name__ check pattern
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# This runs only when this file is executed directly
|
||||||
|
# Not when it's imported
|
||||||
|
pass # In real code, you'd call main()
|
||||||
|
|
||||||
|
print("\nThe 'if __name__ == \"__main__\"' pattern prevents code from")
|
||||||
|
print("running when the module is imported.")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Module Structure Example
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Module Structure ---")
|
||||||
|
|
||||||
|
# A well-structured module typically has:
|
||||||
|
module_template = '''
|
||||||
|
"""
|
||||||
|
Module: my_module
|
||||||
|
Description: What this module does
|
||||||
|
|
||||||
|
This module provides functionality for...
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Imports at the top
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
# Module-level constants
|
||||||
|
VERSION = "1.0.0"
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
|
||||||
|
# Private helpers (convention: prefix with _)
|
||||||
|
def _internal_helper():
|
||||||
|
"""Not meant to be used outside this module."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Public functions
|
||||||
|
def public_function(arg1: str) -> str:
|
||||||
|
"""
|
||||||
|
This is a public function.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arg1: Description of argument
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Description of return value
|
||||||
|
"""
|
||||||
|
return f"Result: {arg1}"
|
||||||
|
|
||||||
|
# Classes
|
||||||
|
class MyClass:
|
||||||
|
"""A class in the module."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Main entry point (if applicable)
|
||||||
|
def main():
|
||||||
|
"""Entry point when run as script."""
|
||||||
|
print(public_function("test"))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
'''
|
||||||
|
|
||||||
|
print("A module should have:")
|
||||||
|
print(" 1. Module docstring at top")
|
||||||
|
print(" 2. Imports")
|
||||||
|
print(" 3. Constants")
|
||||||
|
print(" 4. Private helpers (prefixed with _)")
|
||||||
|
print(" 5. Public functions/classes")
|
||||||
|
print(" 6. Main block if it's executable")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Creating a Package
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Creating a Package ---")
|
||||||
|
|
||||||
|
# A package is a directory containing:
|
||||||
|
# - __init__.py (can be empty)
|
||||||
|
# - One or more module files
|
||||||
|
|
||||||
|
package_structure = """
|
||||||
|
my_package/
|
||||||
|
__init__.py
|
||||||
|
module1.py
|
||||||
|
module2.py
|
||||||
|
utils/
|
||||||
|
__init__.py
|
||||||
|
helpers.py
|
||||||
|
validators.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Package structure:")
|
||||||
|
print(package_structure)
|
||||||
|
|
||||||
|
# The __init__.py file makes a directory a package
|
||||||
|
# It can be empty or contain initialization code
|
||||||
|
|
||||||
|
init_example = '''
|
||||||
|
# my_package/__init__.py
|
||||||
|
|
||||||
|
# Package version
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
|
# Control what gets imported with "from my_package import *"
|
||||||
|
__all__ = ['module1', 'module2', 'important_function']
|
||||||
|
|
||||||
|
# Import commonly used items for convenient access
|
||||||
|
from .module1 import important_function
|
||||||
|
from .module2 import AnotherClass
|
||||||
|
'''
|
||||||
|
|
||||||
|
print("__init__.py can:")
|
||||||
|
print(" - Define package version")
|
||||||
|
print(" - Control __all__ exports")
|
||||||
|
print(" - Import frequently used items for convenience")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Import Examples for Packages
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Package Import Examples ---")
|
||||||
|
|
||||||
|
import_examples = """
|
||||||
|
# Given this package structure:
|
||||||
|
# myapp/
|
||||||
|
# __init__.py
|
||||||
|
# core.py
|
||||||
|
# utils/
|
||||||
|
# __init__.py
|
||||||
|
# helpers.py
|
||||||
|
|
||||||
|
# Import the package
|
||||||
|
import myapp
|
||||||
|
|
||||||
|
# Import a module from the package
|
||||||
|
from myapp import core
|
||||||
|
|
||||||
|
# Import a function from a module
|
||||||
|
from myapp.core import process_data
|
||||||
|
|
||||||
|
# Import from nested package
|
||||||
|
from myapp.utils import helpers
|
||||||
|
from myapp.utils.helpers import format_output
|
||||||
|
|
||||||
|
# Relative imports (inside the package)
|
||||||
|
# In myapp/core.py:
|
||||||
|
from . import utils # Same-level import
|
||||||
|
from .utils import helpers # Nested module
|
||||||
|
from .utils.helpers import format_output
|
||||||
|
from .. import other_package # Parent-level (if applicable)
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Package import patterns:")
|
||||||
|
print(" import package")
|
||||||
|
print(" from package import module")
|
||||||
|
print(" from package.module import function")
|
||||||
|
print(" from package.sub import submodule")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Simulating a Module
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Simulated Module Example ---")
|
||||||
|
|
||||||
|
# Let's create module-like code inline to demonstrate
|
||||||
|
|
||||||
|
# === This would be in: calculator.py ===
|
||||||
|
class Calculator:
|
||||||
|
"""A simple calculator class."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.history = []
|
||||||
|
|
||||||
|
def add(self, a, b):
|
||||||
|
"""Add two numbers."""
|
||||||
|
result = a + b
|
||||||
|
self.history.append(f"{a} + {b} = {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def subtract(self, a, b):
|
||||||
|
"""Subtract b from a."""
|
||||||
|
result = a - b
|
||||||
|
self.history.append(f"{a} - {b} = {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_history(self):
|
||||||
|
"""Get calculation history."""
|
||||||
|
return self.history
|
||||||
|
|
||||||
|
# === Using the "module" ===
|
||||||
|
calc = Calculator()
|
||||||
|
print(f"5 + 3 = {calc.add(5, 3)}")
|
||||||
|
print(f"10 - 4 = {calc.subtract(10, 4)}")
|
||||||
|
print(f"History: {calc.get_history()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Module Documentation
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Module Documentation ---")
|
||||||
|
|
||||||
|
# Good module documentation includes:
|
||||||
|
# - Module-level docstring
|
||||||
|
# - Function/class docstrings
|
||||||
|
# - Type hints
|
||||||
|
# - Examples
|
||||||
|
|
||||||
|
documented_module = '''
|
||||||
|
"""
|
||||||
|
mymodule - A demonstration of proper documentation
|
||||||
|
|
||||||
|
This module provides utilities for string manipulation
|
||||||
|
and validation. It follows Google-style docstrings.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> from mymodule import validate_email
|
||||||
|
>>> validate_email("test@example.com")
|
||||||
|
True
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
EMAIL_REGEX (str): Regular expression for email validation
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
EMAIL_REGEX = r'^[\w.-]+@[\w.-]+\.\w+$'
|
||||||
|
|
||||||
|
def validate_email(email: str) -> bool:
|
||||||
|
"""
|
||||||
|
Validate an email address format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
email: The email address to validate
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if valid format, False otherwise
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TypeError: If email is not a string
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> validate_email("user@domain.com")
|
||||||
|
True
|
||||||
|
>>> validate_email("invalid-email")
|
||||||
|
False
|
||||||
|
"""
|
||||||
|
if not isinstance(email, str):
|
||||||
|
raise TypeError("email must be a string")
|
||||||
|
return bool(re.match(EMAIL_REGEX, email))
|
||||||
|
'''
|
||||||
|
|
||||||
|
print("Include in your modules:")
|
||||||
|
print(" - Module docstring explaining purpose")
|
||||||
|
print(" - Type hints for parameters and returns")
|
||||||
|
print(" - Examples in docstrings")
|
||||||
|
print(" - Proper exception documentation")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. The __all__ Variable
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- The __all__ Variable ---")
|
||||||
|
|
||||||
|
# __all__ controls what's exported with "from module import *"
|
||||||
|
|
||||||
|
all_example = '''
|
||||||
|
# mymodule.py
|
||||||
|
|
||||||
|
# Only these will be exported with "from mymodule import *"
|
||||||
|
__all__ = ['public_func', 'PublicClass', 'CONSTANT']
|
||||||
|
|
||||||
|
CONSTANT = 42
|
||||||
|
|
||||||
|
def public_func():
|
||||||
|
"""This is meant to be used externally."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _private_func():
|
||||||
|
"""This is internal (won't be exported)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PublicClass:
|
||||||
|
"""This class is for external use."""
|
||||||
|
pass
|
||||||
|
'''
|
||||||
|
|
||||||
|
print("__all__ defines what gets exported with 'import *'")
|
||||||
|
print("Items NOT in __all__ won't be exported")
|
||||||
|
print("Underscore-prefixed items are convention for private")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Reloading Modules
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Reloading Modules ---")
|
||||||
|
|
||||||
|
# During development, you might need to reload a modified module
|
||||||
|
from importlib import reload
|
||||||
|
|
||||||
|
# If you modify 'mymodule', you can reload it:
|
||||||
|
# import mymodule
|
||||||
|
# # ... modify the file ...
|
||||||
|
# reload(mymodule) # Get the updated version
|
||||||
|
|
||||||
|
print("Use importlib.reload() to reload a modified module")
|
||||||
|
print("Useful during development and debugging")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Project Structure Best Practices
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Project Structure Best Practices ---")
|
||||||
|
|
||||||
|
project_structure = """
|
||||||
|
myproject/
|
||||||
|
README.md
|
||||||
|
setup.py or pyproject.toml
|
||||||
|
requirements.txt
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
src/
|
||||||
|
mypackage/
|
||||||
|
__init__.py
|
||||||
|
core.py
|
||||||
|
utils.py
|
||||||
|
models/
|
||||||
|
__init__.py
|
||||||
|
user.py
|
||||||
|
product.py
|
||||||
|
|
||||||
|
tests/
|
||||||
|
__init__.py
|
||||||
|
test_core.py
|
||||||
|
test_utils.py
|
||||||
|
|
||||||
|
docs/
|
||||||
|
index.md
|
||||||
|
|
||||||
|
scripts/
|
||||||
|
run_server.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Recommended project structure:")
|
||||||
|
print(project_structure)
|
||||||
|
|
||||||
|
print("Key points:")
|
||||||
|
print(" - Separate source code (src/) from tests")
|
||||||
|
print(" - Keep configuration at project root")
|
||||||
|
print(" - Use __init__.py for all packages")
|
||||||
|
print(" - Tests mirror source structure")
|
||||||
335
07_error_handling/01_try_except.py
Normal file
335
07_error_handling/01_try_except.py
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_try_except.py
|
||||||
|
Topic: Exception Handling with try-except
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates error handling in Python using try-except blocks.
|
||||||
|
Proper error handling makes your code more robust and user-friendly by
|
||||||
|
gracefully managing unexpected situations.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- try, except, else, finally blocks
|
||||||
|
- Catching specific exceptions
|
||||||
|
- Exception hierarchy
|
||||||
|
- Raising exceptions
|
||||||
|
- Getting exception information
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic try-except
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Catch and handle errors that would otherwise crash your program
|
||||||
|
|
||||||
|
print("--- Basic try-except ---")
|
||||||
|
|
||||||
|
# Without error handling - this would crash:
|
||||||
|
# result = 10 / 0 # ZeroDivisionError!
|
||||||
|
|
||||||
|
# With error handling:
|
||||||
|
try:
|
||||||
|
result = 10 / 0
|
||||||
|
except:
|
||||||
|
print("An error occurred!")
|
||||||
|
|
||||||
|
# Better - catch specific exception:
|
||||||
|
try:
|
||||||
|
result = 10 / 0
|
||||||
|
except ZeroDivisionError:
|
||||||
|
print("Cannot divide by zero!")
|
||||||
|
result = 0
|
||||||
|
|
||||||
|
print(f"Result: {result}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Catching Specific Exceptions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Different errors need different handling
|
||||||
|
|
||||||
|
print("\n--- Catching Specific Exceptions ---")
|
||||||
|
|
||||||
|
def safe_divide(a, b):
|
||||||
|
"""Divide with specific error handling."""
|
||||||
|
try:
|
||||||
|
result = a / b
|
||||||
|
except ZeroDivisionError:
|
||||||
|
print(" Error: Cannot divide by zero")
|
||||||
|
return None
|
||||||
|
except TypeError:
|
||||||
|
print(" Error: Invalid types for division")
|
||||||
|
return None
|
||||||
|
return result
|
||||||
|
|
||||||
|
print(f"10 / 2 = {safe_divide(10, 2)}")
|
||||||
|
print(f"10 / 0 = {safe_divide(10, 0)}")
|
||||||
|
print(f"10 / 'a' = {safe_divide(10, 'a')}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Catching Multiple Exceptions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Multiple Exceptions ---")
|
||||||
|
|
||||||
|
# Catch multiple in one line
|
||||||
|
def process_number(value):
|
||||||
|
"""Process a number with multiple exception handlers."""
|
||||||
|
try:
|
||||||
|
# This might raise ValueError or TypeError
|
||||||
|
number = int(value)
|
||||||
|
result = 100 / number
|
||||||
|
return result
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
print(f" Conversion error: {e}")
|
||||||
|
return None
|
||||||
|
except ZeroDivisionError:
|
||||||
|
print(" Cannot divide by zero")
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(f"process_number('5'): {process_number('5')}")
|
||||||
|
print(f"process_number('abc'): {process_number('abc')}")
|
||||||
|
print(f"process_number('0'): {process_number('0')}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. The else Clause
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Runs only if no exception occurred
|
||||||
|
|
||||||
|
print("\n--- The else Clause ---")
|
||||||
|
|
||||||
|
def divide_with_else(a, b):
|
||||||
|
"""Division with else clause for success logging."""
|
||||||
|
try:
|
||||||
|
result = a / b
|
||||||
|
except ZeroDivisionError:
|
||||||
|
print(" Division failed: Cannot divide by zero")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# Only runs if try block succeeded
|
||||||
|
print(f" Division successful: {a} / {b} = {result}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
divide_with_else(10, 2)
|
||||||
|
divide_with_else(10, 0)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. The finally Clause
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Always runs, regardless of whether an exception occurred
|
||||||
|
|
||||||
|
print("\n--- The finally Clause ---")
|
||||||
|
|
||||||
|
def read_file_example(filename):
|
||||||
|
"""Demonstrate finally for cleanup."""
|
||||||
|
file = None
|
||||||
|
try:
|
||||||
|
print(f" Attempting to open '{filename}'...")
|
||||||
|
# Simulating file operation
|
||||||
|
if filename == "missing.txt":
|
||||||
|
raise FileNotFoundError("File not found")
|
||||||
|
print(" File opened successfully!")
|
||||||
|
return "File content"
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
print(f" Error: {e}")
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
# This ALWAYS runs
|
||||||
|
print(" Cleanup: Closing file (if open)")
|
||||||
|
|
||||||
|
read_file_example("data.txt")
|
||||||
|
print()
|
||||||
|
read_file_example("missing.txt")
|
||||||
|
|
||||||
|
# Real-world pattern with file
|
||||||
|
print("\n--- Real file handling ---")
|
||||||
|
|
||||||
|
# Best practice: use 'with' statement (handles cleanup automatically)
|
||||||
|
# But for learning, here's the try-finally pattern:
|
||||||
|
"""
|
||||||
|
file = None
|
||||||
|
try:
|
||||||
|
file = open("data.txt", "r")
|
||||||
|
content = file.read()
|
||||||
|
finally:
|
||||||
|
if file:
|
||||||
|
file.close() # Always closes, even if error occurred
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Complete try-except-else-finally
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Complete Pattern ---")
|
||||||
|
|
||||||
|
def complete_example(value):
|
||||||
|
"""Show all four clauses in action."""
|
||||||
|
print(f"\n Processing: {value}")
|
||||||
|
try:
|
||||||
|
number = int(value)
|
||||||
|
result = 100 / number
|
||||||
|
except ValueError:
|
||||||
|
print(" EXCEPT: Not a valid integer")
|
||||||
|
result = None
|
||||||
|
except ZeroDivisionError:
|
||||||
|
print(" EXCEPT: Division by zero")
|
||||||
|
result = None
|
||||||
|
else:
|
||||||
|
print(f" ELSE: Success! Result = {result}")
|
||||||
|
finally:
|
||||||
|
print(" FINALLY: This always runs")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
complete_example("10")
|
||||||
|
complete_example("abc")
|
||||||
|
complete_example("0")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Getting Exception Information
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Exception Information ---")
|
||||||
|
|
||||||
|
try:
|
||||||
|
numbers = [1, 2, 3]
|
||||||
|
print(numbers[10])
|
||||||
|
except IndexError as e:
|
||||||
|
print(f"Exception type: {type(e).__name__}")
|
||||||
|
print(f"Exception message: {e}")
|
||||||
|
print(f"Exception args: {e.args}")
|
||||||
|
|
||||||
|
# Getting full traceback
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = 1 / 0
|
||||||
|
except ZeroDivisionError:
|
||||||
|
print("\nFull traceback:")
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Common Built-in Exceptions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Common Exceptions ---")
|
||||||
|
|
||||||
|
exceptions_demo = [
|
||||||
|
("ValueError", "int('abc')"),
|
||||||
|
("TypeError", "'2' + 2"),
|
||||||
|
("IndexError", "[1,2,3][10]"),
|
||||||
|
("KeyError", "{}['missing']"),
|
||||||
|
("AttributeError", "'string'.missing_method()"),
|
||||||
|
("FileNotFoundError", "open('nonexistent.txt')"),
|
||||||
|
("ZeroDivisionError", "1/0"),
|
||||||
|
("ImportError", "import nonexistent_module"),
|
||||||
|
("NameError", "undefined_variable"),
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Common exception types:")
|
||||||
|
for name, example in exceptions_demo:
|
||||||
|
print(f" {name}: {example}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Exception Hierarchy
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Exception Hierarchy ---")
|
||||||
|
|
||||||
|
# All exceptions inherit from BaseException
|
||||||
|
# Most use Exception as base
|
||||||
|
|
||||||
|
hierarchy = """
|
||||||
|
BaseException
|
||||||
|
├── SystemExit
|
||||||
|
├── KeyboardInterrupt
|
||||||
|
├── GeneratorExit
|
||||||
|
└── Exception
|
||||||
|
├── StopIteration
|
||||||
|
├── ArithmeticError
|
||||||
|
│ ├── ZeroDivisionError
|
||||||
|
│ ├── FloatingPointError
|
||||||
|
│ └── OverflowError
|
||||||
|
├── LookupError
|
||||||
|
│ ├── IndexError
|
||||||
|
│ └── KeyError
|
||||||
|
├── ValueError
|
||||||
|
├── TypeError
|
||||||
|
├── AttributeError
|
||||||
|
├── OSError
|
||||||
|
│ ├── FileNotFoundError
|
||||||
|
│ └── PermissionError
|
||||||
|
└── RuntimeError
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("Exception hierarchy (simplified):")
|
||||||
|
print(hierarchy)
|
||||||
|
|
||||||
|
# Catching parent catches all children
|
||||||
|
try:
|
||||||
|
result = 1 / 0
|
||||||
|
except ArithmeticError: # Catches ZeroDivisionError too!
|
||||||
|
print("Caught an arithmetic error (parent class)")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# Example 1: User input validation
|
||||||
|
def get_positive_number(prompt):
|
||||||
|
"""Get a positive number from user (simulated)."""
|
||||||
|
test_inputs = ["abc", "-5", "0", "10"]
|
||||||
|
|
||||||
|
for user_input in test_inputs:
|
||||||
|
print(f" Input: '{user_input}'", end=" → ")
|
||||||
|
try:
|
||||||
|
number = float(user_input)
|
||||||
|
if number <= 0:
|
||||||
|
raise ValueError("Number must be positive")
|
||||||
|
print(f"Valid! ({number})")
|
||||||
|
return number
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Invalid: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
print("User input validation:")
|
||||||
|
get_positive_number("Enter a positive number: ")
|
||||||
|
|
||||||
|
# Example 2: Safe dictionary access
|
||||||
|
def safe_get(dictionary, *keys, default=None):
|
||||||
|
"""Safely navigate nested dictionary."""
|
||||||
|
current = dictionary
|
||||||
|
try:
|
||||||
|
for key in keys:
|
||||||
|
current = current[key]
|
||||||
|
return current
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
return default
|
||||||
|
|
||||||
|
data = {"user": {"profile": {"name": "Alice"}}}
|
||||||
|
print(f"\nNested access: {safe_get(data, 'user', 'profile', 'name')}")
|
||||||
|
print(f"Missing key: {safe_get(data, 'user', 'missing', 'name', default='N/A')}")
|
||||||
|
|
||||||
|
# Example 3: Retry pattern
|
||||||
|
def fetch_with_retry(max_retries=3):
|
||||||
|
"""Simulate fetching with retry on failure."""
|
||||||
|
import random
|
||||||
|
|
||||||
|
for attempt in range(1, max_retries + 1):
|
||||||
|
try:
|
||||||
|
print(f" Attempt {attempt}...", end=" ")
|
||||||
|
# Simulate random failure
|
||||||
|
if random.random() < 0.7: # 70% chance of failure
|
||||||
|
raise ConnectionError("Network error")
|
||||||
|
print("Success!")
|
||||||
|
return "Data"
|
||||||
|
except ConnectionError as e:
|
||||||
|
print(f"Failed: {e}")
|
||||||
|
if attempt == max_retries:
|
||||||
|
print(" All retries exhausted!")
|
||||||
|
return None
|
||||||
|
|
||||||
|
print("\nRetry pattern:")
|
||||||
|
fetch_with_retry()
|
||||||
408
07_error_handling/02_custom_exceptions.py
Normal file
408
07_error_handling/02_custom_exceptions.py
Normal file
|
|
@ -0,0 +1,408 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_custom_exceptions.py
|
||||||
|
Topic: Creating and Using Custom Exceptions
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates how to create your own exception classes in Python.
|
||||||
|
Custom exceptions make your code more expressive and allow you to handle
|
||||||
|
specific error conditions in a meaningful way.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Why use custom exceptions
|
||||||
|
- Creating exception classes
|
||||||
|
- Exception with custom attributes
|
||||||
|
- Exception chaining
|
||||||
|
- Best practices
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Why Custom Exceptions?
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("--- Why Custom Exceptions? ---")
|
||||||
|
|
||||||
|
# Built-in exceptions are generic
|
||||||
|
# Custom exceptions:
|
||||||
|
# - Are more descriptive
|
||||||
|
# - Can carry additional data
|
||||||
|
# - Allow specific handling
|
||||||
|
# - Document error conditions
|
||||||
|
|
||||||
|
print("Benefits of custom exceptions:")
|
||||||
|
print(" - More meaningful error messages")
|
||||||
|
print(" - Carry context-specific data")
|
||||||
|
print(" - Enable targeted exception handling")
|
||||||
|
print(" - Self-documenting code")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Basic Custom Exception
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Basic Custom Exception ---")
|
||||||
|
|
||||||
|
# Inherit from Exception (or a more specific built-in)
|
||||||
|
class InvalidAgeError(Exception):
|
||||||
|
"""Raised when an age value is invalid."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Using the custom exception
|
||||||
|
def set_age(age):
|
||||||
|
"""Set age with validation."""
|
||||||
|
if age < 0:
|
||||||
|
raise InvalidAgeError("Age cannot be negative")
|
||||||
|
if age > 150:
|
||||||
|
raise InvalidAgeError("Age seems unrealistically high")
|
||||||
|
return age
|
||||||
|
|
||||||
|
# Catching custom exception
|
||||||
|
try:
|
||||||
|
set_age(-5)
|
||||||
|
except InvalidAgeError as e:
|
||||||
|
print(f"Caught InvalidAgeError: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
set_age(200)
|
||||||
|
except InvalidAgeError as e:
|
||||||
|
print(f"Caught InvalidAgeError: {e}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Custom Exception with Attributes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Exception with Custom Attributes ---")
|
||||||
|
|
||||||
|
class ValidationError(Exception):
|
||||||
|
"""Raised when validation fails."""
|
||||||
|
|
||||||
|
def __init__(self, field, value, message):
|
||||||
|
self.field = field
|
||||||
|
self.value = value
|
||||||
|
self.message = message
|
||||||
|
# Call parent constructor with full message
|
||||||
|
super().__init__(f"{field}: {message} (got: {value})")
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""Convert error to dictionary (useful for API responses)."""
|
||||||
|
return {
|
||||||
|
"field": self.field,
|
||||||
|
"value": self.value,
|
||||||
|
"message": self.message
|
||||||
|
}
|
||||||
|
|
||||||
|
# Using the enhanced exception
|
||||||
|
def validate_email(email):
|
||||||
|
"""Validate email format."""
|
||||||
|
if not email:
|
||||||
|
raise ValidationError("email", email, "Email is required")
|
||||||
|
if "@" not in email:
|
||||||
|
raise ValidationError("email", email, "Invalid email format")
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate_email("invalid-email")
|
||||||
|
except ValidationError as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
print(f"Field: {e.field}")
|
||||||
|
print(f"Value: {e.value}")
|
||||||
|
print(f"As dict: {e.to_dict()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Exception Hierarchy
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Exception Hierarchy ---")
|
||||||
|
|
||||||
|
# Create a base exception for your module/application
|
||||||
|
class AppError(Exception):
|
||||||
|
"""Base exception for the application."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DatabaseError(AppError):
|
||||||
|
"""Database-related errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConnectionError(DatabaseError):
|
||||||
|
"""Database connection errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class QueryError(DatabaseError):
|
||||||
|
"""Query execution errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AuthenticationError(AppError):
|
||||||
|
"""Authentication-related errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PermissionError(AppError):
|
||||||
|
"""Permission-related errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Now you can catch broadly or specifically
|
||||||
|
def database_operation():
|
||||||
|
raise QueryError("Invalid SQL syntax")
|
||||||
|
|
||||||
|
try:
|
||||||
|
database_operation()
|
||||||
|
except DatabaseError as e:
|
||||||
|
# Catches ConnectionError and QueryError
|
||||||
|
print(f"Database error: {e}")
|
||||||
|
except AppError as e:
|
||||||
|
# Catches all app errors
|
||||||
|
print(f"App error: {e}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Exception Chaining
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Exception Chaining ---")
|
||||||
|
|
||||||
|
class ProcessingError(Exception):
|
||||||
|
"""Error during data processing."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def parse_config(data):
|
||||||
|
"""Parse configuration data."""
|
||||||
|
try:
|
||||||
|
# Simulating a parsing error
|
||||||
|
if not data:
|
||||||
|
raise ValueError("Empty data")
|
||||||
|
return {"parsed": data}
|
||||||
|
except ValueError as e:
|
||||||
|
# Chain the original exception
|
||||||
|
raise ProcessingError("Failed to parse config") from e
|
||||||
|
|
||||||
|
try:
|
||||||
|
parse_config("")
|
||||||
|
except ProcessingError as e:
|
||||||
|
print(f"Processing error: {e}")
|
||||||
|
print(f"Caused by: {e.__cause__}")
|
||||||
|
|
||||||
|
# Using 'from None' to suppress chaining
|
||||||
|
def simple_error():
|
||||||
|
try:
|
||||||
|
raise ValueError("original")
|
||||||
|
except ValueError:
|
||||||
|
raise RuntimeError("new error") from None # Hides original
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Practical Example: User Registration
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example: User Registration ---")
|
||||||
|
|
||||||
|
class RegistrationError(Exception):
|
||||||
|
"""Base class for registration errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UsernameError(RegistrationError):
|
||||||
|
"""Username-related errors."""
|
||||||
|
def __init__(self, username, reason):
|
||||||
|
self.username = username
|
||||||
|
self.reason = reason
|
||||||
|
super().__init__(f"Username '{username}': {reason}")
|
||||||
|
|
||||||
|
class PasswordError(RegistrationError):
|
||||||
|
"""Password-related errors."""
|
||||||
|
def __init__(self, issues):
|
||||||
|
self.issues = issues
|
||||||
|
super().__init__(f"Password issues: {', '.join(issues)}")
|
||||||
|
|
||||||
|
class EmailError(RegistrationError):
|
||||||
|
"""Email-related errors."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate_username(username):
|
||||||
|
"""Validate username."""
|
||||||
|
if len(username) < 3:
|
||||||
|
raise UsernameError(username, "Too short (min 3 chars)")
|
||||||
|
if not username.isalnum():
|
||||||
|
raise UsernameError(username, "Must be alphanumeric")
|
||||||
|
# Check if username exists (simulated)
|
||||||
|
existing = ["admin", "root", "user"]
|
||||||
|
if username.lower() in existing:
|
||||||
|
raise UsernameError(username, "Already taken")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def validate_password(password):
|
||||||
|
"""Validate password strength."""
|
||||||
|
issues = []
|
||||||
|
if len(password) < 8:
|
||||||
|
issues.append("too short")
|
||||||
|
if not any(c.isupper() for c in password):
|
||||||
|
issues.append("needs uppercase")
|
||||||
|
if not any(c.islower() for c in password):
|
||||||
|
issues.append("needs lowercase")
|
||||||
|
if not any(c.isdigit() for c in password):
|
||||||
|
issues.append("needs digit")
|
||||||
|
|
||||||
|
if issues:
|
||||||
|
raise PasswordError(issues)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def register_user(username, password, email):
|
||||||
|
"""Register a new user."""
|
||||||
|
try:
|
||||||
|
validate_username(username)
|
||||||
|
validate_password(password)
|
||||||
|
# validate_email(email) - already defined above
|
||||||
|
print(f" ✓ User '{username}' registered successfully!")
|
||||||
|
return True
|
||||||
|
except RegistrationError as e:
|
||||||
|
print(f" ✗ Registration failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Test registration
|
||||||
|
print("Registration attempts:")
|
||||||
|
register_user("ab", "password123", "test@example.com")
|
||||||
|
register_user("admin", "Password1", "test@example.com")
|
||||||
|
register_user("newuser", "weak", "test@example.com")
|
||||||
|
register_user("newuser", "StrongPass123", "test@example.com")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Context Manager with Exceptions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Exception in Context Manager ---")
|
||||||
|
|
||||||
|
class ManagedResource:
|
||||||
|
"""Resource with cleanup that handles exceptions."""
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
print(f" Acquiring resource: {self.name}")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
print(f" Releasing resource: {self.name}")
|
||||||
|
if exc_type is not None:
|
||||||
|
print(f" Exception occurred: {exc_type.__name__}: {exc_val}")
|
||||||
|
# Return True to suppress the exception
|
||||||
|
# Return False to propagate it
|
||||||
|
return False # Don't suppress exceptions
|
||||||
|
|
||||||
|
# Using the context manager
|
||||||
|
print("Context manager with exception:")
|
||||||
|
try:
|
||||||
|
with ManagedResource("database") as resource:
|
||||||
|
print(f" Using {resource.name}")
|
||||||
|
raise RuntimeError("Something went wrong!")
|
||||||
|
except RuntimeError:
|
||||||
|
print(" Caught exception outside")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Re-raising Exceptions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Re-raising Exceptions ---")
|
||||||
|
|
||||||
|
def process_data(data):
|
||||||
|
"""Process data with logging and re-raise."""
|
||||||
|
try:
|
||||||
|
# Processing that might fail
|
||||||
|
if not data:
|
||||||
|
raise ValueError("Empty data")
|
||||||
|
return data.upper()
|
||||||
|
except ValueError as e:
|
||||||
|
print(f" Logging error: {e}")
|
||||||
|
raise # Re-raise the same exception
|
||||||
|
|
||||||
|
try:
|
||||||
|
process_data("")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Caught re-raised exception: {e}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Exception Documentation
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Exception Documentation ---")
|
||||||
|
|
||||||
|
class APIError(Exception):
|
||||||
|
"""
|
||||||
|
Base exception for API errors.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
status_code: HTTP status code
|
||||||
|
message: Human-readable error message
|
||||||
|
error_code: Application-specific error code
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> raise APIError(404, "Resource not found", "NOT_FOUND")
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, status_code: int, message: str, error_code: str = None):
|
||||||
|
"""
|
||||||
|
Initialize API error.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status_code: HTTP status code (e.g., 400, 404, 500)
|
||||||
|
message: Human-readable error description
|
||||||
|
error_code: Optional application error code
|
||||||
|
"""
|
||||||
|
self.status_code = status_code
|
||||||
|
self.message = message
|
||||||
|
self.error_code = error_code
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
def to_response(self):
|
||||||
|
"""Convert to API response format."""
|
||||||
|
return {
|
||||||
|
"error": {
|
||||||
|
"code": self.error_code,
|
||||||
|
"message": self.message,
|
||||||
|
"status": self.status_code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Using documented exception
|
||||||
|
try:
|
||||||
|
raise APIError(404, "User not found", "USER_NOT_FOUND")
|
||||||
|
except APIError as e:
|
||||||
|
print(f"API Error Response: {e.to_response()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Best Practices
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Best Practices ---")
|
||||||
|
|
||||||
|
best_practices = """
|
||||||
|
1. Inherit from Exception, not BaseException
|
||||||
|
- BaseException includes SystemExit, KeyboardInterrupt
|
||||||
|
|
||||||
|
2. Create a hierarchy for related exceptions
|
||||||
|
- Base exception for your module/app
|
||||||
|
- Specific exceptions inherit from base
|
||||||
|
|
||||||
|
3. Add useful attributes
|
||||||
|
- Include context that helps debugging
|
||||||
|
- Provide methods for serialization (API responses)
|
||||||
|
|
||||||
|
4. Document your exceptions
|
||||||
|
- What conditions trigger them
|
||||||
|
- What attributes they have
|
||||||
|
- How to handle them
|
||||||
|
|
||||||
|
5. Use meaningful names
|
||||||
|
- End with 'Error' or 'Exception'
|
||||||
|
- Be specific: UserNotFoundError, not JustError
|
||||||
|
|
||||||
|
6. Don't over-catch
|
||||||
|
- except Exception: catches too much
|
||||||
|
- Be specific about what you expect
|
||||||
|
|
||||||
|
7. Re-raise when appropriate
|
||||||
|
- Log and re-raise for debugging
|
||||||
|
- Transform to appropriate exception type
|
||||||
|
|
||||||
|
8. Use exception chaining
|
||||||
|
- Use 'from' to preserve original exception
|
||||||
|
- Helps with debugging complex systems
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(best_practices)
|
||||||
389
08_oop/01_classes_objects.py
Normal file
389
08_oop/01_classes_objects.py
Normal file
|
|
@ -0,0 +1,389 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_classes_objects.py
|
||||||
|
Topic: Classes and Objects in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file introduces Object-Oriented Programming (OOP) in Python, focusing on
|
||||||
|
classes and objects. Classes are blueprints for creating objects, which bundle
|
||||||
|
data (attributes) and functionality (methods) together.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Defining classes
|
||||||
|
- Creating objects (instances)
|
||||||
|
- Instance attributes and methods
|
||||||
|
- Class attributes
|
||||||
|
- The self parameter
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. What is a Class?
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A class is a blueprint for creating objects
|
||||||
|
|
||||||
|
print("--- What is a Class? ---")
|
||||||
|
|
||||||
|
# Simple class definition
|
||||||
|
class Dog:
|
||||||
|
"""A simple class representing a dog."""
|
||||||
|
pass # Empty class (for now)
|
||||||
|
|
||||||
|
# Creating objects (instances) from the class
|
||||||
|
dog1 = Dog()
|
||||||
|
dog2 = Dog()
|
||||||
|
|
||||||
|
print(f"dog1 is a: {type(dog1)}")
|
||||||
|
print(f"dog1 and dog2 are different objects: {dog1 is not dog2}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Instance Attributes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Each object can have its own data
|
||||||
|
|
||||||
|
print("\n--- Instance Attributes ---")
|
||||||
|
|
||||||
|
class Person:
|
||||||
|
"""A class representing a person."""
|
||||||
|
|
||||||
|
def __init__(self, name, age):
|
||||||
|
"""Initialize person with name and age."""
|
||||||
|
# self refers to the instance being created
|
||||||
|
self.name = name
|
||||||
|
self.age = age
|
||||||
|
|
||||||
|
# Creating instances with attributes
|
||||||
|
person1 = Person("Alice", 25)
|
||||||
|
person2 = Person("Bob", 30)
|
||||||
|
|
||||||
|
print(f"Person 1: {person1.name}, age {person1.age}")
|
||||||
|
print(f"Person 2: {person2.name}, age {person2.age}")
|
||||||
|
|
||||||
|
# Modifying attributes
|
||||||
|
person1.age = 26
|
||||||
|
print(f"After birthday: {person1.name} is now {person1.age}")
|
||||||
|
|
||||||
|
# Adding new attributes to instance
|
||||||
|
person1.email = "alice@example.com"
|
||||||
|
print(f"Added email: {person1.email}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Instance Methods
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions defined inside a class that operate on instances
|
||||||
|
|
||||||
|
print("\n--- Instance Methods ---")
|
||||||
|
|
||||||
|
class Rectangle:
|
||||||
|
"""A class representing a rectangle."""
|
||||||
|
|
||||||
|
def __init__(self, width, height):
|
||||||
|
"""Initialize rectangle dimensions."""
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
"""Calculate the area of the rectangle."""
|
||||||
|
return self.width * self.height
|
||||||
|
|
||||||
|
def perimeter(self):
|
||||||
|
"""Calculate the perimeter of the rectangle."""
|
||||||
|
return 2 * (self.width + self.height)
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
"""Return a description of the rectangle."""
|
||||||
|
return f"Rectangle {self.width}x{self.height}"
|
||||||
|
|
||||||
|
# Using methods
|
||||||
|
rect = Rectangle(5, 3)
|
||||||
|
print(f"Rectangle: {rect.describe()}")
|
||||||
|
print(f"Area: {rect.area()}")
|
||||||
|
print(f"Perimeter: {rect.perimeter()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. The self Parameter
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# self refers to the current instance
|
||||||
|
|
||||||
|
print("\n--- Understanding self ---")
|
||||||
|
|
||||||
|
class Counter:
|
||||||
|
"""A class demonstrating self."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize counter to 0."""
|
||||||
|
self.count = 0
|
||||||
|
|
||||||
|
def increment(self):
|
||||||
|
"""Increase count by 1."""
|
||||||
|
self.count += 1 # self.count refers to this instance's count
|
||||||
|
return self
|
||||||
|
|
||||||
|
def decrement(self):
|
||||||
|
"""Decrease count by 1."""
|
||||||
|
self.count -= 1
|
||||||
|
return self
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Reset count to 0."""
|
||||||
|
self.count = 0
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_count(self):
|
||||||
|
"""Return current count."""
|
||||||
|
return self.count
|
||||||
|
|
||||||
|
counter1 = Counter()
|
||||||
|
counter2 = Counter()
|
||||||
|
|
||||||
|
counter1.increment()
|
||||||
|
counter1.increment()
|
||||||
|
counter1.increment()
|
||||||
|
|
||||||
|
counter2.increment()
|
||||||
|
|
||||||
|
print(f"Counter 1: {counter1.get_count()}") # 3
|
||||||
|
print(f"Counter 2: {counter2.get_count()}") # 1 - separate instance!
|
||||||
|
|
||||||
|
# Method chaining (returning self)
|
||||||
|
counter1.reset().increment().increment()
|
||||||
|
print(f"After chaining: {counter1.get_count()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Class Attributes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Attributes shared by all instances of a class
|
||||||
|
|
||||||
|
print("\n--- Class Attributes ---")
|
||||||
|
|
||||||
|
class Car:
|
||||||
|
"""A class with class attributes."""
|
||||||
|
|
||||||
|
# Class attribute - shared by all instances
|
||||||
|
wheels = 4
|
||||||
|
count = 0 # Track number of cars created
|
||||||
|
|
||||||
|
def __init__(self, brand, model):
|
||||||
|
"""Initialize car with brand and model."""
|
||||||
|
self.brand = brand # Instance attribute
|
||||||
|
self.model = model # Instance attribute
|
||||||
|
Car.count += 1 # Increment class attribute
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
"""Describe the car."""
|
||||||
|
return f"{self.brand} {self.model} ({Car.wheels} wheels)"
|
||||||
|
|
||||||
|
# Creating cars
|
||||||
|
car1 = Car("Toyota", "Camry")
|
||||||
|
car2 = Car("Honda", "Civic")
|
||||||
|
car3 = Car("Ford", "Mustang")
|
||||||
|
|
||||||
|
print(f"Car 1: {car1.describe()}")
|
||||||
|
print(f"Car 2: {car2.describe()}")
|
||||||
|
print(f"Total cars created: {Car.count}")
|
||||||
|
|
||||||
|
# Accessing class attribute from class or instance
|
||||||
|
print(f"Car.wheels: {Car.wheels}")
|
||||||
|
print(f"car1.wheels: {car1.wheels}")
|
||||||
|
|
||||||
|
# Modifying class attribute
|
||||||
|
Car.wheels = 6 # Now all cars have 6 wheels
|
||||||
|
print(f"After modification - car1.wheels: {car1.wheels}")
|
||||||
|
print(f"After modification - car2.wheels: {car2.wheels}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Private Attributes (Convention)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Python uses naming conventions for privacy
|
||||||
|
|
||||||
|
print("\n--- Private Attributes ---")
|
||||||
|
|
||||||
|
class BankAccount:
|
||||||
|
"""A class demonstrating private attributes."""
|
||||||
|
|
||||||
|
def __init__(self, owner, balance=0):
|
||||||
|
"""Initialize bank account."""
|
||||||
|
self.owner = owner # Public
|
||||||
|
self._balance = balance # Protected (convention)
|
||||||
|
self.__id = id(self) # Private (name mangling)
|
||||||
|
|
||||||
|
def deposit(self, amount):
|
||||||
|
"""Deposit money into account."""
|
||||||
|
if amount > 0:
|
||||||
|
self._balance += amount
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def withdraw(self, amount):
|
||||||
|
"""Withdraw money from account."""
|
||||||
|
if 0 < amount <= self._balance:
|
||||||
|
self._balance -= amount
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_balance(self):
|
||||||
|
"""Get current balance."""
|
||||||
|
return self._balance
|
||||||
|
|
||||||
|
account = BankAccount("Alice", 1000)
|
||||||
|
account.deposit(500)
|
||||||
|
account.withdraw(200)
|
||||||
|
print(f"Account owner: {account.owner}")
|
||||||
|
print(f"Balance: {account.get_balance()}")
|
||||||
|
|
||||||
|
# Can still access protected (but shouldn't)
|
||||||
|
print(f"Protected _balance: {account._balance}")
|
||||||
|
|
||||||
|
# Mangled name for private
|
||||||
|
print(f"Mangled private __id: {account._BankAccount__id}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Methods that Return New Objects
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Returning Objects ---")
|
||||||
|
|
||||||
|
class Point:
|
||||||
|
"""A class representing a 2D point."""
|
||||||
|
|
||||||
|
def __init__(self, x, y):
|
||||||
|
"""Initialize point coordinates."""
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def move(self, dx, dy):
|
||||||
|
"""Return a new point moved by (dx, dy)."""
|
||||||
|
return Point(self.x + dx, self.y + dy)
|
||||||
|
|
||||||
|
def distance_to(self, other):
|
||||||
|
"""Calculate distance to another point."""
|
||||||
|
return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""String representation."""
|
||||||
|
return f"Point({self.x}, {self.y})"
|
||||||
|
|
||||||
|
p1 = Point(0, 0)
|
||||||
|
p2 = p1.move(3, 4)
|
||||||
|
print(f"Original point: {p1}")
|
||||||
|
print(f"New point: {p2}")
|
||||||
|
print(f"Distance: {p1.distance_to(p2)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Special Methods (Dunder Methods)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Methods with double underscores have special meanings
|
||||||
|
|
||||||
|
print("\n--- Special Methods ---")
|
||||||
|
|
||||||
|
class Vector:
|
||||||
|
"""A class demonstrating special methods."""
|
||||||
|
|
||||||
|
def __init__(self, x, y):
|
||||||
|
"""Initialize vector."""
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""Human-readable string representation."""
|
||||||
|
return f"Vector({self.x}, {self.y})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Developer representation (for debugging)."""
|
||||||
|
return f"Vector(x={self.x}, y={self.y})"
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Length (in this case, magnitude as int)."""
|
||||||
|
return int((self.x ** 2 + self.y ** 2) ** 0.5)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
"""Add two vectors."""
|
||||||
|
return Vector(self.x + other.x, self.y + other.y)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Check equality."""
|
||||||
|
return self.x == other.x and self.y == other.y
|
||||||
|
|
||||||
|
v1 = Vector(3, 4)
|
||||||
|
v2 = Vector(1, 2)
|
||||||
|
v3 = v1 + v2 # Uses __add__
|
||||||
|
|
||||||
|
print(f"v1: {v1}") # Uses __str__
|
||||||
|
print(f"v1 + v2 = {v3}") # Uses __add__ and __str__
|
||||||
|
print(f"len(v1): {len(v1)}") # Uses __len__
|
||||||
|
print(f"v1 == Vector(3, 4): {v1 == Vector(3, 4)}") # Uses __eq__
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Practical Example: Student Class
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example: Student ---")
|
||||||
|
|
||||||
|
class Student:
|
||||||
|
"""A class representing a student."""
|
||||||
|
|
||||||
|
school_name = "Python Academy" # Class attribute
|
||||||
|
|
||||||
|
def __init__(self, name, student_id):
|
||||||
|
"""Initialize student."""
|
||||||
|
self.name = name
|
||||||
|
self.student_id = student_id
|
||||||
|
self.grades = []
|
||||||
|
|
||||||
|
def add_grade(self, grade):
|
||||||
|
"""Add a grade (0-100)."""
|
||||||
|
if 0 <= grade <= 100:
|
||||||
|
self.grades.append(grade)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_average(self):
|
||||||
|
"""Calculate grade average."""
|
||||||
|
if not self.grades:
|
||||||
|
return 0
|
||||||
|
return sum(self.grades) / len(self.grades)
|
||||||
|
|
||||||
|
def get_letter_grade(self):
|
||||||
|
"""Get letter grade based on average."""
|
||||||
|
avg = self.get_average()
|
||||||
|
if avg >= 90: return 'A'
|
||||||
|
if avg >= 80: return 'B'
|
||||||
|
if avg >= 70: return 'C'
|
||||||
|
if avg >= 60: return 'D'
|
||||||
|
return 'F'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""String representation."""
|
||||||
|
return f"{self.name} (ID: {self.student_id}) - {self.school_name}"
|
||||||
|
|
||||||
|
# Using the Student class
|
||||||
|
student = Student("Baraa", "S12345")
|
||||||
|
student.add_grade(85)
|
||||||
|
student.add_grade(90)
|
||||||
|
student.add_grade(78)
|
||||||
|
|
||||||
|
print(student)
|
||||||
|
print(f"Grades: {student.grades}")
|
||||||
|
print(f"Average: {student.get_average():.1f}")
|
||||||
|
print(f"Letter Grade: {student.get_letter_grade()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Summary
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Summary ---")
|
||||||
|
|
||||||
|
summary = """
|
||||||
|
Key OOP concepts:
|
||||||
|
- Class: Blueprint for objects
|
||||||
|
- Object: Instance of a class
|
||||||
|
- self: Reference to current instance
|
||||||
|
- __init__: Constructor method
|
||||||
|
- Instance attributes: Unique to each object
|
||||||
|
- Class attributes: Shared by all instances
|
||||||
|
- Methods: Functions that belong to a class
|
||||||
|
- Special methods: __str__, __repr__, __add__, etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(summary)
|
||||||
446
08_oop/02_init_methods.py
Normal file
446
08_oop/02_init_methods.py
Normal file
|
|
@ -0,0 +1,446 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_init_methods.py
|
||||||
|
Topic: Constructors and Initialization in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file explores the __init__ method and other initialization patterns
|
||||||
|
in Python classes. The __init__ method is called when creating new objects
|
||||||
|
and sets up the initial state of the instance.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- The __init__ constructor
|
||||||
|
- Required vs optional parameters
|
||||||
|
- Default values
|
||||||
|
- Property decorators
|
||||||
|
- Alternative constructors (classmethod)
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic __init__ Method
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# __init__ initializes newly created objects
|
||||||
|
|
||||||
|
print("--- Basic __init__ ---")
|
||||||
|
|
||||||
|
class Book:
|
||||||
|
"""A class representing a book."""
|
||||||
|
|
||||||
|
def __init__(self, title, author):
|
||||||
|
"""
|
||||||
|
Initialize a new book.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title: The book's title
|
||||||
|
author: The book's author
|
||||||
|
"""
|
||||||
|
self.title = title
|
||||||
|
self.author = author
|
||||||
|
|
||||||
|
# Creating instances calls __init__ automatically
|
||||||
|
book = Book("1984", "George Orwell")
|
||||||
|
print(f"Book: '{book.title}' by {book.author}")
|
||||||
|
|
||||||
|
# Without proper arguments, you get an error:
|
||||||
|
# book = Book() # TypeError: missing 2 required positional arguments
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Default Parameter Values
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Make some parameters optional
|
||||||
|
|
||||||
|
print("\n--- Default Values ---")
|
||||||
|
|
||||||
|
class User:
|
||||||
|
"""A class with default parameter values."""
|
||||||
|
|
||||||
|
def __init__(self, username, email=None, role="user", active=True):
|
||||||
|
"""
|
||||||
|
Initialize a new user.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
username: Required username
|
||||||
|
email: Optional email address
|
||||||
|
role: User role (default: "user")
|
||||||
|
active: Is user active (default: True)
|
||||||
|
"""
|
||||||
|
self.username = username
|
||||||
|
self.email = email
|
||||||
|
self.role = role
|
||||||
|
self.active = active
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
email = self.email or "No email"
|
||||||
|
status = "Active" if self.active else "Inactive"
|
||||||
|
return f"{self.username} ({self.role}) - {email} - {status}"
|
||||||
|
|
||||||
|
# Various ways to create users
|
||||||
|
user1 = User("alice")
|
||||||
|
user2 = User("bob", "bob@example.com")
|
||||||
|
user3 = User("charlie", "charlie@example.com", "admin")
|
||||||
|
user4 = User("dave", active=False)
|
||||||
|
|
||||||
|
print(user1)
|
||||||
|
print(user2)
|
||||||
|
print(user3)
|
||||||
|
print(user4)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Validation in __init__
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Validate and process data during initialization
|
||||||
|
|
||||||
|
print("\n--- Validation in __init__ ---")
|
||||||
|
|
||||||
|
class Temperature:
|
||||||
|
"""A class with validation in __init__."""
|
||||||
|
|
||||||
|
def __init__(self, celsius):
|
||||||
|
"""
|
||||||
|
Initialize temperature in Celsius.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
celsius: Temperature in Celsius
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If temperature is below absolute zero
|
||||||
|
"""
|
||||||
|
if celsius < -273.15:
|
||||||
|
raise ValueError("Temperature cannot be below absolute zero!")
|
||||||
|
self._celsius = celsius
|
||||||
|
|
||||||
|
@property
|
||||||
|
def celsius(self):
|
||||||
|
"""Get temperature in Celsius."""
|
||||||
|
return self._celsius
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fahrenheit(self):
|
||||||
|
"""Get temperature in Fahrenheit."""
|
||||||
|
return (self._celsius * 9/5) + 32
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kelvin(self):
|
||||||
|
"""Get temperature in Kelvin."""
|
||||||
|
return self._celsius + 273.15
|
||||||
|
|
||||||
|
temp = Temperature(25)
|
||||||
|
print(f"Temperature: {temp.celsius}°C = {temp.fahrenheit}°F = {temp.kelvin}K")
|
||||||
|
|
||||||
|
# Validation in action
|
||||||
|
try:
|
||||||
|
invalid_temp = Temperature(-300)
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Validation error: {e}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Mutable Default Arguments Warning
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Never use mutable objects as default arguments!
|
||||||
|
|
||||||
|
print("\n--- Mutable Defaults Warning ---")
|
||||||
|
|
||||||
|
# BAD - mutable default argument
|
||||||
|
class BadStudent:
|
||||||
|
def __init__(self, name, grades=[]): # DON'T DO THIS!
|
||||||
|
self.name = name
|
||||||
|
self.grades = grades
|
||||||
|
|
||||||
|
s1 = BadStudent("Alice")
|
||||||
|
s1.grades.append(90)
|
||||||
|
|
||||||
|
s2 = BadStudent("Bob")
|
||||||
|
print(f"Bob's grades (should be empty): {s2.grades}") # Contains 90!
|
||||||
|
|
||||||
|
# GOOD - use None and create new list
|
||||||
|
class GoodStudent:
|
||||||
|
def __init__(self, name, grades=None):
|
||||||
|
self.name = name
|
||||||
|
self.grades = grades if grades is not None else []
|
||||||
|
|
||||||
|
g1 = GoodStudent("Carol")
|
||||||
|
g1.grades.append(85)
|
||||||
|
|
||||||
|
g2 = GoodStudent("Dan")
|
||||||
|
print(f"Dan's grades (correctly empty): {g2.grades}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Property Decorators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Control attribute access with getters and setters
|
||||||
|
|
||||||
|
print("\n--- Property Decorators ---")
|
||||||
|
|
||||||
|
class Circle:
|
||||||
|
"""A circle with property decorators."""
|
||||||
|
|
||||||
|
def __init__(self, radius):
|
||||||
|
"""Initialize circle with radius."""
|
||||||
|
self._radius = radius # Protected attribute
|
||||||
|
|
||||||
|
@property
|
||||||
|
def radius(self):
|
||||||
|
"""Get the radius."""
|
||||||
|
return self._radius
|
||||||
|
|
||||||
|
@radius.setter
|
||||||
|
def radius(self, value):
|
||||||
|
"""Set the radius with validation."""
|
||||||
|
if value <= 0:
|
||||||
|
raise ValueError("Radius must be positive")
|
||||||
|
self._radius = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def diameter(self):
|
||||||
|
"""Get the diameter (read-only computed property)."""
|
||||||
|
return self._radius * 2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area(self):
|
||||||
|
"""Get the area (read-only computed property)."""
|
||||||
|
import math
|
||||||
|
return math.pi * self._radius ** 2
|
||||||
|
|
||||||
|
circle = Circle(5)
|
||||||
|
print(f"Radius: {circle.radius}")
|
||||||
|
print(f"Diameter: {circle.diameter}")
|
||||||
|
print(f"Area: {circle.area:.2f}")
|
||||||
|
|
||||||
|
# Using setter
|
||||||
|
circle.radius = 10
|
||||||
|
print(f"New radius: {circle.radius}")
|
||||||
|
print(f"New area: {circle.area:.2f}")
|
||||||
|
|
||||||
|
# Validation in setter
|
||||||
|
try:
|
||||||
|
circle.radius = -5
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Setter validation: {e}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Alternative Constructors with @classmethod
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Create objects in different ways
|
||||||
|
|
||||||
|
print("\n--- Alternative Constructors ---")
|
||||||
|
|
||||||
|
class Person:
|
||||||
|
"""A class with alternative constructors."""
|
||||||
|
|
||||||
|
def __init__(self, first_name, last_name, age):
|
||||||
|
"""Standard constructor."""
|
||||||
|
self.first_name = first_name
|
||||||
|
self.last_name = last_name
|
||||||
|
self.age = age
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_birth_year(cls, first_name, last_name, birth_year):
|
||||||
|
"""Create person from birth year instead of age."""
|
||||||
|
import datetime
|
||||||
|
age = datetime.datetime.now().year - birth_year
|
||||||
|
return cls(first_name, last_name, age)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_full_name(cls, full_name, age):
|
||||||
|
"""Create person from full name string."""
|
||||||
|
parts = full_name.split()
|
||||||
|
first_name = parts[0]
|
||||||
|
last_name = parts[-1] if len(parts) > 1 else ""
|
||||||
|
return cls(first_name, last_name, age)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data):
|
||||||
|
"""Create person from dictionary."""
|
||||||
|
return cls(
|
||||||
|
data.get('first_name', ''),
|
||||||
|
data.get('last_name', ''),
|
||||||
|
data.get('age', 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.first_name} {self.last_name}, age {self.age}"
|
||||||
|
|
||||||
|
# Using different constructors
|
||||||
|
p1 = Person("John", "Doe", 30)
|
||||||
|
p2 = Person.from_birth_year("Jane", "Smith", 1995)
|
||||||
|
p3 = Person.from_full_name("Bob Johnson", 25)
|
||||||
|
p4 = Person.from_dict({"first_name": "Alice", "last_name": "Williams", "age": 28})
|
||||||
|
|
||||||
|
print("Created using different constructors:")
|
||||||
|
print(f" Standard: {p1}")
|
||||||
|
print(f" From birth year: {p2}")
|
||||||
|
print(f" From full name: {p3}")
|
||||||
|
print(f" From dict: {p4}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. __new__ vs __init__
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# __new__ creates the instance, __init__ initializes it
|
||||||
|
|
||||||
|
print("\n--- __new__ vs __init__ ---")
|
||||||
|
|
||||||
|
class Singleton:
|
||||||
|
"""A singleton class using __new__."""
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
"""Create instance only if one doesn't exist."""
|
||||||
|
if cls._instance is None:
|
||||||
|
print(" Creating new instance...")
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
else:
|
||||||
|
print(" Returning existing instance...")
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize (called every time)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Both variables point to the same instance
|
||||||
|
print("Creating s1:")
|
||||||
|
s1 = Singleton()
|
||||||
|
print("Creating s2:")
|
||||||
|
s2 = Singleton()
|
||||||
|
print(f"Same instance? {s1 is s2}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Initialization with Inheritance
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Initialization with Inheritance ---")
|
||||||
|
|
||||||
|
class Animal:
|
||||||
|
"""Base class for animals."""
|
||||||
|
|
||||||
|
def __init__(self, name, species):
|
||||||
|
"""Initialize animal."""
|
||||||
|
self.name = name
|
||||||
|
self.species = species
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
"""Make a sound."""
|
||||||
|
return "Some sound"
|
||||||
|
|
||||||
|
class Dog(Animal):
|
||||||
|
"""Dog class inheriting from Animal."""
|
||||||
|
|
||||||
|
def __init__(self, name, breed):
|
||||||
|
"""Initialize dog with name and breed."""
|
||||||
|
# Call parent's __init__
|
||||||
|
super().__init__(name, species="Canis familiaris")
|
||||||
|
self.breed = breed
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
"""Dogs bark."""
|
||||||
|
return "Woof!"
|
||||||
|
|
||||||
|
class Cat(Animal):
|
||||||
|
"""Cat class inheriting from Animal."""
|
||||||
|
|
||||||
|
def __init__(self, name, indoor=True):
|
||||||
|
"""Initialize cat."""
|
||||||
|
super().__init__(name, species="Felis catus")
|
||||||
|
self.indoor = indoor
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
"""Cats meow."""
|
||||||
|
return "Meow!"
|
||||||
|
|
||||||
|
dog = Dog("Buddy", "Golden Retriever")
|
||||||
|
cat = Cat("Whiskers", indoor=True)
|
||||||
|
|
||||||
|
print(f"Dog: {dog.name} ({dog.breed}) says {dog.speak()}")
|
||||||
|
print(f"Cat: {cat.name} (Indoor: {cat.indoor}) says {cat.speak()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Complex Initialization Example
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Complex Initialization ---")
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class Order:
|
||||||
|
"""A class with complex initialization."""
|
||||||
|
|
||||||
|
_order_counter = 0
|
||||||
|
|
||||||
|
def __init__(self, customer_name, items=None, discount=0):
|
||||||
|
"""
|
||||||
|
Initialize an order.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
customer_name: Name of the customer
|
||||||
|
items: List of (item_name, price, quantity) tuples
|
||||||
|
discount: Discount percentage (0-100)
|
||||||
|
"""
|
||||||
|
# Auto-generate order ID
|
||||||
|
Order._order_counter += 1
|
||||||
|
self.order_id = f"ORD-{Order._order_counter:04d}"
|
||||||
|
|
||||||
|
# Set basic attributes
|
||||||
|
self.customer_name = customer_name
|
||||||
|
self.items = items or []
|
||||||
|
self.discount = max(0, min(100, discount)) # Clamp to 0-100
|
||||||
|
|
||||||
|
# Auto-generated attributes
|
||||||
|
self.created_at = datetime.now()
|
||||||
|
self._subtotal = None # Cached calculation
|
||||||
|
|
||||||
|
def add_item(self, name, price, quantity=1):
|
||||||
|
"""Add an item to the order."""
|
||||||
|
self.items.append((name, price, quantity))
|
||||||
|
self._subtotal = None # Invalidate cache
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subtotal(self):
|
||||||
|
"""Calculate subtotal."""
|
||||||
|
if self._subtotal is None:
|
||||||
|
self._subtotal = sum(price * qty for _, price, qty in self.items)
|
||||||
|
return self._subtotal
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total(self):
|
||||||
|
"""Calculate total with discount."""
|
||||||
|
return self.subtotal * (1 - self.discount / 100)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Order {self.order_id} for {self.customer_name}: ${self.total:.2f}"
|
||||||
|
|
||||||
|
# Create orders
|
||||||
|
order1 = Order("Alice")
|
||||||
|
order1.add_item("Widget", 9.99, 2)
|
||||||
|
order1.add_item("Gadget", 14.99, 1)
|
||||||
|
|
||||||
|
order2 = Order("Bob", discount=10)
|
||||||
|
order2.add_item("Premium Widget", 29.99, 1)
|
||||||
|
|
||||||
|
print(order1)
|
||||||
|
print(f" Subtotal: ${order1.subtotal:.2f}")
|
||||||
|
print(f" Total: ${order1.total:.2f}")
|
||||||
|
|
||||||
|
print(order2)
|
||||||
|
print(f" Subtotal: ${order2.subtotal:.2f}")
|
||||||
|
print(f" Total (10% off): ${order2.total:.2f}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Summary
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Summary ---")
|
||||||
|
|
||||||
|
summary = """
|
||||||
|
Initialization patterns:
|
||||||
|
- __init__: Main constructor, initializes attributes
|
||||||
|
- Default values: Make parameters optional
|
||||||
|
- Validation: Raise errors for invalid input
|
||||||
|
- Properties: Computed/validated attributes
|
||||||
|
- @classmethod: Alternative constructors
|
||||||
|
- __new__: Instance creation (before __init__)
|
||||||
|
- super().__init__(): Call parent constructor
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(summary)
|
||||||
436
08_oop/03_inheritance.py
Normal file
436
08_oop/03_inheritance.py
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_inheritance.py
|
||||||
|
Topic: Inheritance in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates inheritance in Python, a fundamental OOP concept that
|
||||||
|
allows classes to inherit attributes and methods from other classes. This
|
||||||
|
promotes code reuse and establishes relationships between classes.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Single inheritance
|
||||||
|
- Method overriding
|
||||||
|
- super() function
|
||||||
|
- Multiple inheritance
|
||||||
|
- Method Resolution Order (MRO)
|
||||||
|
- Abstract base classes
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic Inheritance
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Child class inherits from parent class
|
||||||
|
|
||||||
|
print("--- Basic Inheritance ---")
|
||||||
|
|
||||||
|
class Animal:
|
||||||
|
"""Base class for all animals."""
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
"""Initialize animal with a name."""
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
"""Make a generic sound."""
|
||||||
|
return "Some generic sound"
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
"""Describe the animal."""
|
||||||
|
return f"I am {self.name}"
|
||||||
|
|
||||||
|
class Dog(Animal):
|
||||||
|
"""Dog class inheriting from Animal."""
|
||||||
|
pass # Inherits everything from Animal
|
||||||
|
|
||||||
|
# Dog has all Animal's methods
|
||||||
|
buddy = Dog("Buddy")
|
||||||
|
print(buddy.describe())
|
||||||
|
print(f"Says: {buddy.speak()}")
|
||||||
|
print(f"Dog is instance of Animal: {isinstance(buddy, Animal)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Method Overriding
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Child class can override parent methods
|
||||||
|
|
||||||
|
print("\n--- Method Overriding ---")
|
||||||
|
|
||||||
|
class Cat(Animal):
|
||||||
|
"""Cat class with overridden method."""
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
"""Cats meow instead of generic sound."""
|
||||||
|
return "Meow!"
|
||||||
|
|
||||||
|
class Cow(Animal):
|
||||||
|
"""Cow class with overridden method."""
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
"""Cows moo."""
|
||||||
|
return "Moo!"
|
||||||
|
|
||||||
|
# Each animal speaks differently
|
||||||
|
animals = [Dog("Rex"), Cat("Whiskers"), Cow("Bessie")]
|
||||||
|
|
||||||
|
print("Each animal speaks:")
|
||||||
|
for animal in animals:
|
||||||
|
print(f" {animal.name}: {animal.speak()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Extending Parent Class with super()
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use super() to call parent methods
|
||||||
|
|
||||||
|
print("\n--- Using super() ---")
|
||||||
|
|
||||||
|
class Bird(Animal):
|
||||||
|
"""Bird class extending Animal."""
|
||||||
|
|
||||||
|
def __init__(self, name, can_fly=True):
|
||||||
|
"""Initialize bird with flying ability."""
|
||||||
|
super().__init__(name) # Call parent's __init__
|
||||||
|
self.can_fly = can_fly
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
"""Birds chirp."""
|
||||||
|
return "Chirp!"
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
"""Extend parent's describe method."""
|
||||||
|
base = super().describe() # Get parent's description
|
||||||
|
fly_status = "can fly" if self.can_fly else "cannot fly"
|
||||||
|
return f"{base} and I {fly_status}"
|
||||||
|
|
||||||
|
sparrow = Bird("Sparrow", can_fly=True)
|
||||||
|
penguin = Bird("Penny", can_fly=False)
|
||||||
|
|
||||||
|
print(sparrow.describe())
|
||||||
|
print(penguin.describe())
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Inheritance Chain
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Classes can inherit from other child classes
|
||||||
|
|
||||||
|
print("\n--- Inheritance Chain ---")
|
||||||
|
|
||||||
|
class Vehicle:
|
||||||
|
"""Base vehicle class."""
|
||||||
|
|
||||||
|
def __init__(self, brand):
|
||||||
|
self.brand = brand
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
return "Vehicle starting..."
|
||||||
|
|
||||||
|
class Car(Vehicle):
|
||||||
|
"""Car inherits from Vehicle."""
|
||||||
|
|
||||||
|
def __init__(self, brand, model):
|
||||||
|
super().__init__(brand)
|
||||||
|
self.model = model
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
return f"{self.brand} {self.model} engine starting..."
|
||||||
|
|
||||||
|
class ElectricCar(Car):
|
||||||
|
"""ElectricCar inherits from Car."""
|
||||||
|
|
||||||
|
def __init__(self, brand, model, battery_kw):
|
||||||
|
super().__init__(brand, model)
|
||||||
|
self.battery_kw = battery_kw
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
return f"{self.brand} {self.model} powering up silently... (Battery: {self.battery_kw}kW)"
|
||||||
|
|
||||||
|
def charge(self):
|
||||||
|
return f"Charging {self.battery_kw}kW battery..."
|
||||||
|
|
||||||
|
# Create instances
|
||||||
|
regular_car = Car("Toyota", "Camry")
|
||||||
|
electric_car = ElectricCar("Tesla", "Model 3", 75)
|
||||||
|
|
||||||
|
print(regular_car.start())
|
||||||
|
print(electric_car.start())
|
||||||
|
print(electric_car.charge())
|
||||||
|
|
||||||
|
# Check inheritance
|
||||||
|
print(f"\nElectricCar is Car: {isinstance(electric_car, Car)}")
|
||||||
|
print(f"ElectricCar is Vehicle: {isinstance(electric_car, Vehicle)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Multiple Inheritance
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A class can inherit from multiple parent classes
|
||||||
|
|
||||||
|
print("\n--- Multiple Inheritance ---")
|
||||||
|
|
||||||
|
class Flyable:
|
||||||
|
"""Mixin class for flying ability."""
|
||||||
|
|
||||||
|
def fly(self):
|
||||||
|
return f"{self.name} is flying!"
|
||||||
|
|
||||||
|
def land(self):
|
||||||
|
return f"{self.name} has landed."
|
||||||
|
|
||||||
|
class Swimmable:
|
||||||
|
"""Mixin class for swimming ability."""
|
||||||
|
|
||||||
|
def swim(self):
|
||||||
|
return f"{self.name} is swimming!"
|
||||||
|
|
||||||
|
def dive(self):
|
||||||
|
return f"{self.name} dives underwater."
|
||||||
|
|
||||||
|
class Duck(Animal, Flyable, Swimmable):
|
||||||
|
"""Duck can do everything!"""
|
||||||
|
|
||||||
|
def speak(self):
|
||||||
|
return "Quack!"
|
||||||
|
|
||||||
|
# Duck has methods from all parent classes
|
||||||
|
duck = Duck("Donald")
|
||||||
|
print(f"{duck.name} says: {duck.speak()}")
|
||||||
|
print(duck.fly())
|
||||||
|
print(duck.swim())
|
||||||
|
print(duck.describe())
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Method Resolution Order (MRO)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Python determines method lookup order
|
||||||
|
|
||||||
|
print("\n--- Method Resolution Order (MRO) ---")
|
||||||
|
|
||||||
|
class A:
|
||||||
|
def method(self):
|
||||||
|
return "A"
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
def method(self):
|
||||||
|
return "B"
|
||||||
|
|
||||||
|
class C(A):
|
||||||
|
def method(self):
|
||||||
|
return "C"
|
||||||
|
|
||||||
|
class D(B, C):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class E(B, C):
|
||||||
|
def method(self):
|
||||||
|
return "E"
|
||||||
|
|
||||||
|
# Check MRO
|
||||||
|
print("MRO for class D:")
|
||||||
|
for cls in D.__mro__:
|
||||||
|
print(f" {cls.__name__}")
|
||||||
|
|
||||||
|
d = D()
|
||||||
|
e = E()
|
||||||
|
print(f"\nd.method(): {d.method()}") # Returns "B" (first in MRO after D)
|
||||||
|
print(f"e.method(): {e.method()}") # Returns "E" (overridden)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Calling Methods from Specific Parent
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Calling Specific Parent Methods ---")
|
||||||
|
|
||||||
|
class Parent1:
|
||||||
|
def greet(self):
|
||||||
|
return "Hello from Parent1"
|
||||||
|
|
||||||
|
class Parent2:
|
||||||
|
def greet(self):
|
||||||
|
return "Hello from Parent2"
|
||||||
|
|
||||||
|
class Child(Parent1, Parent2):
|
||||||
|
def greet(self):
|
||||||
|
return "Hello from Child"
|
||||||
|
|
||||||
|
def greet_all(self):
|
||||||
|
"""Call all parent greet methods."""
|
||||||
|
return [
|
||||||
|
f"Child: {self.greet()}",
|
||||||
|
f"Parent1: {Parent1.greet(self)}",
|
||||||
|
f"Parent2: {Parent2.greet(self)}"
|
||||||
|
]
|
||||||
|
|
||||||
|
child = Child()
|
||||||
|
print("Calling all greet methods:")
|
||||||
|
for greeting in child.greet_all():
|
||||||
|
print(f" {greeting}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Abstract Base Classes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Define interfaces that must be implemented
|
||||||
|
|
||||||
|
print("\n--- Abstract Base Classes ---")
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class Shape(ABC):
|
||||||
|
"""Abstract base class for shapes."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def area(self):
|
||||||
|
"""Calculate area - must be implemented."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def perimeter(self):
|
||||||
|
"""Calculate perimeter - must be implemented."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
"""Non-abstract method - can be used as-is."""
|
||||||
|
return f"A shape with area {self.area():.2f}"
|
||||||
|
|
||||||
|
class Rectangle(Shape):
|
||||||
|
"""Concrete rectangle class."""
|
||||||
|
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
return self.width * self.height
|
||||||
|
|
||||||
|
def perimeter(self):
|
||||||
|
return 2 * (self.width + self.height)
|
||||||
|
|
||||||
|
class Circle(Shape):
|
||||||
|
"""Concrete circle class."""
|
||||||
|
|
||||||
|
def __init__(self, radius):
|
||||||
|
self.radius = radius
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
import math
|
||||||
|
return math.pi * self.radius ** 2
|
||||||
|
|
||||||
|
def perimeter(self):
|
||||||
|
import math
|
||||||
|
return 2 * math.pi * self.radius
|
||||||
|
|
||||||
|
# Cannot instantiate abstract class
|
||||||
|
# shape = Shape() # TypeError!
|
||||||
|
|
||||||
|
# Concrete classes work fine
|
||||||
|
rect = Rectangle(5, 3)
|
||||||
|
circle = Circle(4)
|
||||||
|
|
||||||
|
print(f"Rectangle: area={rect.area()}, perimeter={rect.perimeter()}")
|
||||||
|
print(f"Circle: area={circle.area():.2f}, perimeter={circle.perimeter():.2f}")
|
||||||
|
print(rect.describe())
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Using isinstance() and issubclass()
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Type Checking ---")
|
||||||
|
|
||||||
|
class Mammal(Animal):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Reptile(Animal):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DogV2(Mammal):
|
||||||
|
def speak(self):
|
||||||
|
return "Woof!"
|
||||||
|
|
||||||
|
class Snake(Reptile):
|
||||||
|
def speak(self):
|
||||||
|
return "Hiss!"
|
||||||
|
|
||||||
|
dog = DogV2("Rex")
|
||||||
|
snake = Snake("Slinky")
|
||||||
|
|
||||||
|
# isinstance() checks if object is instance of class
|
||||||
|
print("isinstance checks:")
|
||||||
|
print(f" dog isinstance of DogV2: {isinstance(dog, DogV2)}")
|
||||||
|
print(f" dog isinstance of Mammal: {isinstance(dog, Mammal)}")
|
||||||
|
print(f" dog isinstance of Animal: {isinstance(dog, Animal)}")
|
||||||
|
print(f" dog isinstance of Reptile: {isinstance(dog, Reptile)}")
|
||||||
|
|
||||||
|
# issubclass() checks class relationships
|
||||||
|
print("\nissubclass checks:")
|
||||||
|
print(f" DogV2 issubclass of Mammal: {issubclass(DogV2, Mammal)}")
|
||||||
|
print(f" DogV2 issubclass of Animal: {issubclass(DogV2, Animal)}")
|
||||||
|
print(f" DogV2 issubclass of Reptile: {issubclass(DogV2, Reptile)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Example: Employee Hierarchy
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example: Employee Hierarchy ---")
|
||||||
|
|
||||||
|
class Employee:
|
||||||
|
"""Base employee class."""
|
||||||
|
|
||||||
|
def __init__(self, name, employee_id, salary):
|
||||||
|
self.name = name
|
||||||
|
self.employee_id = employee_id
|
||||||
|
self._salary = salary
|
||||||
|
|
||||||
|
@property
|
||||||
|
def salary(self):
|
||||||
|
return self._salary
|
||||||
|
|
||||||
|
def get_annual_salary(self):
|
||||||
|
return self._salary * 12
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} (ID: {self.employee_id})"
|
||||||
|
|
||||||
|
class Manager(Employee):
|
||||||
|
"""Manager with team and bonus."""
|
||||||
|
|
||||||
|
def __init__(self, name, employee_id, salary, team_size=0):
|
||||||
|
super().__init__(name, employee_id, salary)
|
||||||
|
self.team_size = team_size
|
||||||
|
|
||||||
|
def get_annual_salary(self):
|
||||||
|
# Managers get bonus based on team size
|
||||||
|
base = super().get_annual_salary()
|
||||||
|
bonus = self.team_size * 1000 # $1000 per team member
|
||||||
|
return base + bonus
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Manager {super().__str__()} - Team: {self.team_size}"
|
||||||
|
|
||||||
|
class Developer(Employee):
|
||||||
|
"""Developer with tech stack."""
|
||||||
|
|
||||||
|
def __init__(self, name, employee_id, salary, languages):
|
||||||
|
super().__init__(name, employee_id, salary)
|
||||||
|
self.languages = languages
|
||||||
|
|
||||||
|
def get_annual_salary(self):
|
||||||
|
# Developers get extra for each language
|
||||||
|
base = super().get_annual_salary()
|
||||||
|
language_bonus = len(self.languages) * 500 # $500 per language
|
||||||
|
return base + language_bonus
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
langs = ", ".join(self.languages)
|
||||||
|
return f"Developer {super().__str__()} - Languages: {langs}"
|
||||||
|
|
||||||
|
# Create employees
|
||||||
|
manager = Manager("Alice", "M001", 8000, team_size=5)
|
||||||
|
dev1 = Developer("Bob", "D001", 6000, ["Python", "JavaScript", "SQL"])
|
||||||
|
dev2 = Developer("Charlie", "D002", 5500, ["Python"])
|
||||||
|
|
||||||
|
employees = [manager, dev1, dev2]
|
||||||
|
|
||||||
|
print("Employee Details:")
|
||||||
|
for emp in employees:
|
||||||
|
print(f" {emp}")
|
||||||
|
print(f" Annual Salary: ${emp.get_annual_salary():,}")
|
||||||
530
08_oop/04_polymorphism.py
Normal file
530
08_oop/04_polymorphism.py
Normal file
|
|
@ -0,0 +1,530 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 04_polymorphism.py
|
||||||
|
Topic: Polymorphism in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates polymorphism in Python - the ability of different
|
||||||
|
objects to respond to the same method call in different ways. This is a
|
||||||
|
core OOP principle that enables flexible and extensible code.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Duck typing
|
||||||
|
- Method polymorphism
|
||||||
|
- Operator overloading
|
||||||
|
- Protocols and ABC
|
||||||
|
- Practical polymorphism patterns
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. What is Polymorphism?
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# "Many forms" - same interface, different implementations
|
||||||
|
|
||||||
|
print("--- What is Polymorphism? ---")
|
||||||
|
|
||||||
|
class Dog:
|
||||||
|
def speak(self):
|
||||||
|
return "Woof!"
|
||||||
|
|
||||||
|
class Cat:
|
||||||
|
def speak(self):
|
||||||
|
return "Meow!"
|
||||||
|
|
||||||
|
class Duck:
|
||||||
|
def speak(self):
|
||||||
|
return "Quack!"
|
||||||
|
|
||||||
|
# Same method call, different behavior
|
||||||
|
animals = [Dog(), Cat(), Duck()]
|
||||||
|
|
||||||
|
print("Different animals, same method:")
|
||||||
|
for animal in animals:
|
||||||
|
print(f" {type(animal).__name__}: {animal.speak()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Duck Typing
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# "If it walks like a duck and quacks like a duck, it's a duck"
|
||||||
|
|
||||||
|
print("\n--- Duck Typing ---")
|
||||||
|
|
||||||
|
class RealDuck:
|
||||||
|
"""A real duck."""
|
||||||
|
def quack(self):
|
||||||
|
return "Quack quack!"
|
||||||
|
|
||||||
|
def fly(self):
|
||||||
|
return "Flap flap, flying!"
|
||||||
|
|
||||||
|
class RobotDuck:
|
||||||
|
"""A robot that acts like a duck."""
|
||||||
|
def quack(self):
|
||||||
|
return "Beep boop quack!"
|
||||||
|
|
||||||
|
def fly(self):
|
||||||
|
return "Propellers spinning, ascending!"
|
||||||
|
|
||||||
|
class Person:
|
||||||
|
"""A person pretending to be a duck."""
|
||||||
|
def quack(self):
|
||||||
|
return "*Person making quack sounds*"
|
||||||
|
|
||||||
|
def fly(self):
|
||||||
|
return "*Person flapping arms*"
|
||||||
|
|
||||||
|
def duck_demo(duck):
|
||||||
|
"""Demonstrate a duck (or anything duck-like)."""
|
||||||
|
print(f" {type(duck).__name__}:")
|
||||||
|
print(f" Quacking: {duck.quack()}")
|
||||||
|
print(f" Flying: {duck.fly()}")
|
||||||
|
|
||||||
|
# All these work because they have the required methods
|
||||||
|
print("Duck typing demo:")
|
||||||
|
duck_demo(RealDuck())
|
||||||
|
duck_demo(RobotDuck())
|
||||||
|
duck_demo(Person())
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Method Polymorphism with Inheritance
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Method Polymorphism ---")
|
||||||
|
|
||||||
|
class Shape:
|
||||||
|
"""Base shape class."""
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
raise NotImplementedError("Subclass must implement area()")
|
||||||
|
|
||||||
|
def describe(self):
|
||||||
|
return f"{self.__class__.__name__} with area {self.area():.2f}"
|
||||||
|
|
||||||
|
class Rectangle(Shape):
|
||||||
|
def __init__(self, width, height):
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
return self.width * self.height
|
||||||
|
|
||||||
|
class Circle(Shape):
|
||||||
|
def __init__(self, radius):
|
||||||
|
self.radius = radius
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
import math
|
||||||
|
return math.pi * self.radius ** 2
|
||||||
|
|
||||||
|
class Triangle(Shape):
|
||||||
|
def __init__(self, base, height):
|
||||||
|
self.base = base
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
def area(self):
|
||||||
|
return 0.5 * self.base * self.height
|
||||||
|
|
||||||
|
# Polymorphism in action
|
||||||
|
shapes = [
|
||||||
|
Rectangle(5, 3),
|
||||||
|
Circle(4),
|
||||||
|
Triangle(6, 4)
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Shape descriptions:")
|
||||||
|
for shape in shapes:
|
||||||
|
print(f" {shape.describe()}")
|
||||||
|
|
||||||
|
# Calculate total area polymorphically
|
||||||
|
total_area = sum(shape.area() for shape in shapes)
|
||||||
|
print(f"\nTotal area of all shapes: {total_area:.2f}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Operator Overloading
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Same operators, different behaviors
|
||||||
|
|
||||||
|
print("\n--- Operator Overloading ---")
|
||||||
|
|
||||||
|
class Vector:
|
||||||
|
"""A 2D vector with overloaded operators."""
|
||||||
|
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Vector({self.x}, {self.y})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
"""Vector addition: v1 + v2"""
|
||||||
|
return Vector(self.x + other.x, self.y + other.y)
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
"""Vector subtraction: v1 - v2"""
|
||||||
|
return Vector(self.x - other.x, self.y - other.y)
|
||||||
|
|
||||||
|
def __mul__(self, scalar):
|
||||||
|
"""Scalar multiplication: v * n"""
|
||||||
|
return Vector(self.x * scalar, self.y * scalar)
|
||||||
|
|
||||||
|
def __rmul__(self, scalar):
|
||||||
|
"""Reverse multiplication: n * v"""
|
||||||
|
return self.__mul__(scalar)
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
"""Negation: -v"""
|
||||||
|
return Vector(-self.x, -self.y)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
"""Equality: v1 == v2"""
|
||||||
|
return self.x == other.x and self.y == other.y
|
||||||
|
|
||||||
|
def __abs__(self):
|
||||||
|
"""Magnitude: abs(v)"""
|
||||||
|
return (self.x ** 2 + self.y ** 2) ** 0.5
|
||||||
|
|
||||||
|
v1 = Vector(3, 4)
|
||||||
|
v2 = Vector(1, 2)
|
||||||
|
|
||||||
|
print(f"v1 = {v1}")
|
||||||
|
print(f"v2 = {v2}")
|
||||||
|
print(f"v1 + v2 = {v1 + v2}")
|
||||||
|
print(f"v1 - v2 = {v1 - v2}")
|
||||||
|
print(f"v1 * 3 = {v1 * 3}")
|
||||||
|
print(f"2 * v2 = {2 * v2}")
|
||||||
|
print(f"-v1 = {-v1}")
|
||||||
|
print(f"|v1| = {abs(v1)}")
|
||||||
|
print(f"v1 == Vector(3, 4): {v1 == Vector(3, 4)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Polymorphism with Built-in Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Built-in Function Polymorphism ---")
|
||||||
|
|
||||||
|
class Playlist:
|
||||||
|
"""A playlist that works with len() and iteration."""
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.songs = []
|
||||||
|
|
||||||
|
def add_song(self, song):
|
||||||
|
self.songs.append(song)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""Enable len(playlist)"""
|
||||||
|
return len(self.songs)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""Enable for song in playlist"""
|
||||||
|
return iter(self.songs)
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
"""Enable playlist[index]"""
|
||||||
|
return self.songs[index]
|
||||||
|
|
||||||
|
def __contains__(self, song):
|
||||||
|
"""Enable 'song' in playlist"""
|
||||||
|
return song in self.songs
|
||||||
|
|
||||||
|
playlist = Playlist("My Favorites")
|
||||||
|
playlist.add_song("Song A")
|
||||||
|
playlist.add_song("Song B")
|
||||||
|
playlist.add_song("Song C")
|
||||||
|
|
||||||
|
print(f"Playlist '{playlist.name}':")
|
||||||
|
print(f" Length: {len(playlist)}")
|
||||||
|
print(f" First song: {playlist[0]}")
|
||||||
|
print(f" 'Song B' in playlist: {'Song B' in playlist}")
|
||||||
|
print(" All songs:")
|
||||||
|
for song in playlist:
|
||||||
|
print(f" - {song}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Polymorphic Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Polymorphic Functions ---")
|
||||||
|
|
||||||
|
def process_payment(payment_method):
|
||||||
|
"""
|
||||||
|
Process any payment method polymorphically.
|
||||||
|
Any object with a process() method works!
|
||||||
|
"""
|
||||||
|
print(f" Processing with {type(payment_method).__name__}...")
|
||||||
|
return payment_method.process()
|
||||||
|
|
||||||
|
class CreditCard:
|
||||||
|
def __init__(self, card_number):
|
||||||
|
self.card_number = card_number[-4:] # Last 4 digits
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
return f"Charged to card ending in {self.card_number}"
|
||||||
|
|
||||||
|
class PayPal:
|
||||||
|
def __init__(self, email):
|
||||||
|
self.email = email
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
return f"Payment sent via PayPal ({self.email})"
|
||||||
|
|
||||||
|
class CryptoCurrency:
|
||||||
|
def __init__(self, wallet):
|
||||||
|
self.wallet = wallet[:8]
|
||||||
|
|
||||||
|
def process(self):
|
||||||
|
return f"Crypto transferred from {self.wallet}..."
|
||||||
|
|
||||||
|
# Same function, different payment methods
|
||||||
|
payment_methods = [
|
||||||
|
CreditCard("4111111111111234"),
|
||||||
|
PayPal("user@example.com"),
|
||||||
|
CryptoCurrency("0x1234567890abcdef")
|
||||||
|
]
|
||||||
|
|
||||||
|
print("Processing payments:")
|
||||||
|
for method in payment_methods:
|
||||||
|
result = process_payment(method)
|
||||||
|
print(f" Result: {result}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Protocols (Informal Interfaces)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Protocols (Informal Interfaces) ---")
|
||||||
|
|
||||||
|
# Python 3.8+ has typing.Protocol for formal protocols
|
||||||
|
# Here's the concept with duck typing:
|
||||||
|
|
||||||
|
class Drawable:
|
||||||
|
"""Protocol: anything with a draw() method."""
|
||||||
|
def draw(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class Circle2D:
|
||||||
|
def draw(self):
|
||||||
|
return "Drawing a circle: O"
|
||||||
|
|
||||||
|
class Square2D:
|
||||||
|
def draw(self):
|
||||||
|
return "Drawing a square: □"
|
||||||
|
|
||||||
|
class Triangle2D:
|
||||||
|
def draw(self):
|
||||||
|
return "Drawing a triangle: △"
|
||||||
|
|
||||||
|
class Text2D:
|
||||||
|
def __init__(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
return f"Drawing text: '{self.text}'"
|
||||||
|
|
||||||
|
def render_canvas(drawables):
|
||||||
|
"""Render anything that has a draw() method."""
|
||||||
|
print("Canvas:")
|
||||||
|
for drawable in drawables:
|
||||||
|
print(f" {drawable.draw()}")
|
||||||
|
|
||||||
|
# All these can be rendered
|
||||||
|
elements = [Circle2D(), Square2D(), Triangle2D(), Text2D("Hello")]
|
||||||
|
render_canvas(elements)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Polymorphism with Abstract Base Classes
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- ABC Polymorphism ---")
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
class DataExporter(ABC):
|
||||||
|
"""Abstract base class for data exporters."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def export(self, data):
|
||||||
|
"""Export data - must be implemented."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
"""Common validation (can be overridden)."""
|
||||||
|
if not data:
|
||||||
|
raise ValueError("No data to export")
|
||||||
|
return True
|
||||||
|
|
||||||
|
class JSONExporter(DataExporter):
|
||||||
|
def export(self, data):
|
||||||
|
import json
|
||||||
|
self.validate(data)
|
||||||
|
return json.dumps(data, indent=2)
|
||||||
|
|
||||||
|
class CSVExporter(DataExporter):
|
||||||
|
def export(self, data):
|
||||||
|
self.validate(data)
|
||||||
|
if not data:
|
||||||
|
return ""
|
||||||
|
headers = ",".join(data[0].keys())
|
||||||
|
rows = [",".join(str(v) for v in row.values()) for row in data]
|
||||||
|
return headers + "\n" + "\n".join(rows)
|
||||||
|
|
||||||
|
class XMLExporter(DataExporter):
|
||||||
|
def export(self, data):
|
||||||
|
self.validate(data)
|
||||||
|
xml = "<root>\n"
|
||||||
|
for item in data:
|
||||||
|
xml += " <item>\n"
|
||||||
|
for key, value in item.items():
|
||||||
|
xml += f" <{key}>{value}</{key}>\n"
|
||||||
|
xml += " </item>\n"
|
||||||
|
xml += "</root>"
|
||||||
|
return xml
|
||||||
|
|
||||||
|
# Same data, different formats
|
||||||
|
sample_data = [
|
||||||
|
{"name": "Alice", "age": 25},
|
||||||
|
{"name": "Bob", "age": 30}
|
||||||
|
]
|
||||||
|
|
||||||
|
exporters = [JSONExporter(), CSVExporter(), XMLExporter()]
|
||||||
|
|
||||||
|
print("Exporting same data in different formats:")
|
||||||
|
for exporter in exporters:
|
||||||
|
print(f"\n{type(exporter).__name__}:")
|
||||||
|
print(exporter.export(sample_data))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Method Dispatch Based on Type
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Type-Based Method Dispatch ---")
|
||||||
|
|
||||||
|
from functools import singledispatch
|
||||||
|
|
||||||
|
@singledispatch
|
||||||
|
def process(value):
|
||||||
|
"""Default processing for unknown types."""
|
||||||
|
return f"Don't know how to process {type(value).__name__}"
|
||||||
|
|
||||||
|
@process.register(int)
|
||||||
|
def _(value):
|
||||||
|
"""Process integers."""
|
||||||
|
return f"Integer: {value * 2}"
|
||||||
|
|
||||||
|
@process.register(str)
|
||||||
|
def _(value):
|
||||||
|
"""Process strings."""
|
||||||
|
return f"String: {value.upper()}"
|
||||||
|
|
||||||
|
@process.register(list)
|
||||||
|
def _(value):
|
||||||
|
"""Process lists."""
|
||||||
|
return f"List with {len(value)} items"
|
||||||
|
|
||||||
|
# Same function name, different behavior based on type
|
||||||
|
print("Single dispatch polymorphism:")
|
||||||
|
print(f" process(42): {process(42)}")
|
||||||
|
print(f" process('hello'): {process('hello')}")
|
||||||
|
print(f" process([1,2,3]): {process([1,2,3])}")
|
||||||
|
print(f" process(3.14): {process(3.14)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Example: Notification System
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Example: Notification System ---")
|
||||||
|
|
||||||
|
class NotificationService(ABC):
|
||||||
|
"""Abstract base for notification services."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def send(self, recipient, message):
|
||||||
|
"""Send a notification."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_status(self):
|
||||||
|
"""Get service status."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class EmailNotification(NotificationService):
|
||||||
|
def __init__(self, smtp_server="mail.example.com"):
|
||||||
|
self.smtp_server = smtp_server
|
||||||
|
|
||||||
|
def send(self, recipient, message):
|
||||||
|
return f"📧 Email sent to {recipient}: '{message}'"
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return f"Email service connected to {self.smtp_server}"
|
||||||
|
|
||||||
|
class SMSNotification(NotificationService):
|
||||||
|
def __init__(self, provider="TwilioMock"):
|
||||||
|
self.provider = provider
|
||||||
|
|
||||||
|
def send(self, recipient, message):
|
||||||
|
return f"📱 SMS sent to {recipient}: '{message[:50]}...'"
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return f"SMS service using {self.provider}"
|
||||||
|
|
||||||
|
class PushNotification(NotificationService):
|
||||||
|
def __init__(self, app_name="MyApp"):
|
||||||
|
self.app_name = app_name
|
||||||
|
|
||||||
|
def send(self, recipient, message):
|
||||||
|
return f"🔔 Push notification to {recipient}: '{message}'"
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return f"Push service for {self.app_name}"
|
||||||
|
|
||||||
|
class SlackNotification(NotificationService):
|
||||||
|
def __init__(self, workspace="MyWorkspace"):
|
||||||
|
self.workspace = workspace
|
||||||
|
|
||||||
|
def send(self, recipient, message):
|
||||||
|
return f"💬 Slack message to #{recipient}: '{message}'"
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
return f"Slack connected to {self.workspace}"
|
||||||
|
|
||||||
|
# Polymorphic notification manager
|
||||||
|
class NotificationManager:
|
||||||
|
"""Manages multiple notification services."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.services = []
|
||||||
|
|
||||||
|
def add_service(self, service):
|
||||||
|
self.services.append(service)
|
||||||
|
|
||||||
|
def send_all(self, recipient, message):
|
||||||
|
"""Send via all services."""
|
||||||
|
results = []
|
||||||
|
for service in self.services:
|
||||||
|
results.append(service.send(recipient, message))
|
||||||
|
return results
|
||||||
|
|
||||||
|
def status(self):
|
||||||
|
"""Get status of all services."""
|
||||||
|
return [service.get_status() for service in self.services]
|
||||||
|
|
||||||
|
# Create manager and add services
|
||||||
|
manager = NotificationManager()
|
||||||
|
manager.add_service(EmailNotification())
|
||||||
|
manager.add_service(SMSNotification())
|
||||||
|
manager.add_service(PushNotification())
|
||||||
|
manager.add_service(SlackNotification())
|
||||||
|
|
||||||
|
print("Service status:")
|
||||||
|
for status in manager.status():
|
||||||
|
print(f" ✓ {status}")
|
||||||
|
|
||||||
|
print("\nSending notification via all channels:")
|
||||||
|
for result in manager.send_all("user123", "Your order has been shipped!"):
|
||||||
|
print(f" {result}")
|
||||||
304
09_advanced_python/01_list_comprehensions.py
Normal file
304
09_advanced_python/01_list_comprehensions.py
Normal file
|
|
@ -0,0 +1,304 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_list_comprehensions.py
|
||||||
|
Topic: List Comprehensions and Other Comprehensions
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates comprehensions in Python - a concise and powerful way
|
||||||
|
to create lists, dictionaries, sets, and generators. Comprehensions are more
|
||||||
|
readable and often faster than traditional loops.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- List comprehensions
|
||||||
|
- Dictionary comprehensions
|
||||||
|
- Set comprehensions
|
||||||
|
- Generator expressions
|
||||||
|
- Nested comprehensions
|
||||||
|
- When to use comprehensions
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Basic List Comprehension
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# [expression for item in iterable]
|
||||||
|
|
||||||
|
print("--- Basic List Comprehension ---")
|
||||||
|
|
||||||
|
# Traditional way
|
||||||
|
squares_loop = []
|
||||||
|
for x in range(1, 6):
|
||||||
|
squares_loop.append(x ** 2)
|
||||||
|
print(f"Loop method: {squares_loop}")
|
||||||
|
|
||||||
|
# List comprehension way
|
||||||
|
squares_comp = [x ** 2 for x in range(1, 6)]
|
||||||
|
print(f"Comprehension: {squares_comp}")
|
||||||
|
|
||||||
|
# More examples
|
||||||
|
numbers = [1, 2, 3, 4, 5]
|
||||||
|
doubled = [n * 2 for n in numbers]
|
||||||
|
strings = [str(n) for n in numbers]
|
||||||
|
|
||||||
|
print(f"\nOriginal: {numbers}")
|
||||||
|
print(f"Doubled: {doubled}")
|
||||||
|
print(f"As strings: {strings}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. List Comprehension with Condition
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# [expression for item in iterable if condition]
|
||||||
|
|
||||||
|
print("\n--- Comprehension with Condition ---")
|
||||||
|
|
||||||
|
# Get only even numbers
|
||||||
|
numbers = range(1, 21)
|
||||||
|
evens = [n for n in numbers if n % 2 == 0]
|
||||||
|
print(f"Even numbers: {evens}")
|
||||||
|
|
||||||
|
# Filter and transform
|
||||||
|
words = ["hello", "world", "python", "AI", "ml"]
|
||||||
|
long_upper = [w.upper() for w in words if len(w) > 3]
|
||||||
|
print(f"Long words (uppercase): {long_upper}")
|
||||||
|
|
||||||
|
# Multiple conditions (AND)
|
||||||
|
numbers = range(1, 51)
|
||||||
|
divisible_by_3_and_5 = [n for n in numbers if n % 3 == 0 and n % 5 == 0]
|
||||||
|
print(f"Divisible by 3 and 5: {divisible_by_3_and_5}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Conditional Expression in Comprehension
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# [expr1 if condition else expr2 for item in iterable]
|
||||||
|
|
||||||
|
print("\n--- Conditional Expression ---")
|
||||||
|
|
||||||
|
# Replace negatives with zero
|
||||||
|
numbers = [-3, -1, 0, 2, 5, -4, 8]
|
||||||
|
non_negative = [n if n >= 0 else 0 for n in numbers]
|
||||||
|
print(f"Original: {numbers}")
|
||||||
|
print(f"Non-negative: {non_negative}")
|
||||||
|
|
||||||
|
# Categorize numbers
|
||||||
|
categorized = ["even" if n % 2 == 0 else "odd" for n in range(1, 6)]
|
||||||
|
print(f"Categories: {categorized}")
|
||||||
|
|
||||||
|
# Pass/fail based on score
|
||||||
|
scores = [85, 42, 91, 78, 55]
|
||||||
|
results = ["Pass" if s >= 60 else "Fail" for s in scores]
|
||||||
|
print(f"Scores: {scores}")
|
||||||
|
print(f"Results: {results}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Nested Loops in Comprehension
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Nested Loops ---")
|
||||||
|
|
||||||
|
# Flatten a 2D list
|
||||||
|
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
|
||||||
|
flat = [num for row in matrix for num in row]
|
||||||
|
print(f"Matrix: {matrix}")
|
||||||
|
print(f"Flattened: {flat}")
|
||||||
|
|
||||||
|
# All combinations
|
||||||
|
colors = ["red", "green"]
|
||||||
|
sizes = ["S", "M", "L"]
|
||||||
|
combinations = [(color, size) for color in colors for size in sizes]
|
||||||
|
print(f"\nCombinations: {combinations}")
|
||||||
|
|
||||||
|
# Multiplication table
|
||||||
|
table = [[i * j for j in range(1, 4)] for i in range(1, 4)]
|
||||||
|
print("\nMultiplication table:")
|
||||||
|
for row in table:
|
||||||
|
print(f" {row}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Dictionary Comprehensions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# {key: value for item in iterable}
|
||||||
|
|
||||||
|
print("\n--- Dictionary Comprehensions ---")
|
||||||
|
|
||||||
|
# Square numbers dictionary
|
||||||
|
squares_dict = {x: x ** 2 for x in range(1, 6)}
|
||||||
|
print(f"Squares dict: {squares_dict}")
|
||||||
|
|
||||||
|
# From two lists
|
||||||
|
keys = ["name", "age", "city"]
|
||||||
|
values = ["Alice", 25, "NYC"]
|
||||||
|
person = {k: v for k, v in zip(keys, values)}
|
||||||
|
print(f"Person dict: {person}")
|
||||||
|
|
||||||
|
# Swap keys and values
|
||||||
|
original = {"a": 1, "b": 2, "c": 3}
|
||||||
|
swapped = {v: k for k, v in original.items()}
|
||||||
|
print(f"Original: {original}")
|
||||||
|
print(f"Swapped: {swapped}")
|
||||||
|
|
||||||
|
# Filter dictionary
|
||||||
|
scores = {"Alice": 85, "Bob": 42, "Charlie": 91, "Dave": 55}
|
||||||
|
passing = {name: score for name, score in scores.items() if score >= 60}
|
||||||
|
print(f"\nPasssing students: {passing}")
|
||||||
|
|
||||||
|
# Transform dictionary
|
||||||
|
prices = {"apple": 1.5, "banana": 0.75, "cherry": 2.0}
|
||||||
|
taxed = {item: price * 1.1 for item, price in prices.items()}
|
||||||
|
print(f"With tax: {taxed}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Set Comprehensions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# {expression for item in iterable}
|
||||||
|
|
||||||
|
print("\n--- Set Comprehensions ---")
|
||||||
|
|
||||||
|
# Unique squares
|
||||||
|
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
|
||||||
|
unique_squares = {x ** 2 for x in numbers}
|
||||||
|
print(f"Numbers: {numbers}")
|
||||||
|
print(f"Unique squares: {unique_squares}")
|
||||||
|
|
||||||
|
# Unique first letters
|
||||||
|
words = ["apple", "banana", "apricot", "blueberry", "cherry"]
|
||||||
|
first_letters = {w[0] for w in words}
|
||||||
|
print(f"First letters: {first_letters}")
|
||||||
|
|
||||||
|
# Get unique word lengths
|
||||||
|
sentences = "the quick brown fox jumps over the lazy dog"
|
||||||
|
word_lengths = {len(word) for word in sentences.split()}
|
||||||
|
print(f"Unique word lengths: {word_lengths}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Generator Expressions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# (expression for item in iterable) - lazy evaluation!
|
||||||
|
|
||||||
|
print("\n--- Generator Expressions ---")
|
||||||
|
|
||||||
|
# Generator expression (note the parentheses)
|
||||||
|
squares_gen = (x ** 2 for x in range(1, 6))
|
||||||
|
print(f"Generator object: {squares_gen}")
|
||||||
|
print(f"As list: {list(squares_gen)}")
|
||||||
|
|
||||||
|
# Memory efficient for large data
|
||||||
|
# List: creates all values immediately
|
||||||
|
# Generator: creates values on demand
|
||||||
|
|
||||||
|
# Sum of squares (generator is more memory efficient)
|
||||||
|
total = sum(x ** 2 for x in range(1, 1001))
|
||||||
|
print(f"Sum of squares 1-1000: {total}")
|
||||||
|
|
||||||
|
# Check if any/all
|
||||||
|
numbers = [1, 3, 5, 7, 9]
|
||||||
|
any_even = any(n % 2 == 0 for n in numbers)
|
||||||
|
all_positive = all(n > 0 for n in numbers)
|
||||||
|
print(f"\nNumbers: {numbers}")
|
||||||
|
print(f"Any even? {any_even}")
|
||||||
|
print(f"All positive? {all_positive}")
|
||||||
|
|
||||||
|
# Find first match
|
||||||
|
names = ["Alice", "Bob", "Charlie", "Diana"]
|
||||||
|
first_long = next((name for name in names if len(name) > 5), None)
|
||||||
|
print(f"First name > 5 chars: {first_long}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Nested Comprehensions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Nested Comprehensions ---")
|
||||||
|
|
||||||
|
# Create a matrix
|
||||||
|
rows, cols = 3, 4
|
||||||
|
matrix = [[0 for _ in range(cols)] for _ in range(rows)]
|
||||||
|
print(f"Zero matrix {rows}x{cols}: {matrix}")
|
||||||
|
|
||||||
|
# Identity matrix
|
||||||
|
identity = [[1 if i == j else 0 for j in range(3)] for i in range(3)]
|
||||||
|
print("\nIdentity matrix:")
|
||||||
|
for row in identity:
|
||||||
|
print(f" {row}")
|
||||||
|
|
||||||
|
# Transpose matrix
|
||||||
|
original = [[1, 2, 3], [4, 5, 6]]
|
||||||
|
transposed = [[row[i] for row in original] for i in range(len(original[0]))]
|
||||||
|
print(f"\nOriginal: {original}")
|
||||||
|
print(f"Transposed: {transposed}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. When NOT to Use Comprehensions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- When NOT to Use Comprehensions ---")
|
||||||
|
|
||||||
|
# 1. Complex logic - use regular loop
|
||||||
|
# BAD:
|
||||||
|
# result = [func1(x) if x > 0 else func2(x) if x < 0 else func3(x) for x in data if valid(x)]
|
||||||
|
|
||||||
|
# GOOD: Use regular loop for complex logic
|
||||||
|
def process_numbers(data):
|
||||||
|
result = []
|
||||||
|
for x in data:
|
||||||
|
if not x: # Skip None or 0
|
||||||
|
continue
|
||||||
|
if x > 0:
|
||||||
|
result.append(x ** 2)
|
||||||
|
else:
|
||||||
|
result.append(abs(x))
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 2. Side effects - use regular loop
|
||||||
|
# BAD: [print(x) for x in items] # Creates unnecessary list
|
||||||
|
|
||||||
|
# GOOD:
|
||||||
|
# for x in items:
|
||||||
|
# print(x)
|
||||||
|
|
||||||
|
# 3. Very long comprehensions that require wrapping
|
||||||
|
# Consider regular loop for readability
|
||||||
|
|
||||||
|
print("Use regular loops when:")
|
||||||
|
print(" - Logic is complex")
|
||||||
|
print(" - There are side effects (print, modify, etc.)")
|
||||||
|
print(" - Comprehension becomes too long to read")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# 1. Parse CSV-like data
|
||||||
|
csv_data = "name,age,city\nAlice,25,NYC\nBob,30,LA\nCharlie,35,Chicago"
|
||||||
|
rows = [line.split(",") for line in csv_data.split("\n")]
|
||||||
|
print(f"Parsed CSV: {rows}")
|
||||||
|
|
||||||
|
# 2. Filter and transform objects
|
||||||
|
users = [
|
||||||
|
{"name": "Alice", "age": 25, "active": True},
|
||||||
|
{"name": "Bob", "age": 17, "active": True},
|
||||||
|
{"name": "Charlie", "age": 30, "active": False},
|
||||||
|
{"name": "Diana", "age": 22, "active": True}
|
||||||
|
]
|
||||||
|
active_adults = [
|
||||||
|
user["name"]
|
||||||
|
for user in users
|
||||||
|
if user["active"] and user["age"] >= 18
|
||||||
|
]
|
||||||
|
print(f"\nActive adults: {active_adults}")
|
||||||
|
|
||||||
|
# 3. Word frequency (dict comprehension)
|
||||||
|
text = "the quick brown fox jumps over the lazy dog"
|
||||||
|
words = text.split()
|
||||||
|
frequency = {word: words.count(word) for word in set(words)}
|
||||||
|
print(f"\nWord frequency: {frequency}")
|
||||||
|
|
||||||
|
# 4. File path manipulation
|
||||||
|
files = ["report.pdf", "data.csv", "image.png", "document.pdf", "log.txt"]
|
||||||
|
pdf_files = [f for f in files if f.endswith(".pdf")]
|
||||||
|
print(f"\nPDF files: {pdf_files}")
|
||||||
|
|
||||||
|
# 5. Coordinate pairs
|
||||||
|
coords = [(x, y) for x in range(3) for y in range(3) if x != y]
|
||||||
|
print(f"\nCoordinate pairs (x != y): {coords}")
|
||||||
363
09_advanced_python/02_generators.py
Normal file
363
09_advanced_python/02_generators.py
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_generators.py
|
||||||
|
Topic: Generators and Iterators in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates generators and iterators in Python. Generators are
|
||||||
|
special functions that can pause and resume execution, yielding values one
|
||||||
|
at a time. They're memory-efficient for processing large data.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Iterator protocol (__iter__, __next__)
|
||||||
|
- Generator functions (yield)
|
||||||
|
- Generator expressions
|
||||||
|
- yield from
|
||||||
|
- Practical applications
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Understanding Iterators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Iterators implement __iter__ and __next__
|
||||||
|
|
||||||
|
print("--- Understanding Iterators ---")
|
||||||
|
|
||||||
|
# Lists are iterable (have __iter__)
|
||||||
|
numbers = [1, 2, 3]
|
||||||
|
iterator = iter(numbers) # Get iterator
|
||||||
|
|
||||||
|
print(f"First: {next(iterator)}")
|
||||||
|
print(f"Second: {next(iterator)}")
|
||||||
|
print(f"Third: {next(iterator)}")
|
||||||
|
# next(iterator) # Would raise StopIteration
|
||||||
|
|
||||||
|
# Custom iterator class
|
||||||
|
class CountDown:
|
||||||
|
"""A countdown iterator."""
|
||||||
|
|
||||||
|
def __init__(self, start):
|
||||||
|
self.current = start
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
if self.current <= 0:
|
||||||
|
raise StopIteration
|
||||||
|
value = self.current
|
||||||
|
self.current -= 1
|
||||||
|
return value
|
||||||
|
|
||||||
|
print("\nCountdown iterator:")
|
||||||
|
for num in CountDown(5):
|
||||||
|
print(f" {num}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Generator Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use yield to create generators
|
||||||
|
|
||||||
|
print("\n--- Generator Functions ---")
|
||||||
|
|
||||||
|
def countdown(n):
|
||||||
|
"""Generator function for countdown."""
|
||||||
|
while n > 0:
|
||||||
|
yield n # Pause here and return value
|
||||||
|
n -= 1 # Resume from here on next()
|
||||||
|
|
||||||
|
# Using the generator
|
||||||
|
print("Generator countdown:")
|
||||||
|
for num in countdown(5):
|
||||||
|
print(f" {num}")
|
||||||
|
|
||||||
|
# Generator is an iterator
|
||||||
|
gen = countdown(3)
|
||||||
|
print(f"\nGenerator object: {gen}")
|
||||||
|
print(f"next(): {next(gen)}")
|
||||||
|
print(f"next(): {next(gen)}")
|
||||||
|
print(f"next(): {next(gen)}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Generators vs Lists - Memory Efficiency
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Memory Efficiency ---")
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# List - all values in memory at once
|
||||||
|
list_nums = [x ** 2 for x in range(1000)]
|
||||||
|
list_size = sys.getsizeof(list_nums)
|
||||||
|
|
||||||
|
# Generator - values created on demand
|
||||||
|
gen_nums = (x ** 2 for x in range(1000))
|
||||||
|
gen_size = sys.getsizeof(gen_nums)
|
||||||
|
|
||||||
|
print(f"List of 1000 squares: {list_size:,} bytes")
|
||||||
|
print(f"Generator for 1000 squares: {gen_size} bytes")
|
||||||
|
print(f"Memory saved: {list_size - gen_size:,} bytes")
|
||||||
|
|
||||||
|
# For very large sequences, generators are essential
|
||||||
|
# This would crash with a list: sum(range(10**12))
|
||||||
|
# But works with generator: sum(range(10**6)) # Uses constant memory
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Infinite Generators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Infinite Generators ---")
|
||||||
|
|
||||||
|
def infinite_counter(start=0):
|
||||||
|
"""Generate numbers infinitely."""
|
||||||
|
n = start
|
||||||
|
while True:
|
||||||
|
yield n
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
def fibonacci():
|
||||||
|
"""Generate Fibonacci numbers infinitely."""
|
||||||
|
a, b = 0, 1
|
||||||
|
while True:
|
||||||
|
yield a
|
||||||
|
a, b = b, a + b
|
||||||
|
|
||||||
|
# Use islice to take limited values from infinite generator
|
||||||
|
from itertools import islice
|
||||||
|
|
||||||
|
print("First 10 counter values:")
|
||||||
|
counter = infinite_counter(10)
|
||||||
|
first_10 = list(islice(counter, 10))
|
||||||
|
print(f" {first_10}")
|
||||||
|
|
||||||
|
print("\nFirst 10 Fibonacci numbers:")
|
||||||
|
fib = fibonacci()
|
||||||
|
fib_10 = list(islice(fib, 10))
|
||||||
|
print(f" {fib_10}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Generator Methods
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Generator Methods ---")
|
||||||
|
|
||||||
|
def interactive_generator():
|
||||||
|
"""Generator that can receive values."""
|
||||||
|
print(" Generator started")
|
||||||
|
|
||||||
|
# yield can receive values via send()
|
||||||
|
received = yield "First yield"
|
||||||
|
print(f" Received: {received}")
|
||||||
|
|
||||||
|
received = yield "Second yield"
|
||||||
|
print(f" Received: {received}")
|
||||||
|
|
||||||
|
yield "Final yield"
|
||||||
|
|
||||||
|
gen = interactive_generator()
|
||||||
|
print(f"next(): {next(gen)}") # Start and get first yield
|
||||||
|
print(f"send('Hello'): {gen.send('Hello')}") # Send value, get second yield
|
||||||
|
print(f"send('World'): {gen.send('World')}") # Send value, get final yield
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. yield from - Delegating Generators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- yield from ---")
|
||||||
|
|
||||||
|
def sub_generator():
|
||||||
|
"""A simple sub-generator."""
|
||||||
|
yield 1
|
||||||
|
yield 2
|
||||||
|
yield 3
|
||||||
|
|
||||||
|
# Without yield from
|
||||||
|
def main_generator_old():
|
||||||
|
for item in sub_generator():
|
||||||
|
yield item
|
||||||
|
yield 4
|
||||||
|
yield 5
|
||||||
|
|
||||||
|
# With yield from (cleaner)
|
||||||
|
def main_generator():
|
||||||
|
yield from sub_generator()
|
||||||
|
yield 4
|
||||||
|
yield 5
|
||||||
|
|
||||||
|
print("Using yield from:")
|
||||||
|
result = list(main_generator())
|
||||||
|
print(f" {result}")
|
||||||
|
|
||||||
|
# Flatten nested structure with yield from
|
||||||
|
def flatten(nested):
|
||||||
|
"""Flatten arbitrarily nested lists."""
|
||||||
|
for item in nested:
|
||||||
|
if isinstance(item, list):
|
||||||
|
yield from flatten(item)
|
||||||
|
else:
|
||||||
|
yield item
|
||||||
|
|
||||||
|
nested = [1, [2, 3, [4, 5]], 6, [7, [8, 9]]]
|
||||||
|
print(f"\nFlattening: {nested}")
|
||||||
|
print(f"Result: {list(flatten(nested))}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Generator Pipelines
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Generator Pipelines ---")
|
||||||
|
|
||||||
|
# Process data through multiple generators (like Unix pipes)
|
||||||
|
|
||||||
|
def read_data():
|
||||||
|
"""Simulate reading data."""
|
||||||
|
data = [" John,25,NYC ", " Jane,30,LA ", " Bob,35,Chicago ", ""]
|
||||||
|
for line in data:
|
||||||
|
yield line
|
||||||
|
|
||||||
|
def strip_lines(lines):
|
||||||
|
"""Remove whitespace from lines."""
|
||||||
|
for line in lines:
|
||||||
|
yield line.strip()
|
||||||
|
|
||||||
|
def filter_empty(lines):
|
||||||
|
"""Filter out empty lines."""
|
||||||
|
for line in lines:
|
||||||
|
if line:
|
||||||
|
yield line
|
||||||
|
|
||||||
|
def parse_csv(lines):
|
||||||
|
"""Parse CSV lines into dictionaries."""
|
||||||
|
for line in lines:
|
||||||
|
name, age, city = line.split(",")
|
||||||
|
yield {"name": name, "age": int(age), "city": city}
|
||||||
|
|
||||||
|
# Chain generators together
|
||||||
|
pipeline = parse_csv(filter_empty(strip_lines(read_data())))
|
||||||
|
|
||||||
|
print("Pipeline processing:")
|
||||||
|
for record in pipeline:
|
||||||
|
print(f" {record}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Context Manager with Generator
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Generator as Context Manager ---")
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def managed_resource(name):
|
||||||
|
"""A context manager using generator."""
|
||||||
|
print(f" Acquiring: {name}")
|
||||||
|
try:
|
||||||
|
yield name # Resource is available here
|
||||||
|
finally:
|
||||||
|
print(f" Releasing: {name}")
|
||||||
|
|
||||||
|
# Using the context manager
|
||||||
|
print("Using managed resource:")
|
||||||
|
with managed_resource("Database Connection") as conn:
|
||||||
|
print(f" Using: {conn}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Built-in Generator Functions
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Built-in Generator Functions ---")
|
||||||
|
|
||||||
|
# range() is a generator-like object
|
||||||
|
print(f"range(5): {list(range(5))}")
|
||||||
|
|
||||||
|
# enumerate() yields (index, value)
|
||||||
|
fruits = ["apple", "banana", "cherry"]
|
||||||
|
print(f"\nenumerate(): {list(enumerate(fruits))}")
|
||||||
|
|
||||||
|
# zip() yields tuples from multiple iterables
|
||||||
|
names = ["Alice", "Bob"]
|
||||||
|
ages = [25, 30]
|
||||||
|
print(f"zip(): {list(zip(names, ages))}")
|
||||||
|
|
||||||
|
# map() yields transformed values
|
||||||
|
print(f"map(str.upper): {list(map(str.upper, fruits))}")
|
||||||
|
|
||||||
|
# filter() yields filtered values
|
||||||
|
numbers = [1, 2, 3, 4, 5, 6]
|
||||||
|
print(f"filter(even): {list(filter(lambda x: x % 2 == 0, numbers))}")
|
||||||
|
|
||||||
|
# itertools has many useful generators
|
||||||
|
from itertools import chain, cycle, repeat, takewhile
|
||||||
|
|
||||||
|
# chain - combine iterables
|
||||||
|
print(f"\nchain([1,2], [3,4]): {list(chain([1,2], [3,4]))}")
|
||||||
|
|
||||||
|
# takewhile - yield while condition is true
|
||||||
|
nums = [2, 4, 6, 7, 8, 10]
|
||||||
|
print(f"takewhile(even): {list(takewhile(lambda x: x % 2 == 0, nums))}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# 1. Reading large files line by line
|
||||||
|
def read_large_file(filepath):
|
||||||
|
"""Read file lazily, one line at a time."""
|
||||||
|
# In real code: yield from open(filepath)
|
||||||
|
# Here we simulate:
|
||||||
|
lines = ["Line 1", "Line 2", "Line 3"]
|
||||||
|
for line in lines:
|
||||||
|
yield line
|
||||||
|
|
||||||
|
print("Reading 'file' lazily:")
|
||||||
|
for line in read_large_file("data.txt"):
|
||||||
|
print(f" Processing: {line}")
|
||||||
|
|
||||||
|
# 2. Paginated API results
|
||||||
|
def fetch_paginated_data(total_pages=3):
|
||||||
|
"""Simulate fetching paginated API data."""
|
||||||
|
for page in range(1, total_pages + 1):
|
||||||
|
print(f" Fetching page {page}...")
|
||||||
|
yield [f"item_{page}_{i}" for i in range(3)]
|
||||||
|
|
||||||
|
print("\nPaginated data:")
|
||||||
|
for items in fetch_paginated_data():
|
||||||
|
for item in items:
|
||||||
|
print(f" {item}")
|
||||||
|
|
||||||
|
# 3. Sliding window
|
||||||
|
def sliding_window(iterable, size):
|
||||||
|
"""Generate sliding windows over data."""
|
||||||
|
from collections import deque
|
||||||
|
window = deque(maxlen=size)
|
||||||
|
|
||||||
|
for item in iterable:
|
||||||
|
window.append(item)
|
||||||
|
if len(window) == size:
|
||||||
|
yield tuple(window)
|
||||||
|
|
||||||
|
data = [1, 2, 3, 4, 5, 6]
|
||||||
|
print(f"\nSliding window (size 3) over {data}:")
|
||||||
|
for window in sliding_window(data, 3):
|
||||||
|
print(f" {window}")
|
||||||
|
|
||||||
|
# 4. Batch processing
|
||||||
|
def batch(iterable, size):
|
||||||
|
"""Yield items in batches."""
|
||||||
|
batch = []
|
||||||
|
for item in iterable:
|
||||||
|
batch.append(item)
|
||||||
|
if len(batch) == size:
|
||||||
|
yield batch
|
||||||
|
batch = []
|
||||||
|
if batch:
|
||||||
|
yield batch
|
||||||
|
|
||||||
|
items = list(range(1, 11))
|
||||||
|
print(f"\nBatching {items} into groups of 3:")
|
||||||
|
for b in batch(items, 3):
|
||||||
|
print(f" {b}")
|
||||||
421
09_advanced_python/03_decorators.py
Normal file
421
09_advanced_python/03_decorators.py
Normal file
|
|
@ -0,0 +1,421 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_decorators.py
|
||||||
|
Topic: Decorators in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates decorators in Python. Decorators are a powerful feature
|
||||||
|
that allows you to modify or enhance functions without changing their code.
|
||||||
|
They use the @ syntax and are widely used for logging, authentication, caching,
|
||||||
|
and more.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Function as first-class objects
|
||||||
|
- Simple decorators
|
||||||
|
- Decorators with arguments
|
||||||
|
- Preserving function metadata
|
||||||
|
- Class decorators
|
||||||
|
- Built-in decorators
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Functions as First-Class Objects
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Functions can be assigned, passed, and returned
|
||||||
|
|
||||||
|
print("--- Functions as First-Class Objects ---")
|
||||||
|
|
||||||
|
def greet(name):
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
# Assign to variable
|
||||||
|
say_hello = greet
|
||||||
|
print(say_hello("World"))
|
||||||
|
|
||||||
|
# Pass as argument
|
||||||
|
def apply_func(func, value):
|
||||||
|
return func(value)
|
||||||
|
|
||||||
|
print(apply_func(greet, "Python"))
|
||||||
|
|
||||||
|
# Return from function
|
||||||
|
def get_greeter():
|
||||||
|
def inner_greet(name):
|
||||||
|
return f"Hi, {name}!"
|
||||||
|
return inner_greet
|
||||||
|
|
||||||
|
greeter = get_greeter()
|
||||||
|
print(greeter("Alice"))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Simple Decorator
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# A decorator wraps a function to add behavior
|
||||||
|
|
||||||
|
print("\n--- Simple Decorator ---")
|
||||||
|
|
||||||
|
def my_decorator(func):
|
||||||
|
"""A simple decorator."""
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
print(" Before function call")
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
print(" After function call")
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
# Applying decorator manually
|
||||||
|
def say_hello_v1(name):
|
||||||
|
print(f" Hello, {name}!")
|
||||||
|
|
||||||
|
decorated = my_decorator(say_hello_v1)
|
||||||
|
decorated("World")
|
||||||
|
|
||||||
|
# Using @ syntax (syntactic sugar)
|
||||||
|
@my_decorator
|
||||||
|
def say_hello_v2(name):
|
||||||
|
print(f" Hello, {name}!")
|
||||||
|
|
||||||
|
print("\nWith @ syntax:")
|
||||||
|
say_hello_v2("Python")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Preserving Function Metadata
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use functools.wraps to preserve original function info
|
||||||
|
|
||||||
|
print("\n--- Preserving Metadata ---")
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
def better_decorator(func):
|
||||||
|
"""Decorator that preserves function metadata."""
|
||||||
|
@wraps(func) # Preserve original function's metadata
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@better_decorator
|
||||||
|
def example_function():
|
||||||
|
"""This is the docstring."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(f"Function name: {example_function.__name__}")
|
||||||
|
print(f"Function docstring: {example_function.__doc__}")
|
||||||
|
|
||||||
|
# Without @wraps, these would show 'wrapper' info instead
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Practical Decorators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Decorators ---")
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Timing decorator
|
||||||
|
def timer(func):
|
||||||
|
"""Measure function execution time."""
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
start = time.time()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end = time.time()
|
||||||
|
print(f" {func.__name__} took {end - start:.4f} seconds")
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def slow_function():
|
||||||
|
"""A deliberately slow function."""
|
||||||
|
time.sleep(0.1)
|
||||||
|
return "Done"
|
||||||
|
|
||||||
|
result = slow_function()
|
||||||
|
|
||||||
|
# Logging decorator
|
||||||
|
def logger(func):
|
||||||
|
"""Log function calls."""
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
args_str = ", ".join(map(repr, args))
|
||||||
|
kwargs_str = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
|
||||||
|
all_args = ", ".join(filter(None, [args_str, kwargs_str]))
|
||||||
|
print(f" Calling {func.__name__}({all_args})")
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
print(f" {func.__name__} returned {result!r}")
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@logger
|
||||||
|
def add(a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
print("\nLogger example:")
|
||||||
|
add(3, 5)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Decorators with Arguments
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Create configurable decorators with an extra layer
|
||||||
|
|
||||||
|
print("\n--- Decorators with Arguments ---")
|
||||||
|
|
||||||
|
def repeat(times):
|
||||||
|
"""Decorator to repeat function calls."""
|
||||||
|
def decorator(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
results = []
|
||||||
|
for _ in range(times):
|
||||||
|
results.append(func(*args, **kwargs))
|
||||||
|
return results
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
@repeat(times=3)
|
||||||
|
def greet_user(name):
|
||||||
|
print(f" Hello, {name}!")
|
||||||
|
return f"Greeted {name}"
|
||||||
|
|
||||||
|
print("Repeat decorator:")
|
||||||
|
results = greet_user("Alice")
|
||||||
|
print(f"Results: {results}")
|
||||||
|
|
||||||
|
# Retry decorator with custom attempts
|
||||||
|
def retry(max_attempts=3, exceptions=(Exception,)):
|
||||||
|
"""Retry function on failure."""
|
||||||
|
def decorator(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
for attempt in range(1, max_attempts + 1):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except exceptions as e:
|
||||||
|
print(f" Attempt {attempt} failed: {e}")
|
||||||
|
if attempt == max_attempts:
|
||||||
|
raise
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
attempt_count = 0
|
||||||
|
|
||||||
|
@retry(max_attempts=3)
|
||||||
|
def unstable_function():
|
||||||
|
global attempt_count
|
||||||
|
attempt_count += 1
|
||||||
|
if attempt_count < 3:
|
||||||
|
raise ValueError("Not ready yet!")
|
||||||
|
return "Success!"
|
||||||
|
|
||||||
|
print("\nRetry decorator:")
|
||||||
|
try:
|
||||||
|
result = unstable_function()
|
||||||
|
print(f" Final result: {result}")
|
||||||
|
except ValueError:
|
||||||
|
print(" All attempts failed")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Multiple Decorators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Decorators stack from bottom to top
|
||||||
|
|
||||||
|
print("\n--- Multiple Decorators ---")
|
||||||
|
|
||||||
|
def bold(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return f"<b>{func(*args, **kwargs)}</b>"
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def italic(func):
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return f"<i>{func(*args, **kwargs)}</i>"
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@bold
|
||||||
|
@italic
|
||||||
|
def greet_html(name):
|
||||||
|
return f"Hello, {name}"
|
||||||
|
|
||||||
|
# Applied bottom-up: italic first, then bold
|
||||||
|
# Same as: bold(italic(greet_html))
|
||||||
|
print(f"Stacked decorators: {greet_html('World')}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. Class-Based Decorators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Use a class as a decorator
|
||||||
|
|
||||||
|
print("\n--- Class-Based Decorators ---")
|
||||||
|
|
||||||
|
class CallCounter:
|
||||||
|
"""Decorator class to count function calls."""
|
||||||
|
|
||||||
|
def __init__(self, func):
|
||||||
|
self.func = func
|
||||||
|
self.count = 0
|
||||||
|
# Preserve function attributes
|
||||||
|
self.__name__ = func.__name__
|
||||||
|
self.__doc__ = func.__doc__
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
self.count += 1
|
||||||
|
print(f" {self.func.__name__} has been called {self.count} time(s)")
|
||||||
|
return self.func(*args, **kwargs)
|
||||||
|
|
||||||
|
@CallCounter
|
||||||
|
def hello():
|
||||||
|
"""Say hello."""
|
||||||
|
print(" Hello!")
|
||||||
|
|
||||||
|
hello()
|
||||||
|
hello()
|
||||||
|
hello()
|
||||||
|
print(f"Total calls: {hello.count}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Built-in Decorators
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Built-in Decorators ---")
|
||||||
|
|
||||||
|
# @property - create getter/setter
|
||||||
|
class Circle:
|
||||||
|
def __init__(self, radius):
|
||||||
|
self._radius = radius
|
||||||
|
|
||||||
|
@property
|
||||||
|
def radius(self):
|
||||||
|
return self._radius
|
||||||
|
|
||||||
|
@radius.setter
|
||||||
|
def radius(self, value):
|
||||||
|
if value < 0:
|
||||||
|
raise ValueError("Radius cannot be negative")
|
||||||
|
self._radius = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def area(self):
|
||||||
|
import math
|
||||||
|
return math.pi * self._radius ** 2
|
||||||
|
|
||||||
|
circle = Circle(5)
|
||||||
|
print(f"Circle radius: {circle.radius}")
|
||||||
|
print(f"Circle area: {circle.area:.2f}")
|
||||||
|
|
||||||
|
# @staticmethod - method without self
|
||||||
|
class Math:
|
||||||
|
@staticmethod
|
||||||
|
def add(a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
print(f"\n@staticmethod: Math.add(3, 4) = {Math.add(3, 4)}")
|
||||||
|
|
||||||
|
# @classmethod - method with cls instead of self
|
||||||
|
class Counter:
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Counter.count += 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_count(cls):
|
||||||
|
return cls.count
|
||||||
|
|
||||||
|
c1, c2, c3 = Counter(), Counter(), Counter()
|
||||||
|
print(f"@classmethod: Counter.get_count() = {Counter.get_count()}")
|
||||||
|
|
||||||
|
# @functools.lru_cache - memoization
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
@lru_cache(maxsize=128)
|
||||||
|
def fibonacci(n):
|
||||||
|
if n < 2:
|
||||||
|
return n
|
||||||
|
return fibonacci(n-1) + fibonacci(n-2)
|
||||||
|
|
||||||
|
print(f"\n@lru_cache: fibonacci(30) = {fibonacci(30)}")
|
||||||
|
print(f"Cache info: {fibonacci.cache_info()}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Decorator for Methods
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Decorating Methods ---")
|
||||||
|
|
||||||
|
def debug_method(func):
|
||||||
|
"""Debug decorator for class methods."""
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
print(f" {class_name}.{func.__name__} called")
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
class Calculator:
|
||||||
|
@debug_method
|
||||||
|
def add(self, a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
@debug_method
|
||||||
|
def multiply(self, a, b):
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
calc = Calculator()
|
||||||
|
calc.add(3, 5)
|
||||||
|
calc.multiply(4, 6)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Practical Examples
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Examples ---")
|
||||||
|
|
||||||
|
# 1. Authorization decorator
|
||||||
|
def require_auth(func):
|
||||||
|
"""Check if user is authenticated."""
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(user, *args, **kwargs):
|
||||||
|
if not user.get("authenticated", False):
|
||||||
|
raise PermissionError("Authentication required")
|
||||||
|
return func(user, *args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@require_auth
|
||||||
|
def get_secret_data(user):
|
||||||
|
return f"Secret data for {user['name']}"
|
||||||
|
|
||||||
|
print("Authorization decorator:")
|
||||||
|
try:
|
||||||
|
result = get_secret_data({"name": "Guest", "authenticated": False})
|
||||||
|
except PermissionError as e:
|
||||||
|
print(f" Denied: {e}")
|
||||||
|
|
||||||
|
result = get_secret_data({"name": "Admin", "authenticated": True})
|
||||||
|
print(f" Allowed: {result}")
|
||||||
|
|
||||||
|
# 2. Validation decorator
|
||||||
|
def validate_positive(func):
|
||||||
|
"""Ensure all numeric arguments are positive."""
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
for arg in args:
|
||||||
|
if isinstance(arg, (int, float)) and arg < 0:
|
||||||
|
raise ValueError(f"Negative value not allowed: {arg}")
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@validate_positive
|
||||||
|
def calculate_area(width, height):
|
||||||
|
return width * height
|
||||||
|
|
||||||
|
print("\nValidation decorator:")
|
||||||
|
try:
|
||||||
|
calculate_area(-5, 10)
|
||||||
|
except ValueError as e:
|
||||||
|
print(f" Validation error: {e}")
|
||||||
|
|
||||||
|
print(f" Valid call: {calculate_area(5, 10)}")
|
||||||
382
09_advanced_python/04_context_managers.py
Normal file
382
09_advanced_python/04_context_managers.py
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 04_context_managers.py
|
||||||
|
Topic: Context Managers in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates context managers in Python. Context managers are objects
|
||||||
|
that define setup and cleanup actions, typically used with the 'with' statement.
|
||||||
|
They ensure resources are properly managed even when errors occur.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- The 'with' statement
|
||||||
|
- __enter__ and __exit__ methods
|
||||||
|
- contextlib module
|
||||||
|
- File handling
|
||||||
|
- Resource management patterns
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. The 'with' Statement
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Ensures cleanup happens automatically
|
||||||
|
|
||||||
|
print("--- The 'with' Statement ---")
|
||||||
|
|
||||||
|
# Most common use: file handling
|
||||||
|
# Without 'with' (error-prone)
|
||||||
|
"""
|
||||||
|
file = open('example.txt', 'w')
|
||||||
|
try:
|
||||||
|
file.write('Hello')
|
||||||
|
finally:
|
||||||
|
file.close() # Must remember to close!
|
||||||
|
"""
|
||||||
|
|
||||||
|
# With 'with' (automatic cleanup)
|
||||||
|
"""
|
||||||
|
with open('example.txt', 'w') as file:
|
||||||
|
file.write('Hello')
|
||||||
|
# File is automatically closed here
|
||||||
|
"""
|
||||||
|
|
||||||
|
print("The 'with' statement:")
|
||||||
|
print(" - Automatically handles setup and cleanup")
|
||||||
|
print(" - Ensures cleanup even if errors occur")
|
||||||
|
print(" - Cleaner, more readable code")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Creating a Context Manager Class
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Implement __enter__ and __exit__
|
||||||
|
|
||||||
|
print("\n--- Context Manager Class ---")
|
||||||
|
|
||||||
|
class ManagedFile:
|
||||||
|
"""A custom context manager for file handling."""
|
||||||
|
|
||||||
|
def __init__(self, filename, mode='r'):
|
||||||
|
self.filename = filename
|
||||||
|
self.mode = mode
|
||||||
|
self.file = None
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""Called when entering 'with' block."""
|
||||||
|
print(f" Opening file: {self.filename}")
|
||||||
|
# In real code: self.file = open(self.filename, self.mode)
|
||||||
|
self.file = f"<FileObject: {self.filename}>"
|
||||||
|
return self.file # This is assigned to the 'as' variable
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""Called when exiting 'with' block."""
|
||||||
|
print(f" Closing file: {self.filename}")
|
||||||
|
# In real code: self.file.close()
|
||||||
|
|
||||||
|
# exc_type, exc_val, exc_tb contain exception info if an error occurred
|
||||||
|
if exc_type is not None:
|
||||||
|
print(f" Exception occurred: {exc_type.__name__}: {exc_val}")
|
||||||
|
|
||||||
|
# Return True to suppress the exception, False to propagate
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Using the context manager
|
||||||
|
print("Normal usage:")
|
||||||
|
with ManagedFile("data.txt", "w") as f:
|
||||||
|
print(f" Working with: {f}")
|
||||||
|
|
||||||
|
print("\nWith exception:")
|
||||||
|
try:
|
||||||
|
with ManagedFile("data.txt") as f:
|
||||||
|
print(f" Working with: {f}")
|
||||||
|
raise ValueError("Something went wrong!")
|
||||||
|
except ValueError:
|
||||||
|
print(" Exception was propagated")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Context Manager with Exception Suppression
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Suppressing Exceptions ---")
|
||||||
|
|
||||||
|
class SuppressErrors:
|
||||||
|
"""Context manager that suppresses specified exceptions."""
|
||||||
|
|
||||||
|
def __init__(self, *exceptions):
|
||||||
|
self.exceptions = exceptions
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if exc_type is not None and issubclass(exc_type, self.exceptions):
|
||||||
|
print(f" Suppressed: {exc_type.__name__}: {exc_val}")
|
||||||
|
return True # Suppress the exception
|
||||||
|
return False # Don't suppress
|
||||||
|
|
||||||
|
# Using the suppressor
|
||||||
|
print("Suppressing ValueError:")
|
||||||
|
with SuppressErrors(ValueError, TypeError):
|
||||||
|
print(" Before error")
|
||||||
|
raise ValueError("This will be suppressed")
|
||||||
|
print(" After error") # Never reached
|
||||||
|
|
||||||
|
print(" Code continues after 'with' block")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Using contextlib
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Create context managers without classes
|
||||||
|
|
||||||
|
print("\n--- Using contextlib ---")
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def managed_resource(name):
|
||||||
|
"""A context manager using @contextmanager decorator."""
|
||||||
|
print(f" Acquiring: {name}")
|
||||||
|
try:
|
||||||
|
yield name # Control is passed to 'with' block here
|
||||||
|
finally:
|
||||||
|
print(f" Releasing: {name}")
|
||||||
|
|
||||||
|
print("@contextmanager decorator:")
|
||||||
|
with managed_resource("Database") as resource:
|
||||||
|
print(f" Using: {resource}")
|
||||||
|
|
||||||
|
# Timer context manager
|
||||||
|
import time
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def timer(description="Operation"):
|
||||||
|
"""Time the enclosed code block."""
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
print(f" {description} took {elapsed:.4f} seconds")
|
||||||
|
|
||||||
|
print("\nTimer context manager:")
|
||||||
|
with timer("Sleep operation"):
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Multiple Context Managers
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Multiple Context Managers ---")
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def open_mock_file(name):
|
||||||
|
"""Mock file context manager."""
|
||||||
|
print(f" Opening: {name}")
|
||||||
|
yield f"<{name}>"
|
||||||
|
print(f" Closing: {name}")
|
||||||
|
|
||||||
|
# Multiple context managers in one 'with'
|
||||||
|
print("Multiple context managers:")
|
||||||
|
with open_mock_file("input.txt") as infile, \
|
||||||
|
open_mock_file("output.txt") as outfile:
|
||||||
|
print(f" Reading from: {infile}")
|
||||||
|
print(f" Writing to: {outfile}")
|
||||||
|
|
||||||
|
# Python 3.10+ allows parenthesized context managers
|
||||||
|
"""
|
||||||
|
with (
|
||||||
|
open_mock_file("input.txt") as infile,
|
||||||
|
open_mock_file("output.txt") as outfile
|
||||||
|
):
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Practical Context Managers
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Practical Context Managers ---")
|
||||||
|
|
||||||
|
# 1. Database connection (mock)
|
||||||
|
@contextmanager
|
||||||
|
def database_connection(host):
|
||||||
|
"""Mock database connection."""
|
||||||
|
print(f" Connecting to {host}...")
|
||||||
|
connection = {"host": host, "connected": True}
|
||||||
|
try:
|
||||||
|
yield connection
|
||||||
|
finally:
|
||||||
|
connection["connected"] = False
|
||||||
|
print(f" Disconnected from {host}")
|
||||||
|
|
||||||
|
print("Database connection:")
|
||||||
|
with database_connection("localhost") as db:
|
||||||
|
print(f" Connected: {db['connected']}")
|
||||||
|
# Perform database operations
|
||||||
|
|
||||||
|
# 2. Temporary working directory
|
||||||
|
import os
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def temp_directory_context():
|
||||||
|
"""Change to temp directory and restore."""
|
||||||
|
original_dir = os.getcwd()
|
||||||
|
temp_dir = os.path.dirname(original_dir) if original_dir else original_dir
|
||||||
|
try:
|
||||||
|
# In real code: os.chdir(temp_dir)
|
||||||
|
print(f" Changed to: {temp_dir}")
|
||||||
|
yield temp_dir
|
||||||
|
finally:
|
||||||
|
# os.chdir(original_dir)
|
||||||
|
print(f" Restored to: {original_dir}")
|
||||||
|
|
||||||
|
print("\nDirectory change:")
|
||||||
|
with temp_directory_context():
|
||||||
|
print(" Working in temp directory")
|
||||||
|
|
||||||
|
# 3. Lock context manager
|
||||||
|
@contextmanager
|
||||||
|
def locked(lock_name="default"):
|
||||||
|
"""Mock lock context manager."""
|
||||||
|
print(f" Acquiring lock: {lock_name}")
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
print(f" Releasing lock: {lock_name}")
|
||||||
|
|
||||||
|
print("\nLocking:")
|
||||||
|
with locked("resource_lock"):
|
||||||
|
print(" Critical section")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 7. contextlib Utilities
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- contextlib Utilities ---")
|
||||||
|
|
||||||
|
from contextlib import closing, suppress
|
||||||
|
|
||||||
|
# closing() - for objects with close() but no __exit__
|
||||||
|
class Connection:
|
||||||
|
def close(self):
|
||||||
|
print(" Connection closed")
|
||||||
|
|
||||||
|
print("using closing():")
|
||||||
|
with closing(Connection()) as conn:
|
||||||
|
print(" Using connection")
|
||||||
|
|
||||||
|
# suppress() - ignore specified exceptions
|
||||||
|
print("\nsuppress() example:")
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
|
# Instead of try/except for simple cases
|
||||||
|
with suppress(FileNotFoundError, KeyError):
|
||||||
|
# This would normally raise an error
|
||||||
|
print(" Attempting risky operations...")
|
||||||
|
# raise FileNotFoundError("No such file")
|
||||||
|
print(" Continued after suppress")
|
||||||
|
|
||||||
|
# nullcontext() - do-nothing context manager (Python 3.7+)
|
||||||
|
from contextlib import nullcontext
|
||||||
|
|
||||||
|
def process_data(data, lock=None):
|
||||||
|
"""Process with optional lock."""
|
||||||
|
with lock if lock else nullcontext():
|
||||||
|
print(f" Processing: {data}")
|
||||||
|
|
||||||
|
print("\nnullcontext() example:")
|
||||||
|
process_data("data1") # No lock
|
||||||
|
process_data("data2", locked("my_lock")) # With lock
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 8. Async Context Managers
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Async Context Managers ---")
|
||||||
|
|
||||||
|
# For async code, use __aenter__ and __aexit__
|
||||||
|
async_example = '''
|
||||||
|
class AsyncResource:
|
||||||
|
async def __aenter__(self):
|
||||||
|
print("Acquiring async resource...")
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
print("Releasing async resource...")
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Usage:
|
||||||
|
async with AsyncResource() as resource:
|
||||||
|
print("Using resource")
|
||||||
|
'''
|
||||||
|
|
||||||
|
print("Async context managers use __aenter__ and __aexit__")
|
||||||
|
print("Use 'async with' statement")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 9. Reentrant and Reusable Context Managers
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Reentrant Context Managers ---")
|
||||||
|
|
||||||
|
# A context manager that can be used multiple times
|
||||||
|
class ReusableContext:
|
||||||
|
"""Context manager that can be reused."""
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.uses = 0
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.uses += 1
|
||||||
|
print(f" Entering {self.name} (use #{self.uses})")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
print(f" Exiting {self.name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
ctx = ReusableContext("MyContext")
|
||||||
|
|
||||||
|
print("Reusing context manager:")
|
||||||
|
with ctx:
|
||||||
|
print(" First use")
|
||||||
|
|
||||||
|
with ctx:
|
||||||
|
print(" Second use")
|
||||||
|
|
||||||
|
print(f"Total uses: {ctx.uses}")
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 10. Real-World Pattern: Configuration Override
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
print("\n--- Real-World: Config Override ---")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""Application configuration."""
|
||||||
|
settings = {"debug": False, "log_level": "INFO"}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@contextmanager
|
||||||
|
def override(cls, **overrides):
|
||||||
|
"""Temporarily override configuration."""
|
||||||
|
original = cls.settings.copy()
|
||||||
|
try:
|
||||||
|
cls.settings.update(overrides)
|
||||||
|
print(f" Config overridden: {overrides}")
|
||||||
|
yield cls.settings
|
||||||
|
finally:
|
||||||
|
cls.settings = original
|
||||||
|
print(" Config restored")
|
||||||
|
|
||||||
|
print(f"Original config: {Config.settings}")
|
||||||
|
|
||||||
|
with Config.override(debug=True, log_level="DEBUG") as settings:
|
||||||
|
print(f" Inside 'with': {settings}")
|
||||||
|
|
||||||
|
print(f"After 'with': {Config.settings}")
|
||||||
334
10_best_practices/01_pep8.py
Normal file
334
10_best_practices/01_pep8.py
Normal file
|
|
@ -0,0 +1,334 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 01_pep8.py
|
||||||
|
Topic: PEP 8 - Python Style Guide
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates PEP 8, the official Python style guide. Following PEP 8
|
||||||
|
makes your code more readable, consistent, and professional. These are
|
||||||
|
conventions, not strict rules, but following them is highly recommended.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Indentation and whitespace
|
||||||
|
- Naming conventions
|
||||||
|
- Line length and wrapping
|
||||||
|
- Imports organization
|
||||||
|
- Comments and docstrings
|
||||||
|
|
||||||
|
Reference: https://peps.python.org/pep-0008/
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 1. INDENTATION
|
||||||
|
# =============================================================================
|
||||||
|
# Use 4 spaces per indentation level. Never mix tabs and spaces.
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
def function_with_proper_indentation():
|
||||||
|
if True:
|
||||||
|
for i in range(10):
|
||||||
|
print(i)
|
||||||
|
|
||||||
|
# Aligned with opening delimiter
|
||||||
|
foo = long_function_name(var_one, var_two,
|
||||||
|
var_three, var_four)
|
||||||
|
|
||||||
|
# Hanging indent (add a level)
|
||||||
|
def long_function_name(
|
||||||
|
var_one, var_two, var_three,
|
||||||
|
var_four):
|
||||||
|
print(var_one)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 2. LINE LENGTH
|
||||||
|
# =============================================================================
|
||||||
|
# Limit lines to 79 characters (72 for docstrings/comments)
|
||||||
|
|
||||||
|
# GOOD - Use implicit line continuation
|
||||||
|
total = (first_variable
|
||||||
|
+ second_variable
|
||||||
|
+ third_variable)
|
||||||
|
|
||||||
|
# GOOD - Use backslash when necessary
|
||||||
|
with open('/very/long/path/to/file.txt') as file_one, \
|
||||||
|
open('/another/long/path/to/file.txt') as file_two:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 3. BLANK LINES
|
||||||
|
# =============================================================================
|
||||||
|
# - 2 blank lines around top-level functions and classes
|
||||||
|
# - 1 blank line between methods in a class
|
||||||
|
|
||||||
|
|
||||||
|
class FirstClass:
|
||||||
|
"""First class."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SecondClass:
|
||||||
|
"""Second class."""
|
||||||
|
|
||||||
|
def method_one(self):
|
||||||
|
"""First method."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def method_two(self):
|
||||||
|
"""Second method."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def top_level_function():
|
||||||
|
"""A top-level function."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 4. IMPORTS
|
||||||
|
# =============================================================================
|
||||||
|
# - One import per line
|
||||||
|
# - Group in order: standard library, third-party, local
|
||||||
|
# - Use absolute imports
|
||||||
|
|
||||||
|
# GOOD
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
# Third party imports (after blank line)
|
||||||
|
# import numpy as np
|
||||||
|
# import pandas as pd
|
||||||
|
|
||||||
|
# Local imports (after blank line)
|
||||||
|
# from mypackage import mymodule
|
||||||
|
|
||||||
|
# BAD - Multiple imports on one line
|
||||||
|
# import os, sys
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 5. WHITESPACE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# GOOD - No extra whitespace inside brackets
|
||||||
|
spam = [1, 2, 3]
|
||||||
|
ham = {"key": "value"}
|
||||||
|
eggs = (1,)
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
# spam = [ 1, 2, 3 ]
|
||||||
|
# ham = { 'key': 'value' }
|
||||||
|
|
||||||
|
# GOOD - One space around operators
|
||||||
|
x = 1
|
||||||
|
y = 2
|
||||||
|
z = x + y
|
||||||
|
|
||||||
|
# BAD - No space around = in keyword arguments
|
||||||
|
# def function(x, y = 5):
|
||||||
|
def function(x, y=5):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# GOOD - Space after comma
|
||||||
|
some_list = [1, 2, 3]
|
||||||
|
|
||||||
|
# GOOD - No space before colon in slices
|
||||||
|
some_list[1:3]
|
||||||
|
some_list[::2]
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 6. NAMING CONVENTIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Variables and functions: lowercase_with_underscores (snake_case)
|
||||||
|
user_name = "John"
|
||||||
|
total_count = 42
|
||||||
|
|
||||||
|
def calculate_average(numbers):
|
||||||
|
return sum(numbers) / len(numbers)
|
||||||
|
|
||||||
|
# Constants: UPPERCASE_WITH_UNDERSCORES
|
||||||
|
MAX_BUFFER_SIZE = 4096
|
||||||
|
DEFAULT_TIMEOUT = 30
|
||||||
|
PI = 3.14159
|
||||||
|
|
||||||
|
# Classes: CapitalizedWords (PascalCase)
|
||||||
|
class UserAccount:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class HttpConnection:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Private: prefix with underscore
|
||||||
|
_internal_variable = "private"
|
||||||
|
def _internal_function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
def _protected_method(self):
|
||||||
|
"""Single underscore = protected (convention)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __private_method(self):
|
||||||
|
"""Double underscore = name mangling (truly private)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 7. COMMENTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# GOOD - Inline comments have at least 2 spaces before #
|
||||||
|
x = 5 # This is an inline comment
|
||||||
|
|
||||||
|
# Block comments precede the code they describe
|
||||||
|
# This is a block comment that explains
|
||||||
|
# the following piece of code.
|
||||||
|
complex_calculation = 1 + 2 + 3
|
||||||
|
|
||||||
|
# Don't state the obvious
|
||||||
|
# BAD: x = 5 # Assign 5 to x
|
||||||
|
# GOOD: x = 5 # Default timeout in seconds
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 8. DOCSTRINGS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def example_function(param1, param2):
|
||||||
|
"""
|
||||||
|
Brief description of the function.
|
||||||
|
|
||||||
|
Longer description if needed. Explain what the function
|
||||||
|
does, not how it does it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param1: Description of first parameter
|
||||||
|
param2: Description of second parameter
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Description of what is returned
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: When param1 is negative
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> example_function(1, 2)
|
||||||
|
3
|
||||||
|
"""
|
||||||
|
return param1 + param2
|
||||||
|
|
||||||
|
|
||||||
|
class ExampleClass:
|
||||||
|
"""Brief description of the class.
|
||||||
|
|
||||||
|
Longer description of the class including its purpose
|
||||||
|
and usage patterns.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
attr1: Description of first attribute
|
||||||
|
attr2: Description of second attribute
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, attr1, attr2):
|
||||||
|
"""Initialize ExampleClass."""
|
||||||
|
self.attr1 = attr1
|
||||||
|
self.attr2 = attr2
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 9. COMPARISON AND BOOLEAN
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Use 'is' and 'is not' for None comparisons
|
||||||
|
# GOOD
|
||||||
|
x = None
|
||||||
|
if x is None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
# if x == None:
|
||||||
|
|
||||||
|
# Use 'is not' instead of 'not ... is'
|
||||||
|
# GOOD
|
||||||
|
if x is not None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
# if not x is None:
|
||||||
|
|
||||||
|
# Don't compare boolean values with == or !=
|
||||||
|
# GOOD
|
||||||
|
flag = True
|
||||||
|
if flag:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# BAD
|
||||||
|
# if flag == True:
|
||||||
|
# if flag is True: # Only use when testing identity
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 10. EXCEPTION HANDLING
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Catch specific exceptions
|
||||||
|
# GOOD
|
||||||
|
try:
|
||||||
|
value = int(user_input)
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input")
|
||||||
|
|
||||||
|
# BAD - Too broad
|
||||||
|
# try:
|
||||||
|
# value = int(user_input)
|
||||||
|
# except: # or except Exception:
|
||||||
|
# print("Error")
|
||||||
|
|
||||||
|
# Use 'raise' to re-raise exception
|
||||||
|
try:
|
||||||
|
process_data()
|
||||||
|
except ValueError:
|
||||||
|
logger.error("Bad value")
|
||||||
|
raise
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 11. FUNCTION ANNOTATIONS (Type Hints)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def greeting(name: str) -> str:
|
||||||
|
"""Return a greeting."""
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
# Complex types
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
|
||||||
|
def process_items(
|
||||||
|
items: List[str],
|
||||||
|
config: Dict[str, int],
|
||||||
|
default: Optional[str] = None
|
||||||
|
) -> bool:
|
||||||
|
"""Process items with configuration."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 12. SUMMARY: KEY RULES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("PEP 8 Summary - Key Rules:")
|
||||||
|
print("""
|
||||||
|
1. Use 4 spaces for indentation
|
||||||
|
2. Limit lines to 79 characters
|
||||||
|
3. Use blank lines to separate functions and classes
|
||||||
|
4. Organize imports: standard lib, third-party, local
|
||||||
|
5. Use snake_case for functions and variables
|
||||||
|
6. Use PascalCase for classes
|
||||||
|
7. Use UPPER_CASE for constants
|
||||||
|
8. Use spaces around operators and after commas
|
||||||
|
9. Write docstrings for all public modules, functions, classes
|
||||||
|
10. Compare with 'is' for None, use bool directly
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Use flake8 or black to automatically check/format code
|
||||||
|
print("Tools to help with PEP 8:")
|
||||||
|
print(" - flake8: Check for PEP 8 violations")
|
||||||
|
print(" - black: Automatic code formatting")
|
||||||
|
print(" - isort: Sort imports automatically")
|
||||||
|
print(" - pylint: Comprehensive code analysis")
|
||||||
392
10_best_practices/02_type_hinting.py
Normal file
392
10_best_practices/02_type_hinting.py
Normal file
|
|
@ -0,0 +1,392 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 02_type_hinting.py
|
||||||
|
Topic: Type Hints in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file demonstrates type hints (type annotations) in Python. Type hints make
|
||||||
|
code more readable, enable better IDE support, and can catch bugs early using
|
||||||
|
static type checkers like mypy.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Basic type hints
|
||||||
|
- typing module (List, Dict, Optional, etc.)
|
||||||
|
- Function annotations
|
||||||
|
- Class type hints
|
||||||
|
- Generics
|
||||||
|
- Type checking tools
|
||||||
|
|
||||||
|
Note: Type hints are OPTIONAL and don't affect runtime behavior!
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 1. BASIC TYPE HINTS
|
||||||
|
# =============================================================================
|
||||||
|
# Syntax: variable: Type = value
|
||||||
|
|
||||||
|
print("--- Basic Type Hints ---")
|
||||||
|
|
||||||
|
# Simple types
|
||||||
|
name: str = "Alice"
|
||||||
|
age: int = 25
|
||||||
|
height: float = 1.75
|
||||||
|
is_active: bool = True
|
||||||
|
|
||||||
|
print(f"name: str = '{name}'")
|
||||||
|
print(f"age: int = {age}")
|
||||||
|
print(f"height: float = {height}")
|
||||||
|
print(f"is_active: bool = {is_active}")
|
||||||
|
|
||||||
|
# Type hints are just hints - they don't enforce types at runtime!
|
||||||
|
# This works but is wrong (would be caught by type checker):
|
||||||
|
# age: int = "not an int"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 2. FUNCTION TYPE HINTS
|
||||||
|
# =============================================================================
|
||||||
|
# Parameters and return types
|
||||||
|
|
||||||
|
print("\n--- Function Type Hints ---")
|
||||||
|
|
||||||
|
def greet(name: str) -> str:
|
||||||
|
"""Return a greeting string."""
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
def add_numbers(a: int, b: int) -> int:
|
||||||
|
"""Add two integers."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def is_even(n: int) -> bool:
|
||||||
|
"""Check if number is even."""
|
||||||
|
return n % 2 == 0
|
||||||
|
|
||||||
|
# Function with no return value
|
||||||
|
def print_message(message: str) -> None:
|
||||||
|
"""Print a message (returns None)."""
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
print(f"greet('World'): {greet('World')}")
|
||||||
|
print(f"add_numbers(3, 5): {add_numbers(3, 5)}")
|
||||||
|
print(f"is_even(4): {is_even(4)}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 3. TYPING MODULE - COLLECTION TYPES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Collection Types ---")
|
||||||
|
|
||||||
|
from typing import List, Dict, Set, Tuple
|
||||||
|
|
||||||
|
# List of specific type
|
||||||
|
def process_numbers(numbers: List[int]) -> int:
|
||||||
|
"""Sum a list of integers."""
|
||||||
|
return sum(numbers)
|
||||||
|
|
||||||
|
# Dictionary with key and value types
|
||||||
|
def get_user_ages(users: Dict[str, int]) -> List[str]:
|
||||||
|
"""Get names of users older than 18."""
|
||||||
|
return [name for name, age in users.items() if age > 18]
|
||||||
|
|
||||||
|
# Set of specific type
|
||||||
|
def get_unique_words(text: str) -> Set[str]:
|
||||||
|
"""Get unique words from text."""
|
||||||
|
return set(text.lower().split())
|
||||||
|
|
||||||
|
# Tuple with specific types (fixed length)
|
||||||
|
def get_point() -> Tuple[float, float]:
|
||||||
|
"""Return an (x, y) coordinate."""
|
||||||
|
return (1.5, 2.5)
|
||||||
|
|
||||||
|
# Tuple with variable length of same type
|
||||||
|
def get_scores() -> Tuple[int, ...]:
|
||||||
|
"""Return any number of scores."""
|
||||||
|
return (85, 90, 78, 92)
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
print(f"process_numbers([1,2,3]): {process_numbers([1, 2, 3])}")
|
||||||
|
print(f"get_unique_words('hello hello world'): {get_unique_words('hello hello world')}")
|
||||||
|
print(f"get_point(): {get_point()}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 4. OPTIONAL AND UNION TYPES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Optional and Union ---")
|
||||||
|
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
# Optional = can be None
|
||||||
|
def find_user(user_id: int) -> Optional[str]:
|
||||||
|
"""Find user by ID, return None if not found."""
|
||||||
|
users = {1: "Alice", 2: "Bob"}
|
||||||
|
return users.get(user_id) # Returns None if not found
|
||||||
|
|
||||||
|
# Union = can be one of several types
|
||||||
|
def process_input(value: Union[int, str]) -> str:
|
||||||
|
"""Process either int or string input."""
|
||||||
|
if isinstance(value, int):
|
||||||
|
return f"Number: {value}"
|
||||||
|
return f"String: {value}"
|
||||||
|
|
||||||
|
# Python 3.10+ syntax: X | Y instead of Union[X, Y]
|
||||||
|
# def process_input(value: int | str) -> str:
|
||||||
|
|
||||||
|
print(f"find_user(1): {find_user(1)}")
|
||||||
|
print(f"find_user(99): {find_user(99)}")
|
||||||
|
print(f"process_input(42): {process_input(42)}")
|
||||||
|
print(f"process_input('hello'): {process_input('hello')}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 5. SPECIAL TYPES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Special Types ---")
|
||||||
|
|
||||||
|
from typing import Any, Callable, Sequence, Iterable
|
||||||
|
|
||||||
|
# Any - accepts any type (avoid when possible)
|
||||||
|
def log_value(value: Any) -> None:
|
||||||
|
"""Log any value."""
|
||||||
|
print(f" Logged: {value}")
|
||||||
|
|
||||||
|
log_value(42)
|
||||||
|
log_value("hello")
|
||||||
|
log_value([1, 2, 3])
|
||||||
|
|
||||||
|
# Callable - function type
|
||||||
|
def apply_function(func: Callable[[int, int], int], a: int, b: int) -> int:
|
||||||
|
"""Apply a function to two numbers."""
|
||||||
|
return func(a, b)
|
||||||
|
|
||||||
|
print(f"\napply_function(lambda x,y: x+y, 3, 4): {apply_function(lambda x, y: x + y, 3, 4)}")
|
||||||
|
|
||||||
|
# Sequence - any ordered collection (list, tuple, str)
|
||||||
|
def get_first(items: Sequence[int]) -> int:
|
||||||
|
"""Get first item from sequence."""
|
||||||
|
return items[0]
|
||||||
|
|
||||||
|
# Iterable - anything you can iterate over
|
||||||
|
def count_items(items: Iterable[str]) -> int:
|
||||||
|
"""Count items in iterable."""
|
||||||
|
return sum(1 for _ in items)
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 6. TYPE ALIASES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Type Aliases ---")
|
||||||
|
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
# Create readable aliases for complex types
|
||||||
|
UserId = int
|
||||||
|
Username = str
|
||||||
|
Coordinate = Tuple[float, float]
|
||||||
|
UserList = List[Dict[str, Union[str, int]]]
|
||||||
|
|
||||||
|
def get_user(user_id: UserId) -> Username:
|
||||||
|
"""Get username by ID."""
|
||||||
|
return f"user_{user_id}"
|
||||||
|
|
||||||
|
def calculate_distance(point1: Coordinate, point2: Coordinate) -> float:
|
||||||
|
"""Calculate distance between two points."""
|
||||||
|
return ((point2[0] - point1[0])**2 + (point2[1] - point1[1])**2)**0.5
|
||||||
|
|
||||||
|
print(f"get_user(123): {get_user(123)}")
|
||||||
|
print(f"distance((0,0), (3,4)): {calculate_distance((0, 0), (3, 4))}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 7. CLASS TYPE HINTS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Class Type Hints ---")
|
||||||
|
|
||||||
|
from typing import Optional, List, ClassVar
|
||||||
|
|
||||||
|
class Person:
|
||||||
|
"""A person with type-hinted attributes."""
|
||||||
|
|
||||||
|
# Class variables
|
||||||
|
species: ClassVar[str] = "Homo sapiens"
|
||||||
|
|
||||||
|
def __init__(self, name: str, age: int) -> None:
|
||||||
|
self.name: str = name
|
||||||
|
self.age: int = age
|
||||||
|
self.email: Optional[str] = None
|
||||||
|
|
||||||
|
def set_email(self, email: str) -> None:
|
||||||
|
"""Set the person's email."""
|
||||||
|
self.email = email
|
||||||
|
|
||||||
|
def get_info(self) -> str:
|
||||||
|
"""Get person's info."""
|
||||||
|
return f"{self.name}, {self.age} years old"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: Dict[str, Any]) -> "Person":
|
||||||
|
"""Create Person from dictionary."""
|
||||||
|
return cls(data["name"], data["age"])
|
||||||
|
|
||||||
|
person = Person("Alice", 25)
|
||||||
|
person.set_email("alice@example.com")
|
||||||
|
print(f"Person: {person.get_info()}")
|
||||||
|
print(f"Email: {person.email}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 8. GENERICS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Generics ---")
|
||||||
|
|
||||||
|
from typing import TypeVar, Generic, List
|
||||||
|
|
||||||
|
# Define a type variable
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
# Generic function
|
||||||
|
def first_item(items: List[T]) -> T:
|
||||||
|
"""Get first item of any list type."""
|
||||||
|
return items[0]
|
||||||
|
|
||||||
|
# Generic class
|
||||||
|
class Stack(Generic[T]):
|
||||||
|
"""A generic stack."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._items: List[T] = []
|
||||||
|
|
||||||
|
def push(self, item: T) -> None:
|
||||||
|
"""Push item onto stack."""
|
||||||
|
self._items.append(item)
|
||||||
|
|
||||||
|
def pop(self) -> T:
|
||||||
|
"""Pop item from stack."""
|
||||||
|
return self._items.pop()
|
||||||
|
|
||||||
|
def is_empty(self) -> bool:
|
||||||
|
"""Check if stack is empty."""
|
||||||
|
return len(self._items) == 0
|
||||||
|
|
||||||
|
# Using generics
|
||||||
|
print(f"first_item([1, 2, 3]): {first_item([1, 2, 3])}")
|
||||||
|
print(f"first_item(['a', 'b']): {first_item(['a', 'b'])}")
|
||||||
|
|
||||||
|
stack: Stack[int] = Stack()
|
||||||
|
stack.push(1)
|
||||||
|
stack.push(2)
|
||||||
|
stack.push(3)
|
||||||
|
print(f"Stack pop: {stack.pop()}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 9. LITERAL AND FINAL
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Literal and Final ---")
|
||||||
|
|
||||||
|
from typing import Literal, Final
|
||||||
|
|
||||||
|
# Literal - exact values only
|
||||||
|
Mode = Literal["r", "w", "a"]
|
||||||
|
|
||||||
|
def open_file(path: str, mode: Mode) -> str:
|
||||||
|
"""Open file with specific mode."""
|
||||||
|
return f"Opening {path} in mode '{mode}'"
|
||||||
|
|
||||||
|
print(open_file("data.txt", "r"))
|
||||||
|
# open_file("data.txt", "x") # Type error! "x" is not allowed
|
||||||
|
|
||||||
|
# Final - cannot be reassigned
|
||||||
|
MAX_SIZE: Final[int] = 100
|
||||||
|
# MAX_SIZE = 200 # Type checker would flag this
|
||||||
|
|
||||||
|
print(f"MAX_SIZE (Final): {MAX_SIZE}")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 10. TYPE CHECKING TOOLS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n--- Type Checking ---")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
Type hints are NOT enforced at runtime!
|
||||||
|
Use these tools to check types statically:
|
||||||
|
|
||||||
|
1. mypy - The most popular type checker
|
||||||
|
pip install mypy
|
||||||
|
mypy your_script.py
|
||||||
|
|
||||||
|
2. pyright - Microsoft's type checker (fast)
|
||||||
|
pip install pyright
|
||||||
|
pyright your_script.py
|
||||||
|
|
||||||
|
3. IDE Support
|
||||||
|
- VS Code with Pylance
|
||||||
|
- PyCharm (built-in)
|
||||||
|
|
||||||
|
Example mypy output:
|
||||||
|
error: Argument 1 to "greet" has incompatible type "int"; expected "str"
|
||||||
|
|
||||||
|
Benefits of type hints:
|
||||||
|
✓ Catch bugs early (before runtime)
|
||||||
|
✓ Better IDE autocomplete
|
||||||
|
✓ Self-documenting code
|
||||||
|
✓ Easier refactoring
|
||||||
|
✓ Better code reviews
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 11. BEST PRACTICES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("--- Best Practices ---")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
1. Start with function signatures
|
||||||
|
- Hint parameters and return types first
|
||||||
|
|
||||||
|
2. Use Optional[X] for nullable values
|
||||||
|
- Not: x: str = None (wrong type!)
|
||||||
|
- Yes: x: Optional[str] = None
|
||||||
|
|
||||||
|
3. Prefer specific types over Any
|
||||||
|
- Any defeats the purpose of type hints
|
||||||
|
|
||||||
|
4. Use type aliases for complex types
|
||||||
|
- Makes code more readable
|
||||||
|
|
||||||
|
5. Enable strict mode in mypy
|
||||||
|
- mypy --strict your_script.py
|
||||||
|
|
||||||
|
6. Type hint public APIs first
|
||||||
|
- Internal code can be less strict
|
||||||
|
|
||||||
|
7. Use TypedDict for dictionaries with known structure
|
||||||
|
|
||||||
|
8. Consider using dataclasses for structured data
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Example: TypedDict and dataclass
|
||||||
|
from typing import TypedDict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
class UserDict(TypedDict):
|
||||||
|
"""User data as dictionary."""
|
||||||
|
name: str
|
||||||
|
age: int
|
||||||
|
email: Optional[str]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UserDataClass:
|
||||||
|
"""User data as dataclass."""
|
||||||
|
name: str
|
||||||
|
age: int
|
||||||
|
email: Optional[str] = None
|
||||||
|
|
||||||
|
# Both provide type safety for structured data
|
||||||
|
user_dict: UserDict = {"name": "Alice", "age": 25, "email": None}
|
||||||
|
user_obj = UserDataClass(name="Bob", age=30)
|
||||||
|
|
||||||
|
print(f"\nTypedDict: {user_dict}")
|
||||||
|
print(f"Dataclass: {user_obj}")
|
||||||
455
10_best_practices/03_virtual_envs.py
Normal file
455
10_best_practices/03_virtual_envs.py
Normal file
|
|
@ -0,0 +1,455 @@
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: 03_virtual_envs.py
|
||||||
|
Topic: Virtual Environments in Python
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
This file explains virtual environments in Python. Virtual environments are
|
||||||
|
isolated Python environments that allow you to manage dependencies separately
|
||||||
|
for each project, avoiding conflicts between different projects.
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- What are virtual environments
|
||||||
|
- Creating and activating venvs
|
||||||
|
- Managing dependencies with pip
|
||||||
|
- requirements.txt
|
||||||
|
- Best practices
|
||||||
|
|
||||||
|
Note: This is a reference/tutorial file - the commands are shown as examples.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 1. WHAT ARE VIRTUAL ENVIRONMENTS?
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("=== What Are Virtual Environments? ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
A virtual environment is an ISOLATED Python environment that:
|
||||||
|
|
||||||
|
✓ Has its own Python interpreter
|
||||||
|
✓ Has its own site-packages directory
|
||||||
|
✓ Doesn't affect global Python installation
|
||||||
|
✓ Allows different projects to have different dependencies
|
||||||
|
|
||||||
|
Why use virtual environments?
|
||||||
|
- Project A needs Django 3.2
|
||||||
|
- Project B needs Django 4.1
|
||||||
|
- Without venvs, you'd have conflicts!
|
||||||
|
|
||||||
|
With virtual environments, each project has its own Django version.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 2. CREATING A VIRTUAL ENVIRONMENT
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Creating a Virtual Environment ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
Using the built-in 'venv' module (Python 3.3+):
|
||||||
|
|
||||||
|
# Navigate to your project directory
|
||||||
|
cd my_project
|
||||||
|
|
||||||
|
# Create a virtual environment named 'venv'
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# Or name it something else
|
||||||
|
python -m venv .venv # Hidden folder (common convention)
|
||||||
|
python -m venv env # Another common name
|
||||||
|
python -m venv my_env # Custom name
|
||||||
|
|
||||||
|
This creates a folder with:
|
||||||
|
venv/
|
||||||
|
├── Include/ # C header files
|
||||||
|
├── Lib/ # Python packages
|
||||||
|
│ └── site-packages/
|
||||||
|
├── Scripts/ # Activation scripts (Windows)
|
||||||
|
│ ├── activate
|
||||||
|
│ ├── activate.bat
|
||||||
|
│ └── python.exe
|
||||||
|
└── pyvenv.cfg # Configuration file
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 3. ACTIVATING AND DEACTIVATING
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Activating and Deactivating ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
ACTIVATION (must do before using the venv):
|
||||||
|
|
||||||
|
Windows (Command Prompt):
|
||||||
|
venv\\Scripts\\activate.bat
|
||||||
|
|
||||||
|
Windows (PowerShell):
|
||||||
|
venv\\Scripts\\Activate.ps1
|
||||||
|
|
||||||
|
If you get execution policy error:
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
|
||||||
|
macOS/Linux:
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
When activated, your prompt changes:
|
||||||
|
(venv) C:\\my_project>
|
||||||
|
(venv) user@computer:~/my_project$
|
||||||
|
|
||||||
|
DEACTIVATION:
|
||||||
|
deactivate
|
||||||
|
|
||||||
|
After deactivation, prompt returns to normal.
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 4. INSTALLING PACKAGES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Installing Packages ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
After activating your venv, use pip to install packages:
|
||||||
|
|
||||||
|
# Install a package
|
||||||
|
pip install requests
|
||||||
|
|
||||||
|
# Install specific version
|
||||||
|
pip install requests==2.28.0
|
||||||
|
|
||||||
|
# Install minimum version
|
||||||
|
pip install 'requests>=2.25.0'
|
||||||
|
|
||||||
|
# Install multiple packages
|
||||||
|
pip install requests numpy pandas
|
||||||
|
|
||||||
|
# Install from requirements.txt
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Upgrade a package
|
||||||
|
pip install --upgrade requests
|
||||||
|
|
||||||
|
# Uninstall a package
|
||||||
|
pip uninstall requests
|
||||||
|
|
||||||
|
# List installed packages
|
||||||
|
pip list
|
||||||
|
|
||||||
|
# Show package info
|
||||||
|
pip show requests
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 5. REQUIREMENTS FILE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Requirements File ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
The requirements.txt file lists all project dependencies:
|
||||||
|
|
||||||
|
CREATING requirements.txt:
|
||||||
|
# Generate from current environment
|
||||||
|
pip freeze > requirements.txt
|
||||||
|
|
||||||
|
EXAMPLE requirements.txt:
|
||||||
|
# requirements.txt
|
||||||
|
requests==2.28.1
|
||||||
|
numpy>=1.21.0
|
||||||
|
pandas~=1.5.0
|
||||||
|
python-dotenv
|
||||||
|
|
||||||
|
VERSION SPECIFIERS:
|
||||||
|
package==1.0.0 # Exact version
|
||||||
|
package>=1.0.0 # Minimum version
|
||||||
|
package<=1.0.0 # Maximum version
|
||||||
|
package~=1.0.0 # Compatible version (>=1.0.0, <2.0.0)
|
||||||
|
package!=1.0.0 # Exclude version
|
||||||
|
package>=1.0,<2.0 # Version range
|
||||||
|
|
||||||
|
INSTALLING FROM requirements.txt:
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
BEST PRACTICE: Create different files for different environments:
|
||||||
|
requirements.txt # Production dependencies
|
||||||
|
requirements-dev.txt # Development dependencies
|
||||||
|
requirements-test.txt # Testing dependencies
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 6. VIRTUAL ENVIRONMENT ALTERNATIVES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Alternative Tools ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
1. venv (built-in)
|
||||||
|
- Simple, included with Python
|
||||||
|
- Good for basic needs
|
||||||
|
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
2. virtualenv
|
||||||
|
- More features than venv
|
||||||
|
- Faster environment creation
|
||||||
|
|
||||||
|
pip install virtualenv
|
||||||
|
virtualenv venv
|
||||||
|
|
||||||
|
3. pipenv
|
||||||
|
- Combines venv + pip
|
||||||
|
- Uses Pipfile instead of requirements.txt
|
||||||
|
- Automatic locking of dependencies
|
||||||
|
|
||||||
|
pip install pipenv
|
||||||
|
pipenv install requests
|
||||||
|
pipenv shell
|
||||||
|
|
||||||
|
4. poetry
|
||||||
|
- Modern dependency management
|
||||||
|
- Better dependency resolution
|
||||||
|
- Build and publish packages
|
||||||
|
|
||||||
|
pip install poetry
|
||||||
|
poetry new my_project
|
||||||
|
poetry add requests
|
||||||
|
|
||||||
|
5. conda
|
||||||
|
- Package manager + environment manager
|
||||||
|
- Great for data science (NumPy, SciPy pre-compiled)
|
||||||
|
|
||||||
|
conda create -n myenv python=3.10
|
||||||
|
conda activate myenv
|
||||||
|
conda install numpy
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 7. PROJECT STRUCTURE WITH VENV
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Project Structure ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
Recommended project structure:
|
||||||
|
|
||||||
|
my_project/
|
||||||
|
├── venv/ # Virtual environment (don't commit!)
|
||||||
|
├── src/
|
||||||
|
│ └── my_package/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── main.py
|
||||||
|
├── tests/
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── test_main.py
|
||||||
|
├── .gitignore # Include 'venv/' here!
|
||||||
|
├── requirements.txt
|
||||||
|
├── requirements-dev.txt
|
||||||
|
├── setup.py or pyproject.toml
|
||||||
|
└── README.md
|
||||||
|
|
||||||
|
.gitignore should include:
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
env/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 8. COMMON COMMANDS REFERENCE
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Quick Reference ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
╔══════════════════════════════════════════════════════════════════╗
|
||||||
|
║ VIRTUAL ENVIRONMENT COMMANDS ║
|
||||||
|
╠══════════════════════════════════════════════════════════════════╣
|
||||||
|
║ CREATE ║
|
||||||
|
║ python -m venv venv ║
|
||||||
|
║ ║
|
||||||
|
║ ACTIVATE ║
|
||||||
|
║ Windows: venv\\Scripts\\activate ║
|
||||||
|
║ Mac/Linux: source venv/bin/activate ║
|
||||||
|
║ ║
|
||||||
|
║ DEACTIVATE ║
|
||||||
|
║ deactivate ║
|
||||||
|
║ ║
|
||||||
|
║ INSTALL PACKAGES ║
|
||||||
|
║ pip install package_name ║
|
||||||
|
║ pip install -r requirements.txt ║
|
||||||
|
║ ║
|
||||||
|
║ EXPORT DEPENDENCIES ║
|
||||||
|
║ pip freeze > requirements.txt ║
|
||||||
|
║ ║
|
||||||
|
║ CHECK PYTHON LOCATION ║
|
||||||
|
║ Windows: where python ║
|
||||||
|
║ Mac/Linux: which python ║
|
||||||
|
║ ║
|
||||||
|
║ DELETE VENV (just remove the folder) ║
|
||||||
|
║ Windows: rmdir /s /q venv ║
|
||||||
|
║ Mac/Linux: rm -rf venv ║
|
||||||
|
╚══════════════════════════════════════════════════════════════════╝
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 9. BEST PRACTICES
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Best Practices ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
1. ALWAYS use virtual environments
|
||||||
|
- Even for small projects
|
||||||
|
- Prevents "works on my machine" problems
|
||||||
|
|
||||||
|
2. Don't commit venv to version control
|
||||||
|
- Add 'venv/' to .gitignore
|
||||||
|
- Share requirements.txt instead
|
||||||
|
|
||||||
|
3. Use meaningful venv names
|
||||||
|
- venv, .venv, or env are common
|
||||||
|
- Use .venv to hide the folder
|
||||||
|
|
||||||
|
4. Pin your versions in production
|
||||||
|
- Use pip freeze > requirements.txt
|
||||||
|
- Review and clean up before committing
|
||||||
|
|
||||||
|
5. Separate dev and production dependencies
|
||||||
|
- requirements.txt for production
|
||||||
|
- requirements-dev.txt for testing, linting, etc.
|
||||||
|
|
||||||
|
6. Document how to set up the environment
|
||||||
|
- Include setup instructions in README.md
|
||||||
|
|
||||||
|
7. Use pip-tools for better dependency management
|
||||||
|
- pip install pip-tools
|
||||||
|
- Create requirements.in with direct dependencies
|
||||||
|
- pip-compile requirements.in creates pinned file
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 10. TROUBLESHOOTING
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Troubleshooting ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
COMMON ISSUES AND SOLUTIONS:
|
||||||
|
|
||||||
|
1. "python not recognized" after activation
|
||||||
|
Solution: Use full path or reinstall Python with PATH option
|
||||||
|
|
||||||
|
2. PowerShell "execution policy" error
|
||||||
|
Solution:
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
|
||||||
|
3. "pip install" uses global pip
|
||||||
|
Solution: Make sure venv is activated first
|
||||||
|
Check: python -c "import sys; print(sys.prefix)"
|
||||||
|
|
||||||
|
4. Can't delete venv folder (files in use)
|
||||||
|
Solution:
|
||||||
|
- Deactivate first
|
||||||
|
- Close any terminals/IDEs using it
|
||||||
|
- Restart computer if needed
|
||||||
|
|
||||||
|
5. requirements.txt has too many packages
|
||||||
|
Solution: Use pipreqs to generate minimal requirements
|
||||||
|
pip install pipreqs
|
||||||
|
pipreqs /path/to/project
|
||||||
|
|
||||||
|
6. Package conflicts
|
||||||
|
Solution: Create fresh venv and install carefully
|
||||||
|
Or use pip-compile for dependency resolution
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 11. IDE INTEGRATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== IDE Integration ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
VS CODE:
|
||||||
|
1. Open project folder
|
||||||
|
2. Select Python Interpreter (Ctrl+Shift+P)
|
||||||
|
3. Choose "Python: Select Interpreter"
|
||||||
|
4. Select the venv Python (./venv/Scripts/python.exe)
|
||||||
|
5. New terminals auto-activate venv
|
||||||
|
|
||||||
|
PYCHARM:
|
||||||
|
1. File > Settings > Project > Python Interpreter
|
||||||
|
2. Click gear icon > Add
|
||||||
|
3. Select "Existing environment" or create new
|
||||||
|
4. Point to venv/Scripts/python.exe
|
||||||
|
|
||||||
|
JUPYTER NOTEBOOK:
|
||||||
|
1. Activate venv
|
||||||
|
2. Install: pip install ipykernel
|
||||||
|
3. Register: python -m ipykernel install --user --name=myenv
|
||||||
|
4. Select kernel in Jupyter
|
||||||
|
""")
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 12. EXAMPLE WORKFLOW
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
print("\n=== Example Workflow ===")
|
||||||
|
|
||||||
|
print("""
|
||||||
|
STARTING A NEW PROJECT:
|
||||||
|
|
||||||
|
# 1. Create project folder
|
||||||
|
mkdir my_awesome_project
|
||||||
|
cd my_awesome_project
|
||||||
|
|
||||||
|
# 2. Create virtual environment
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# 3. Activate it
|
||||||
|
venv\\Scripts\\activate # Windows
|
||||||
|
source venv/bin/activate # Mac/Linux
|
||||||
|
|
||||||
|
# 4. Upgrade pip (good practice)
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
|
||||||
|
# 5. Install packages
|
||||||
|
pip install requests numpy pandas
|
||||||
|
|
||||||
|
# 6. Create requirements.txt
|
||||||
|
pip freeze > requirements.txt
|
||||||
|
|
||||||
|
# 7. Create .gitignore
|
||||||
|
echo "venv/" > .gitignore
|
||||||
|
echo "__pycache__/" >> .gitignore
|
||||||
|
|
||||||
|
# 8. Start coding!
|
||||||
|
code .
|
||||||
|
|
||||||
|
JOINING AN EXISTING PROJECT:
|
||||||
|
|
||||||
|
# 1. Clone the repository
|
||||||
|
git clone https://github.com/user/project.git
|
||||||
|
cd project
|
||||||
|
|
||||||
|
# 2. Create virtual environment
|
||||||
|
python -m venv venv
|
||||||
|
|
||||||
|
# 3. Activate it
|
||||||
|
venv\\Scripts\\activate # Windows
|
||||||
|
|
||||||
|
# 4. Install dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# 5. Start working!
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("Virtual environments are essential for Python development!")
|
||||||
|
print("Always use them to keep your projects isolated and reproducible.")
|
||||||
|
print("=" * 60)
|
||||||
130
README.md
Normal file
130
README.md
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Python Review - Complete Tutorial
|
||||||
|
|
||||||
|
A comprehensive Python tutorial covering everything from basics to advanced topics.
|
||||||
|
Each file is self-contained with detailed explanations and executable examples.
|
||||||
|
|
||||||
|
## 📚 Topics Covered
|
||||||
|
|
||||||
|
### 01. Basics
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_print.py` | Print function, formatting, escape characters |
|
||||||
|
| `02_comments.py` | Single-line, multi-line comments, docstrings |
|
||||||
|
| `03_variables.py` | Variable assignment, naming, multiple assignment |
|
||||||
|
| `04_data_types.py` | Numbers, strings, booleans, type conversion |
|
||||||
|
|
||||||
|
### 02. Control Flow
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_if_else.py` | Conditionals, comparison operators, logical operators |
|
||||||
|
| `02_elif.py` | Multiple conditions, grading systems, ranges |
|
||||||
|
| `03_match_case.py` | Pattern matching (Python 3.10+), guards, unpacking |
|
||||||
|
|
||||||
|
### 03. Loops
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_for_loop.py` | For loops, range(), enumerate(), zip() |
|
||||||
|
| `02_while_loop.py` | While loops, counters, sentinels, infinite loops |
|
||||||
|
| `03_break_continue.py` | Loop control, break, continue, pass, for-else |
|
||||||
|
|
||||||
|
### 04. Data Structures
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_lists.py` | Lists, indexing, slicing, methods, comprehensions |
|
||||||
|
| `02_tuples.py` | Tuples, immutability, unpacking, named tuples |
|
||||||
|
| `03_sets.py` | Sets, operations, frozen sets, practical uses |
|
||||||
|
| `04_dictionaries.py` | Dictionaries, methods, nesting, comprehensions |
|
||||||
|
|
||||||
|
### 05. Functions
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_function_basics.py` | Defining functions, scope, docstrings, nested functions |
|
||||||
|
| `02_arguments.py` | Positional, keyword, *args, **kwargs, type hints |
|
||||||
|
| `03_return_values.py` | Returns, multiple returns, early returns, generators |
|
||||||
|
| `04_lambda_functions.py` | Lambda, map, filter, reduce, functional patterns |
|
||||||
|
|
||||||
|
### 06. Modules & Packages
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_imports.py` | Import patterns, standard library, module organization |
|
||||||
|
| `02_custom_modules.py` | Creating modules, packages, __init__.py, __name__ |
|
||||||
|
|
||||||
|
### 07. Error Handling
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_try_except.py` | try/except/else/finally, catching exceptions |
|
||||||
|
| `02_custom_exceptions.py` | Creating exceptions, hierarchy, best practices |
|
||||||
|
|
||||||
|
### 08. Object-Oriented Programming
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_classes_objects.py` | Classes, objects, attributes, methods, self |
|
||||||
|
| `02_init_methods.py` | Constructors, properties, classmethods |
|
||||||
|
| `03_inheritance.py` | Single/multiple inheritance, super(), MRO, ABC |
|
||||||
|
| `04_polymorphism.py` | Duck typing, operator overloading, protocols |
|
||||||
|
|
||||||
|
### 09. Advanced Python
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_list_comprehensions.py` | List, dict, set comprehensions, generators |
|
||||||
|
| `02_generators.py` | Yield, iterators, memory efficiency, pipelines |
|
||||||
|
| `03_decorators.py` | Simple/parameterized decorators, functools |
|
||||||
|
| `04_context_managers.py` | with statement, __enter__/__exit__, contextlib |
|
||||||
|
|
||||||
|
### 10. Best Practices
|
||||||
|
| File | Topic |
|
||||||
|
|------|-------|
|
||||||
|
| `01_pep8.py` | Python style guide, naming, formatting |
|
||||||
|
| `02_type_hinting.py` | Type annotations, typing module, generics |
|
||||||
|
| `03_virtual_envs.py` | venv, pip, requirements.txt, project setup |
|
||||||
|
|
||||||
|
## 🚀 How to Use
|
||||||
|
|
||||||
|
1. **Start from basics**: Begin with `01_basics` and work your way up
|
||||||
|
2. **Run the files**: Each file is executable: `python 01_print.py`
|
||||||
|
3. **Read the comments**: Each section is thoroughly explained
|
||||||
|
4. **Experiment**: Modify the code and observe the results
|
||||||
|
|
||||||
|
## 🛠️ Prerequisites
|
||||||
|
|
||||||
|
- Python 3.10+ recommended (for match/case support)
|
||||||
|
- Python 3.8+ minimum (for most features)
|
||||||
|
|
||||||
|
## 📝 File Structure
|
||||||
|
|
||||||
|
Each file follows this structure:
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
================================================================================
|
||||||
|
File: filename.py
|
||||||
|
Topic: Topic Name
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Description of what the file covers...
|
||||||
|
|
||||||
|
Key Concepts:
|
||||||
|
- Concept 1
|
||||||
|
- Concept 2
|
||||||
|
- ...
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Section 1: Basic Example
|
||||||
|
# ... code with inline comments ...
|
||||||
|
|
||||||
|
# Section 2: Advanced Example
|
||||||
|
# ... more code ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ Learning Path
|
||||||
|
|
||||||
|
```
|
||||||
|
Beginner: 01_basics → 02_control_flow → 03_loops
|
||||||
|
Intermediate: 04_data_structures → 05_functions → 06_modules
|
||||||
|
Advanced: 07_error_handling → 08_oop → 09_advanced
|
||||||
|
Professional: 10_best_practices
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Happy Learning! 🐍*
|
||||||
Loading…
Reference in New Issue
Block a user