What is the primary keyword used to define an asynchronous function (coroutine) in Python's asyncio?
Anonymous Quiz
0%
def async
0%
async def
0%
await def
0%
coroutine def
Here's your Telegram post for Day 29!
Hello Python learners! 👋 It's Day 29, and we're taking a leap into the world of data persistence! 💾 Today, we'll explore **Basic Database Interaction** using Python's built-in `sqlite3` module. SQLite is a lightweight, file-based database perfect for local storage and learning. Learn how to store, retrieve, and manage your data beyond just variables! Let's get started! ✨🚀
```python
import sqlite3
import os
# Define database file name
DB_FILE = 'my_app.db'
# --- Optional: Uncomment the lines below to start with a fresh database each time ---
# if os.path.exists(DB_FILE):
# os.remove(DB_FILE)
# print(f"Removed existing {DB_FILE} for a clean start.")
# -----------------------------------------------------------------------------------
def main():
try:
# Connect to a SQLite database (it will create the file if it doesn't exist)
# Using 'with' statement ensures the connection is properly closed
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
# 1. Create a table if it doesn't already exist
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
''')
print(f"Table 'users' created or already exists in {DB_FILE}.")
# 2. Insert some data
users_to_insert = [
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com')
]
print("nAttempting to insert data...")
try:
# executemany is efficient for inserting multiple rows
cursor.executemany("INSERT INTO users (name, email) VALUES (?, ?)", users_to_insert)
conn.commit() # Commit changes to save them to the database
print("Successfully inserted new users.")
except sqlite3.IntegrityError:
# This error occurs if a UNIQUE constraint is violated (e.g., trying to insert duplicate email)
print("One or more users already exist (duplicate email). Skipping new inserts.")
# No need to rollback here as we just skipped the conflicting inserts
# 3. Select and retrieve data
print("nRetrieving all users:")
cursor.execute("SELECT id, name, email FROM users")
rows = cursor.fetchall() # Fetch all results
if rows:
for row in rows:
print(f"ID: {row[0]}, Name: {row[1]}, Email: {row[2]}")
else:
print("No users found in the database.")
except sqlite3.Error as e:
print(f"A database error occurred: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
main()
```
Output example:
```
Table 'users' created or already exists in my_app.db.
Attempting to insert data...
Successfully inserted new users.
Retrieving all users:
ID: 1, Name: Alice, Email: alice@example.com
ID: 2, Name: Bob, Email: bob@example.com
ID: 3, Name: Charlie, Email: charlie@example.com
```
(If you run the script a second time without deleting `my_app.db`, you'd see:
`One or more users already exist (duplicate email). Skipping new inserts.`)
Challenge:
Modify the script to **update** 'Bob's email address to `robert@example.com`. Remember to use the `UPDATE` SQL command and `conn.commit()`! ✍️
---
Here's a quiz question related to today's lesson:
Hello Python learners! 👋 It's Day 29, and we're taking a leap into the world of data persistence! 💾 Today, we'll explore **Basic Database Interaction** using Python's built-in `sqlite3` module. SQLite is a lightweight, file-based database perfect for local storage and learning. Learn how to store, retrieve, and manage your data beyond just variables! Let's get started! ✨🚀
```python
import sqlite3
import os
# Define database file name
DB_FILE = 'my_app.db'
# --- Optional: Uncomment the lines below to start with a fresh database each time ---
# if os.path.exists(DB_FILE):
# os.remove(DB_FILE)
# print(f"Removed existing {DB_FILE} for a clean start.")
# -----------------------------------------------------------------------------------
def main():
try:
# Connect to a SQLite database (it will create the file if it doesn't exist)
# Using 'with' statement ensures the connection is properly closed
with sqlite3.connect(DB_FILE) as conn:
cursor = conn.cursor()
# 1. Create a table if it doesn't already exist
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
''')
print(f"Table 'users' created or already exists in {DB_FILE}.")
# 2. Insert some data
users_to_insert = [
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com'),
('Charlie', 'charlie@example.com')
]
print("nAttempting to insert data...")
try:
# executemany is efficient for inserting multiple rows
cursor.executemany("INSERT INTO users (name, email) VALUES (?, ?)", users_to_insert)
conn.commit() # Commit changes to save them to the database
print("Successfully inserted new users.")
except sqlite3.IntegrityError:
# This error occurs if a UNIQUE constraint is violated (e.g., trying to insert duplicate email)
print("One or more users already exist (duplicate email). Skipping new inserts.")
# No need to rollback here as we just skipped the conflicting inserts
# 3. Select and retrieve data
print("nRetrieving all users:")
cursor.execute("SELECT id, name, email FROM users")
rows = cursor.fetchall() # Fetch all results
if rows:
for row in rows:
print(f"ID: {row[0]}, Name: {row[1]}, Email: {row[2]}")
else:
print("No users found in the database.")
except sqlite3.Error as e:
print(f"A database error occurred: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
if __name__ == "__main__":
main()
```
Output example:
```
Table 'users' created or already exists in my_app.db.
Attempting to insert data...
Successfully inserted new users.
Retrieving all users:
ID: 1, Name: Alice, Email: alice@example.com
ID: 2, Name: Bob, Email: bob@example.com
ID: 3, Name: Charlie, Email: charlie@example.com
```
(If you run the script a second time without deleting `my_app.db`, you'd see:
`One or more users already exist (duplicate email). Skipping new inserts.`)
Challenge:
Modify the script to **update** 'Bob's email address to `robert@example.com`. Remember to use the `UPDATE` SQL command and `conn.commit()`! ✍️
---
Here's a quiz question related to today's lesson:
Which built-in Python module is primarily used for interacting with SQLite databases?
Anonymous Quiz
0%
mysql.connector
0%
sqlite3
0%
psycopg2
0%
pymongo
🎉 *Day 30: Final Project Idea and Review!* 🎉
Wow, we've made it to Day 30! 🥳 What an incredible journey! Today isn't about new complex topics, but about consolidating everything we've learned and dreaming big for your final project. It's time to brainstorm, review key concepts, and think about how you can apply your newfound Python superpowers. Your final project is your chance to shine and integrate multiple skills! ✨
```python
def suggest_final_project_idea(days_completed):
print(f"🌟 Congrats on completing {days_completed} days of Python!")
print("nIt's time for your final project! Let's review some key areas we covered:")
# List of topics typically covered in a beginner series
topics_covered = [
"Variables & Data Types",
"Control Flow (if/else, loops)",
"Functions & Modularity",
"Lists, Dictionaries & Tuples",
"File I/O (reading/writing files)",
"Error Handling (try-except)",
"Object-Oriented Programming (Classes) - if covered by now",
"Working with Modules (math, random, os, sys, etc.)"
]
for i, topic in enumerate(topics_covered):
print(f" {i+1}. {topic}")
print("n💡 Consider a project that integrates several of these concepts:")
if days_completed >= 25: # Assuming more advanced topics by day 25-30
print(" Project Idea: A simple 'To-Do List' application with file persistence.")
print(" - Utilizes: Lists, Functions, File I/O, Classes (for Task objects), Error Handling.")
print(" Project Idea: A 'Contact Book' application.")
print(" - Utilizes: Dictionaries, Functions, File I/O, User Input validation.")
else:
print(" Project Idea: A 'Guess the Number' game with score tracking.")
print(" - Utilizes: Loops, Conditionals, Random module, User Input, basic File I/O.")
print(" Project Idea: A simple 'Calculator' that handles multiple operations.")
print(" - Utilizes: Functions, Conditionals, User Input validation.")
print("nRemember: Start small, make it work, then make it better! Good luck with your project! 🚀")
# Let's see some ideas for Day 30!
suggest_final_project_idea(30)
```
Output example:
```
🌟 Congrats on completing 30 days of Python!
It's time for your final project! Let's review some key areas we covered:
1. Variables & Data Types
2. Control Flow (if/else, loops)
3. Functions & Modularity
4. Lists, Dictionaries & Tuples
5. File I/O (reading/writing files)
6. Error Handling (try-except)
7. Object-Oriented Programming (Classes) - if covered by now
8. Working with Modules (math, random, os, sys, etc.)
💡 Consider a project that integrates several of these concepts:
Project Idea: A simple 'To-Do List' application with file persistence.
- Utilizes: Lists, Functions, File I/O, Classes (for Task objects), Error Handling.
Project Idea: A 'Contact Book' application.
- Utilizes: Dictionaries, Functions, File I/O, User Input validation.
Remember: Start small, make it work, then make it better! Good luck with your project! 🚀
```
**Small challenge:**
Modify the `suggest_final_project_idea` function to include an idea for a project that uses an external library (e.g., `requests` for web APIs, `turtle` for graphics, or `csv` for data handling) if you've explored them, or add another project idea that focuses on string manipulation! Share your ideas! 👇
---
Wow, we've made it to Day 30! 🥳 What an incredible journey! Today isn't about new complex topics, but about consolidating everything we've learned and dreaming big for your final project. It's time to brainstorm, review key concepts, and think about how you can apply your newfound Python superpowers. Your final project is your chance to shine and integrate multiple skills! ✨
```python
def suggest_final_project_idea(days_completed):
print(f"🌟 Congrats on completing {days_completed} days of Python!")
print("nIt's time for your final project! Let's review some key areas we covered:")
# List of topics typically covered in a beginner series
topics_covered = [
"Variables & Data Types",
"Control Flow (if/else, loops)",
"Functions & Modularity",
"Lists, Dictionaries & Tuples",
"File I/O (reading/writing files)",
"Error Handling (try-except)",
"Object-Oriented Programming (Classes) - if covered by now",
"Working with Modules (math, random, os, sys, etc.)"
]
for i, topic in enumerate(topics_covered):
print(f" {i+1}. {topic}")
print("n💡 Consider a project that integrates several of these concepts:")
if days_completed >= 25: # Assuming more advanced topics by day 25-30
print(" Project Idea: A simple 'To-Do List' application with file persistence.")
print(" - Utilizes: Lists, Functions, File I/O, Classes (for Task objects), Error Handling.")
print(" Project Idea: A 'Contact Book' application.")
print(" - Utilizes: Dictionaries, Functions, File I/O, User Input validation.")
else:
print(" Project Idea: A 'Guess the Number' game with score tracking.")
print(" - Utilizes: Loops, Conditionals, Random module, User Input, basic File I/O.")
print(" Project Idea: A simple 'Calculator' that handles multiple operations.")
print(" - Utilizes: Functions, Conditionals, User Input validation.")
print("nRemember: Start small, make it work, then make it better! Good luck with your project! 🚀")
# Let's see some ideas for Day 30!
suggest_final_project_idea(30)
```
Output example:
```
🌟 Congrats on completing 30 days of Python!
It's time for your final project! Let's review some key areas we covered:
1. Variables & Data Types
2. Control Flow (if/else, loops)
3. Functions & Modularity
4. Lists, Dictionaries & Tuples
5. File I/O (reading/writing files)
6. Error Handling (try-except)
7. Object-Oriented Programming (Classes) - if covered by now
8. Working with Modules (math, random, os, sys, etc.)
💡 Consider a project that integrates several of these concepts:
Project Idea: A simple 'To-Do List' application with file persistence.
- Utilizes: Lists, Functions, File I/O, Classes (for Task objects), Error Handling.
Project Idea: A 'Contact Book' application.
- Utilizes: Dictionaries, Functions, File I/O, User Input validation.
Remember: Start small, make it work, then make it better! Good luck with your project! 🚀
```
**Small challenge:**
Modify the `suggest_final_project_idea` function to include an idea for a project that uses an external library (e.g., `requests` for web APIs, `turtle` for graphics, or `csv` for data handling) if you've explored them, or add another project idea that focuses on string manipulation! Share your ideas! 👇
---
When starting a final project after a learning series, which of the following is generally the BEST first step?
Anonymous Quiz
0%
A. Write all the code immediately without planning.
100%
B. Pick the most complex and ambitious idea you can think of.
0%
C. Brainstorm multiple ideas, then identify key features and concepts each idea would require.
0%
D. Skip planning and just start coding, fixing issues as they arise.
Get ready for Day 31 of our Python journey! 🚀 Today, we're diving deep into one of Python's most powerful, yet often misunderstood, internal mechanisms: **The Descriptor Protocol**! 🕵️♂️
Descriptors are the magic behind `property`, `classmethod`, and `staticmethod`. They allow you to define custom logic for attribute access (`__get__`, `__set__`, `__delete__`) on your objects, enabling reusable, intelligent attributes and elegant object models. Master them to truly understand Python's object system and build highly robust APIs! ✨
```python
class TypeChecked:
"""A descriptor that enforces type checking for an attribute."""
def __init__(self, expected_type):
self._expected_type = expected_type
self._name = None # This will store the attribute name
def __set_name__(self, owner, name):
"""Called by the owner class to set the attribute's name."""
self._name = name
def __get__(self, instance, owner):
"""Called when the attribute is accessed (e.g., obj.attr)."""
if instance is None:
return self # Accessing through class (e.g., MyClass.attr)
if self._name not in instance.__dict__:
raise AttributeError(f"'{self._name}' has not been set.")
return instance.__dict__[self._name]
def __set__(self, instance, value):
"""Called when the attribute is assigned a value (e.g., obj.attr = value)."""
if not isinstance(value, self._expected_type):
raise TypeError(
f"Attribute '{self._name}' must be of type "
f"{self._expected_type.__name__}, got {type(value).__name__}"
)
instance.__dict__[self._name] = value
def __delete__(self, instance):
"""Called when the attribute is deleted (e.g., del obj.attr)."""
if self._name in instance.__dict__:
del instance.__dict__[self._name]
else:
raise AttributeError(f"'{self._name}' is not set and cannot be deleted.")
class User:
username = TypeChecked(str)
age = TypeChecked(int)
email = TypeChecked(str)
def __init__(self, username, age, email):
self.username = username
self.age = age
self.email = email
# --- Usage Examples ---
user1 = User("coding_wiz", 28, "wiz@example.com")
print(f"User: {user1.username}, Age: {user1.age}, Email: {user1.email}")
try:
user1.age = "twenty-nine" # This will trigger the descriptor's __set__
except TypeError as e:
print(f"nCaught Error: {e}")
user1.age = 29 # This is valid
print(f"Updated Age: {user1.age}")
try:
user1.username = 12345 # Another invalid assignment
except TypeError as e:
print(f"Caught Error: {e}")
del user1.email # Deleting an attribute
try:
print(user1.email) # Accessing a deleted attribute
except AttributeError as e:
print(f"Caught Error: {e}")
```
Output example:
```
User: coding_wiz, Age: 28, Email: wiz@example.com
Caught Error: Attribute 'age' must be of type int, got str
Updated Age: 29
Caught Error: Attribute 'username' must be of type str, got int
Caught Error: 'email' has not been set.
```
### Small Challenge:
Modify the `TypeChecked` descriptor to include an optional `nullable` parameter (defaulting to `False`). If `nullable` is `True`, the attribute should allow `None` as a valid value in addition to the `expected_type`.
---
Descriptors are the magic behind `property`, `classmethod`, and `staticmethod`. They allow you to define custom logic for attribute access (`__get__`, `__set__`, `__delete__`) on your objects, enabling reusable, intelligent attributes and elegant object models. Master them to truly understand Python's object system and build highly robust APIs! ✨
```python
class TypeChecked:
"""A descriptor that enforces type checking for an attribute."""
def __init__(self, expected_type):
self._expected_type = expected_type
self._name = None # This will store the attribute name
def __set_name__(self, owner, name):
"""Called by the owner class to set the attribute's name."""
self._name = name
def __get__(self, instance, owner):
"""Called when the attribute is accessed (e.g., obj.attr)."""
if instance is None:
return self # Accessing through class (e.g., MyClass.attr)
if self._name not in instance.__dict__:
raise AttributeError(f"'{self._name}' has not been set.")
return instance.__dict__[self._name]
def __set__(self, instance, value):
"""Called when the attribute is assigned a value (e.g., obj.attr = value)."""
if not isinstance(value, self._expected_type):
raise TypeError(
f"Attribute '{self._name}' must be of type "
f"{self._expected_type.__name__}, got {type(value).__name__}"
)
instance.__dict__[self._name] = value
def __delete__(self, instance):
"""Called when the attribute is deleted (e.g., del obj.attr)."""
if self._name in instance.__dict__:
del instance.__dict__[self._name]
else:
raise AttributeError(f"'{self._name}' is not set and cannot be deleted.")
class User:
username = TypeChecked(str)
age = TypeChecked(int)
email = TypeChecked(str)
def __init__(self, username, age, email):
self.username = username
self.age = age
self.email = email
# --- Usage Examples ---
user1 = User("coding_wiz", 28, "wiz@example.com")
print(f"User: {user1.username}, Age: {user1.age}, Email: {user1.email}")
try:
user1.age = "twenty-nine" # This will trigger the descriptor's __set__
except TypeError as e:
print(f"nCaught Error: {e}")
user1.age = 29 # This is valid
print(f"Updated Age: {user1.age}")
try:
user1.username = 12345 # Another invalid assignment
except TypeError as e:
print(f"Caught Error: {e}")
del user1.email # Deleting an attribute
try:
print(user1.email) # Accessing a deleted attribute
except AttributeError as e:
print(f"Caught Error: {e}")
```
Output example:
```
User: coding_wiz, Age: 28, Email: wiz@example.com
Caught Error: Attribute 'age' must be of type int, got str
Updated Age: 29
Caught Error: Attribute 'username' must be of type str, got int
Caught Error: 'email' has not been set.
```
### Small Challenge:
Modify the `TypeChecked` descriptor to include an optional `nullable` parameter (defaulting to `False`). If `nullable` is `True`, the attribute should allow `None` as a valid value in addition to the `expected_type`.
---
Which special method in a descriptor is responsible for handling attribute assignment (e.g., `obj.attr = value`)?
Anonymous Quiz
0%
__get__
0%
__set__
0%
__delete__
0%
__set_name__
🐍✨ **Day 33: The Type System Strikes Back!** ✨🐍
Ready to supercharge your Python types? Today, we're diving deep beyond basic type hints into the advanced realms of generics, protocol-oriented design, and robust runtime type verification. Learn how to build more flexible, maintainable, and error-proof code by leveraging Python's powerful type system to define clear interfaces and validate data where it matters most – at runtime! Stop bugs before they even begin to crawl. Let's make our Python smarter! 🚀
```python
from typing import Protocol, TypeVar, Generic, List
from pydantic import BaseModel, ValidationError, Field
# 1. Protocol-Oriented Design
class Printable(Protocol):
"""A protocol for objects that can print themselves."""
def print_self(self) -> None:
...
class MyData:
def __init__(self, value: str):
self.value = value
def print_self(self) -> None:
print(f"MyData: {self.value}")
def process_printable(item: Printable) -> None:
item.print_self()
# 2. Advanced Generics
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, content: T):
self.content = content
def get_content(self) -> T:
return self.content
# 3. Runtime Type Verification with Pydantic
class UserProfile(BaseModel):
id: int = Field(..., gt=0, description="Unique user ID, must be positive")
username: str
email: str
is_active: bool = True
tags: List[str] = []
print("--- Protocol Example ---")
data_item = MyData("Hello Advanced Types!")
process_printable(data_item)
print("n--- Generics Example ---")
int_box = Box[int](123)
str_box = Box[str]("Python is awesome")
print(f"Int Box Content: {int_box.get_content()}")
print(f"Str Box Content: {str_box.get_content()}")
print("n--- Pydantic Runtime Validation ---")
valid_user_data = {
"id": 101,
"username": "type_master",
"email": "master@example.com"
}
try:
user = UserProfile(**valid_user_data)
print(f"Valid User: {user.model_dump_json(indent=2)}")
except ValidationError as e:
print(f"Validation Error: {e}")
invalid_user_data = {
"id": 0, # Should fail (gt=0)
"username": "buggy_user",
"email": "invalid" # Not a valid email format for Pydantic (though not explicitly set as EmailStr here, it might infer)
}
try:
user = UserProfile(**invalid_user_data)
print(f"Invalid User: {user.model_dump_json(indent=2)}")
except ValidationError as e:
print(f"Validation Error encountered:n{e.json(indent=2)}")
```
**Output example:**
```
--- Protocol Example ---
MyData: Hello Advanced Types!
--- Generics Example ---
Int Box Content: 123
Str Box Content: Python is awesome
--- Pydantic Runtime Validation ---
Valid User: {
"id": 101,
"username": "type_master",
"email": "master@example.com",
"is_active": true,
"tags": []
}
Validation Error encountered:
[
{
"type": "greater_than",
"loc": [
"id"
],
"msg": "Input should be greater than 0",
"input": 0,
"ctx": {
"gt": 0
},
"url": "https://errors.pydantic.dev/2.5/v/greater_than"
}
]
```
---
💡 **Small Challenge:**
Modify the `UserProfile` Pydantic model to explicitly use `EmailStr` for the `email` field and add a custom validator that ensures the `username` is not "admin". Observe how the validation errors change!
---
Ready to supercharge your Python types? Today, we're diving deep beyond basic type hints into the advanced realms of generics, protocol-oriented design, and robust runtime type verification. Learn how to build more flexible, maintainable, and error-proof code by leveraging Python's powerful type system to define clear interfaces and validate data where it matters most – at runtime! Stop bugs before they even begin to crawl. Let's make our Python smarter! 🚀
```python
from typing import Protocol, TypeVar, Generic, List
from pydantic import BaseModel, ValidationError, Field
# 1. Protocol-Oriented Design
class Printable(Protocol):
"""A protocol for objects that can print themselves."""
def print_self(self) -> None:
...
class MyData:
def __init__(self, value: str):
self.value = value
def print_self(self) -> None:
print(f"MyData: {self.value}")
def process_printable(item: Printable) -> None:
item.print_self()
# 2. Advanced Generics
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, content: T):
self.content = content
def get_content(self) -> T:
return self.content
# 3. Runtime Type Verification with Pydantic
class UserProfile(BaseModel):
id: int = Field(..., gt=0, description="Unique user ID, must be positive")
username: str
email: str
is_active: bool = True
tags: List[str] = []
print("--- Protocol Example ---")
data_item = MyData("Hello Advanced Types!")
process_printable(data_item)
print("n--- Generics Example ---")
int_box = Box[int](123)
str_box = Box[str]("Python is awesome")
print(f"Int Box Content: {int_box.get_content()}")
print(f"Str Box Content: {str_box.get_content()}")
print("n--- Pydantic Runtime Validation ---")
valid_user_data = {
"id": 101,
"username": "type_master",
"email": "master@example.com"
}
try:
user = UserProfile(**valid_user_data)
print(f"Valid User: {user.model_dump_json(indent=2)}")
except ValidationError as e:
print(f"Validation Error: {e}")
invalid_user_data = {
"id": 0, # Should fail (gt=0)
"username": "buggy_user",
"email": "invalid" # Not a valid email format for Pydantic (though not explicitly set as EmailStr here, it might infer)
}
try:
user = UserProfile(**invalid_user_data)
print(f"Invalid User: {user.model_dump_json(indent=2)}")
except ValidationError as e:
print(f"Validation Error encountered:n{e.json(indent=2)}")
```
**Output example:**
```
--- Protocol Example ---
MyData: Hello Advanced Types!
--- Generics Example ---
Int Box Content: 123
Str Box Content: Python is awesome
--- Pydantic Runtime Validation ---
Valid User: {
"id": 101,
"username": "type_master",
"email": "master@example.com",
"is_active": true,
"tags": []
}
Validation Error encountered:
[
{
"type": "greater_than",
"loc": [
"id"
],
"msg": "Input should be greater than 0",
"input": 0,
"ctx": {
"gt": 0
},
"url": "https://errors.pydantic.dev/2.5/v/greater_than"
}
]
```
---
💡 **Small Challenge:**
Modify the `UserProfile` Pydantic model to explicitly use `EmailStr` for the `email` field and add a custom validator that ensures the `username` is not "admin". Observe how the validation errors change!
---
Pydantic Docs
Validation errors
Here's your Telegram post for Day 34!
---
Hey Pythonistas! 👋 Day 34 is here, and we're diving deep into the very fabric of Python code itself! 🤯 Ever wondered how linters, optimizers, or even new language features are built? The secret often lies in Python's Abstract Syntax Tree (AST)! 🌳
Today, we'll learn to *programmatically rewrite our own code* by manipulating its AST. Get ready to sculpt Python at its core – this isn't just advanced; it's truly transformative. Let's peel back the layers! 🛠️
```python
import ast
import inspect
# Original Python code as a string
original_code_str = """
def calculate_square(x):
result = x * x
return result
def greet(name):
print(f"Hello, {name}!")
"""
class FunctionLoggerTransformer(ast.NodeTransformer):
"""
A transformer that adds a print statement at the beginning
of every function definition to log entry.
"""
def visit_FunctionDef(self, node):
# Create a new print statement node: print(f"Entering function '{node.name}'...")
# This constructs an AST node for: print("Entering function 'func_name'...")
log_message = ast.Constant(value=f"Entering function '{node.name}'...")
log_call = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[log_message],
keywords=[]
)
)
# Insert the log_call at the beginning of the function's body
node.body.insert(0, log_call)
# Important: Call generic_visit to ensure child nodes are also visited
# in case there are other transformations to apply within the function.
return self.generic_visit(node)
# 1. Parse the original code into an Abstract Syntax Tree (AST)
tree = ast.parse(original_code_str)
# 2. Apply the transformation using our custom NodeTransformer
transformer = FunctionLoggerTransformer()
new_tree = transformer.visit(tree)
# 3. Fix missing line numbers and other metadata in the new_tree.
# This is crucial for ast.unparse to work correctly, especially on modified nodes.
ast.fix_missing_locations(new_tree)
# 4. Unparse the modified AST back into Python source code (requires Python 3.9+)
transformed_code_str = ast.unparse(new_tree)
print("--- Original Code ---")
print(original_code_str)
print("n--- Transformed Code ---")
print(transformed_code_str)
# To actually execute the transformed code, you could use exec():
# print("n--- Executing Transformed Code ---")
# exec(transformed_code_str)
# calculate_square(10)
# greet("AST Explorer")
```
Output example:
```
--- Original Code ---
def calculate_square(x):
result = x * x
return result
def greet(name):
print(f"Hello, {name}!")
--- Transformed Code ---
def calculate_square(x):
print(f"Entering function 'calculate_square'...")
result = x * x
return result
def greet(name):
print(f"Entering function 'greet'...")
print(f"Hello, AST Explorer!")
```
**Small Challenge:**
Your turn! Can you modify the `FunctionLoggerTransformer` to rename the variable `result` to `squared_value` *only* within the `calculate_square` function? 🤔 (Hint: you'll need to check the function name in `visit_FunctionDef` and then potentially define a new transformer or specifically handle `ast.Name` nodes within that scope.)
---
---
Hey Pythonistas! 👋 Day 34 is here, and we're diving deep into the very fabric of Python code itself! 🤯 Ever wondered how linters, optimizers, or even new language features are built? The secret often lies in Python's Abstract Syntax Tree (AST)! 🌳
Today, we'll learn to *programmatically rewrite our own code* by manipulating its AST. Get ready to sculpt Python at its core – this isn't just advanced; it's truly transformative. Let's peel back the layers! 🛠️
```python
import ast
import inspect
# Original Python code as a string
original_code_str = """
def calculate_square(x):
result = x * x
return result
def greet(name):
print(f"Hello, {name}!")
"""
class FunctionLoggerTransformer(ast.NodeTransformer):
"""
A transformer that adds a print statement at the beginning
of every function definition to log entry.
"""
def visit_FunctionDef(self, node):
# Create a new print statement node: print(f"Entering function '{node.name}'...")
# This constructs an AST node for: print("Entering function 'func_name'...")
log_message = ast.Constant(value=f"Entering function '{node.name}'...")
log_call = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[log_message],
keywords=[]
)
)
# Insert the log_call at the beginning of the function's body
node.body.insert(0, log_call)
# Important: Call generic_visit to ensure child nodes are also visited
# in case there are other transformations to apply within the function.
return self.generic_visit(node)
# 1. Parse the original code into an Abstract Syntax Tree (AST)
tree = ast.parse(original_code_str)
# 2. Apply the transformation using our custom NodeTransformer
transformer = FunctionLoggerTransformer()
new_tree = transformer.visit(tree)
# 3. Fix missing line numbers and other metadata in the new_tree.
# This is crucial for ast.unparse to work correctly, especially on modified nodes.
ast.fix_missing_locations(new_tree)
# 4. Unparse the modified AST back into Python source code (requires Python 3.9+)
transformed_code_str = ast.unparse(new_tree)
print("--- Original Code ---")
print(original_code_str)
print("n--- Transformed Code ---")
print(transformed_code_str)
# To actually execute the transformed code, you could use exec():
# print("n--- Executing Transformed Code ---")
# exec(transformed_code_str)
# calculate_square(10)
# greet("AST Explorer")
```
Output example:
```
--- Original Code ---
def calculate_square(x):
result = x * x
return result
def greet(name):
print(f"Hello, {name}!")
--- Transformed Code ---
def calculate_square(x):
print(f"Entering function 'calculate_square'...")
result = x * x
return result
def greet(name):
print(f"Entering function 'greet'...")
print(f"Hello, AST Explorer!")
```
**Small Challenge:**
Your turn! Can you modify the `FunctionLoggerTransformer` to rename the variable `result` to `squared_value` *only* within the `calculate_square` function? 🤔 (Hint: you'll need to check the function name in `visit_FunctionDef` and then potentially define a new transformer or specifically handle `ast.Name` nodes within that scope.)
---
Here's your Telegram post for Day 35 of the Python learning series!
---
Hey Pythonistas! 👋 Ready to peel back another layer of Python's magic? ✨
For Day 35, we're stepping into the control room of class creation with **Metaclass Maestro: Orchestrating Runtime Behavior with `type` and `__prepare__`**! 🎩🐍 Ever wondered how Python classes are truly born? Beyond `class MyClass:`, there's a fascinating world where `type` itself orchestrates their creation. Uncover the ultimate control over class definition, from customizing its dictionary *before* attributes are even added, to forging unique object models. This is where Python's magic truly reveals its mechanics!
```python
import collections
class CustomNamespaceMeta(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
"""
Called before the class body is executed.
Returns the mapping object that will serve as the class's namespace.
Using OrderedDict to preserve attribute definition order.
"""
print(f"[{name}] __prepare__ called: Creating an OrderedDict for class namespace.")
return collections.OrderedDict()
def __new__(metacls, name, bases, namespace, **kwargs):
"""
Called to create the new class object.
'namespace' is the mapping object returned by __prepare__,
now populated with attributes defined in the class body.
"""
print(f"[{name}] __new__ called: Namespace type is {type(namespace)}")
print(f"[{name}] Namespace keys (in order): {list(namespace.keys())}")
# We can inspect or even modify the namespace here before the class is fully created
namespace['meta_added_magic'] = f"Hello from {name}'s Metaclass!"
# Call the default type.__new__ to actually create the class object
return super().__new__(metacls, name, bases, namespace)
def __init__(cls, name, bases, namespace, **kwargs):
"""
Called after the class object has been created by __new__.
Used for post-creation setup.
"""
print(f"[{name}] __init__ called: Class '{name}' fully created.")
super().__init__(cls, name, bases, namespace)
class MyMetaclassedClass(metaclass=CustomNamespaceMeta):
# These attributes will populate the OrderedDict prepared by CustomNamespaceMeta
first_attr = 10
second_method = lambda self: "Method defined in class body"
third_value = [1, 2, 3]
# --- Demonstrate usage ---
print("n--- Inspecting MyMetaclassedClass ---")
# Access the attribute added by the metaclass
print(f"Value of 'meta_added_magic': {MyMetaclassedClass.meta_added_magic}")
# Check for a user-defined attribute
print(f"Is 'first_attr' present?: {'first_attr' in MyMetaclassedClass.__dict__}")
# Demonstrate the order if we iterate through __dict__ (which is a regular dict typically)
# The OrderedDict primarily affects the namespace passed to __new__
# The actual __dict__ of the class might not preserve order unless the metaclass explicitly copies it.
# However, the key is showing the *namespace* object in __new__ had the order.
```
Output example:
```
[MyMetaclassedClass] __prepare__ called: Creating an OrderedDict for class namespace.
[MyMetaclassedClass] __new__ called: Namespace type is <class 'collections.OrderedDict'>
[MyMetaclassedClass] Namespace keys (in order): ['__module__', '__qualname__', 'first_attr', 'second_method', 'third_value', 'meta_added_magic']
[MyMetaclassedClass] __init__ called: Class 'MyMetaclassedClass' fully created.
--- Inspecting MyMetaclassedClass ---
Value of 'meta_added_magic': Hello from MyMetaclassedClass's Metaclass!
Is 'first_attr' present?: True
```
### Challenge 🚀
Can you modify the `CustomNamespaceMeta` to automatically uppercase the names of all attributes (variables, not methods) defined in `MyMetaclassedClass`? For instance, `first_attr` should become `FIRST_ATTR`. Hint: You'll need to iterate and modify the `namespace` *before* calling `super().__new__`.
---
---
Hey Pythonistas! 👋 Ready to peel back another layer of Python's magic? ✨
For Day 35, we're stepping into the control room of class creation with **Metaclass Maestro: Orchestrating Runtime Behavior with `type` and `__prepare__`**! 🎩🐍 Ever wondered how Python classes are truly born? Beyond `class MyClass:`, there's a fascinating world where `type` itself orchestrates their creation. Uncover the ultimate control over class definition, from customizing its dictionary *before* attributes are even added, to forging unique object models. This is where Python's magic truly reveals its mechanics!
```python
import collections
class CustomNamespaceMeta(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
"""
Called before the class body is executed.
Returns the mapping object that will serve as the class's namespace.
Using OrderedDict to preserve attribute definition order.
"""
print(f"[{name}] __prepare__ called: Creating an OrderedDict for class namespace.")
return collections.OrderedDict()
def __new__(metacls, name, bases, namespace, **kwargs):
"""
Called to create the new class object.
'namespace' is the mapping object returned by __prepare__,
now populated with attributes defined in the class body.
"""
print(f"[{name}] __new__ called: Namespace type is {type(namespace)}")
print(f"[{name}] Namespace keys (in order): {list(namespace.keys())}")
# We can inspect or even modify the namespace here before the class is fully created
namespace['meta_added_magic'] = f"Hello from {name}'s Metaclass!"
# Call the default type.__new__ to actually create the class object
return super().__new__(metacls, name, bases, namespace)
def __init__(cls, name, bases, namespace, **kwargs):
"""
Called after the class object has been created by __new__.
Used for post-creation setup.
"""
print(f"[{name}] __init__ called: Class '{name}' fully created.")
super().__init__(cls, name, bases, namespace)
class MyMetaclassedClass(metaclass=CustomNamespaceMeta):
# These attributes will populate the OrderedDict prepared by CustomNamespaceMeta
first_attr = 10
second_method = lambda self: "Method defined in class body"
third_value = [1, 2, 3]
# --- Demonstrate usage ---
print("n--- Inspecting MyMetaclassedClass ---")
# Access the attribute added by the metaclass
print(f"Value of 'meta_added_magic': {MyMetaclassedClass.meta_added_magic}")
# Check for a user-defined attribute
print(f"Is 'first_attr' present?: {'first_attr' in MyMetaclassedClass.__dict__}")
# Demonstrate the order if we iterate through __dict__ (which is a regular dict typically)
# The OrderedDict primarily affects the namespace passed to __new__
# The actual __dict__ of the class might not preserve order unless the metaclass explicitly copies it.
# However, the key is showing the *namespace* object in __new__ had the order.
```
Output example:
```
[MyMetaclassedClass] __prepare__ called: Creating an OrderedDict for class namespace.
[MyMetaclassedClass] __new__ called: Namespace type is <class 'collections.OrderedDict'>
[MyMetaclassedClass] Namespace keys (in order): ['__module__', '__qualname__', 'first_attr', 'second_method', 'third_value', 'meta_added_magic']
[MyMetaclassedClass] __init__ called: Class 'MyMetaclassedClass' fully created.
--- Inspecting MyMetaclassedClass ---
Value of 'meta_added_magic': Hello from MyMetaclassedClass's Metaclass!
Is 'first_attr' present?: True
```
### Challenge 🚀
Can you modify the `CustomNamespaceMeta` to automatically uppercase the names of all attributes (variables, not methods) defined in `MyMetaclassedClass`? For instance, `first_attr` should become `FIRST_ATTR`. Hint: You'll need to iterate and modify the `namespace` *before* calling `super().__new__`.
---
What happens when you assign a value to an instance attribute (e.g., `obj.attr = value`) where `attr` is managed by a descriptor object (like our `ValidatedNonNegative`)?
Anonymous Quiz
0%
The descriptor's `__set__(obj, value)` method is called, allowing custom logic before storage.
0%
The descriptor's `__get__(obj, obj.__class__)` method is called to retrieve the current value.
0%
The `obj`'s `__dict__` is updated directly with the `value` without any descriptor intervention.
0%
The descriptor's `__delete__(obj)` method is called, which removes the attribute from `obj`.
In Python's descriptor protocol, which special method is invoked when you attempt to set a value to an attribute that is managed by a descriptor (e.g., `instance.my_attribute = 'new_value'`)?
Anonymous Quiz
0%
__get__(self, instance, owner)
0%
__set__(self, instance, value)
0%
__delete__(self, instance)
0%
__set_name__(self, owner, name)
Here's your Telegram post for Day 38 of the Python learning series! 🚀
---
**Day 38: Static Contract Enforcement 🛡️ Mastering Protocols and Generic Typevars for Robust APIs**
Ever wanted to define an interface without inheritance, enforcing specific behaviors across diverse classes? Python's `typing.Protocol` is your answer! 🦆 It brings structural subtyping (duck typing) to the type checker, ensuring your APIs are robust and predictable. Combine this with `TypeVar` for truly generic, type-safe functions. Build libraries that scream reliability! ✨
```python
from typing import Protocol, TypeVar, runtime_checkable
# 1. Define a Protocol for objects that can be 'printed'.
# @runtime_checkable allows isinstance() checks at runtime, though Protocols
# are primarily for static analysis by tools like mypy.
@runtime_checkable
class Printable(Protocol):
"""
A protocol for objects that provide a string representation
via a 'to_string' method.
"""
def to_string(self) -> str:
# Ellipsis indicates an abstract method signature; no implementation needed here.
...
# 2. Implementations (implicit: no explicit inheritance from Printable required!)
class MyObject:
def __init__(self, value: int):
self.value = value
def to_string(self) -> str:
return f"MyObject(value={self.value})"
class AnotherType:
def __init__(self, name: str):
self.name = name
def to_string(self) -> str:
return f"AnotherType(name='{self.name}')"
class NotPrintable:
def __init__(self, data: list):
self.data = data
# This class *does not* have a 'to_string' method, so it does not conform.
# 3. A Generic Function that expects any type conforming to the Printable protocol.
# 'bound=Printable' ensures that P must be Printable or a subtype thereof.
P = TypeVar('P', bound=Printable)
def process_and_print(item: P) -> None:
"""
Takes any object conforming to the Printable protocol and prints its
string representation. Static type checkers will ensure 'item' has 'to_string'.
"""
print(f"Processing and printing: {item.to_string()}")
# 4. Demonstrate usage and type checking
if __name__ == "__main__":
obj1 = MyObject(123)
obj2 = AnotherType("Protocol Example")
obj_not_printable = NotPrintable([1, 2, 3])
process_and_print(obj1)
process_and_print(obj2)
# Check conformity at runtime (thanks to @runtime_checkable)
print(f"nIs obj1 a Printable? {isinstance(obj1, Printable)}")
print(f"Is obj_not_printable a Printable? {isinstance(obj_not_printable, Printable)}")
# If you tried to uncomment the line below and run mypy, it would flag a type error!
# process_and_print(obj_not_printable)
# At runtime, calling process_and_print with obj_not_printable would raise an AttributeError.
```
Output example:
```
Processing and printing: MyObject(value=123)
Processing and printing: AnotherType(name='Protocol Example')
Is obj1 a Printable? True
Is obj_not_printable a Printable? False
```
**Small challenge:**
Extend the `Printable` protocol to also require a `get_id()` method that returns an `int`. Then, create a new class that conforms to this extended protocol and try passing it to `process_and_print`. How would mypy react if `get_id()` were missing from one of your conforming classes?
---
---
**Day 38: Static Contract Enforcement 🛡️ Mastering Protocols and Generic Typevars for Robust APIs**
Ever wanted to define an interface without inheritance, enforcing specific behaviors across diverse classes? Python's `typing.Protocol` is your answer! 🦆 It brings structural subtyping (duck typing) to the type checker, ensuring your APIs are robust and predictable. Combine this with `TypeVar` for truly generic, type-safe functions. Build libraries that scream reliability! ✨
```python
from typing import Protocol, TypeVar, runtime_checkable
# 1. Define a Protocol for objects that can be 'printed'.
# @runtime_checkable allows isinstance() checks at runtime, though Protocols
# are primarily for static analysis by tools like mypy.
@runtime_checkable
class Printable(Protocol):
"""
A protocol for objects that provide a string representation
via a 'to_string' method.
"""
def to_string(self) -> str:
# Ellipsis indicates an abstract method signature; no implementation needed here.
...
# 2. Implementations (implicit: no explicit inheritance from Printable required!)
class MyObject:
def __init__(self, value: int):
self.value = value
def to_string(self) -> str:
return f"MyObject(value={self.value})"
class AnotherType:
def __init__(self, name: str):
self.name = name
def to_string(self) -> str:
return f"AnotherType(name='{self.name}')"
class NotPrintable:
def __init__(self, data: list):
self.data = data
# This class *does not* have a 'to_string' method, so it does not conform.
# 3. A Generic Function that expects any type conforming to the Printable protocol.
# 'bound=Printable' ensures that P must be Printable or a subtype thereof.
P = TypeVar('P', bound=Printable)
def process_and_print(item: P) -> None:
"""
Takes any object conforming to the Printable protocol and prints its
string representation. Static type checkers will ensure 'item' has 'to_string'.
"""
print(f"Processing and printing: {item.to_string()}")
# 4. Demonstrate usage and type checking
if __name__ == "__main__":
obj1 = MyObject(123)
obj2 = AnotherType("Protocol Example")
obj_not_printable = NotPrintable([1, 2, 3])
process_and_print(obj1)
process_and_print(obj2)
# Check conformity at runtime (thanks to @runtime_checkable)
print(f"nIs obj1 a Printable? {isinstance(obj1, Printable)}")
print(f"Is obj_not_printable a Printable? {isinstance(obj_not_printable, Printable)}")
# If you tried to uncomment the line below and run mypy, it would flag a type error!
# process_and_print(obj_not_printable)
# At runtime, calling process_and_print with obj_not_printable would raise an AttributeError.
```
Output example:
```
Processing and printing: MyObject(value=123)
Processing and printing: AnotherType(name='Protocol Example')
Is obj1 a Printable? True
Is obj_not_printable a Printable? False
```
**Small challenge:**
Extend the `Printable` protocol to also require a `get_id()` method that returns an `int`. Then, create a new class that conforms to this extended protocol and try passing it to `process_and_print`. How would mypy react if `get_id()` were missing from one of your conforming classes?
---
Here's your Telegram post for Day 40!
---
🚀 **Day 40: Unlocking Python's Mighty Descriptor Protocol!** 🎩
Ready to peek behind the curtain of Python's object model? Today, we're diving deep into *Descriptors*! Far beyond simple `@property` decorators, descriptors are a powerful mechanism that allows you to customize *how* attributes are accessed, set, or deleted on an object. They're the secret sauce behind many advanced features, enabling robust validation, lazy loading, and dynamic behavior. Let's wield this mighty tool and craft smarter, more controlled classes! ✨
```python
class ValidatedInt:
"""
A descriptor that ensures an attribute is an integer within a specified range.
"""
def __set_name__(self, owner, name):
# Called when the descriptor is assigned. It lets us know its own name!
self.public_name = name
self.private_name = f'_{name}' # Convention for storing the actual data
def __init__(self, min_val, max_val):
self.min_val = min_val
self.max_val = max_val
def __get__(self, obj, objtype=None):
if obj is None:
# Accessing the descriptor from the class itself (e.g., GameCharacter.level)
return self
# Retrieve the value from the instance's dictionary
value = getattr(obj, self.private_name, None)
if value is None:
raise AttributeError(f"'{self.public_name}' has not been set.")
return value
def __set__(self, obj, value):
# Validate the value before setting
if not isinstance(value, int):
raise ValueError(f"'{self.public_name}' must be an integer.")
if not (self.min_val <= value <= self.max_val):
raise ValueError(f"'{self.public_name}' must be between {self.min_val} and {self.max_val}.")
# Store the validated value in the instance's dictionary
setattr(obj, self.private_name, value)
def __delete__(self, obj):
# Prevent deletion of the attribute
raise AttributeError(f"'{self.public_name}' cannot be deleted.")
class GameCharacter:
level = ValidatedInt(1, 100) # Our level attribute now uses the descriptor!
health = ValidatedInt(0, 1000) # Same for health!
def __init__(self, level, health):
self.level = level # This calls __set__ on the descriptor
self.health = health # This calls __set__ on the descriptor
print("--- Creating characters ---")
hero = GameCharacter(10, 500)
print(f"Hero Level: {hero.level}, Health: {hero.health}")
villain = GameCharacter(99, 900)
print(f"Villain Level: {villain.level}, Health: {villain.health}")
print("n--- Testing validation ---")
try:
hero.level = 101 # Too high, will raise ValueError
except ValueError as e:
print(f"Error setting hero level: {e}")
try:
hero.health = "low" # Not an int, will raise ValueError
except ValueError as e:
print(f"Error setting hero health: {e}")
try:
del hero.level # Attempt to delete, will raise AttributeError
except AttributeError as e:
print(f"Error deleting hero level: {e}")
print(f"nHero (after attempts): Level: {hero.level}, Health: {hero.health}")
```
Output example:
```
--- Creating characters ---
Hero Level: 10, Health: 500
Villain Level: 99, Health: 900
--- Testing validation ---
Error setting hero level: 'level' must be between 1 and 100.
Error setting hero health: 'health' must be an integer.
Error deleting hero level: 'level' cannot be deleted.
Hero (after attempts): Level: 10, Health: 500
```
💡 **Challenge:** Modify the `ValidatedInt` descriptor to also accept an optional `default_value` in its `__init__`. If the attribute is accessed but hasn't been set yet (i.e., `getattr(obj, self.private_name, None)` returns `None`), it should return this `default_value` instead of raising an `AttributeError`.
---
Here's a quick quiz to test your descriptor knowledge! 👇
---
🚀 **Day 40: Unlocking Python's Mighty Descriptor Protocol!** 🎩
Ready to peek behind the curtain of Python's object model? Today, we're diving deep into *Descriptors*! Far beyond simple `@property` decorators, descriptors are a powerful mechanism that allows you to customize *how* attributes are accessed, set, or deleted on an object. They're the secret sauce behind many advanced features, enabling robust validation, lazy loading, and dynamic behavior. Let's wield this mighty tool and craft smarter, more controlled classes! ✨
```python
class ValidatedInt:
"""
A descriptor that ensures an attribute is an integer within a specified range.
"""
def __set_name__(self, owner, name):
# Called when the descriptor is assigned. It lets us know its own name!
self.public_name = name
self.private_name = f'_{name}' # Convention for storing the actual data
def __init__(self, min_val, max_val):
self.min_val = min_val
self.max_val = max_val
def __get__(self, obj, objtype=None):
if obj is None:
# Accessing the descriptor from the class itself (e.g., GameCharacter.level)
return self
# Retrieve the value from the instance's dictionary
value = getattr(obj, self.private_name, None)
if value is None:
raise AttributeError(f"'{self.public_name}' has not been set.")
return value
def __set__(self, obj, value):
# Validate the value before setting
if not isinstance(value, int):
raise ValueError(f"'{self.public_name}' must be an integer.")
if not (self.min_val <= value <= self.max_val):
raise ValueError(f"'{self.public_name}' must be between {self.min_val} and {self.max_val}.")
# Store the validated value in the instance's dictionary
setattr(obj, self.private_name, value)
def __delete__(self, obj):
# Prevent deletion of the attribute
raise AttributeError(f"'{self.public_name}' cannot be deleted.")
class GameCharacter:
level = ValidatedInt(1, 100) # Our level attribute now uses the descriptor!
health = ValidatedInt(0, 1000) # Same for health!
def __init__(self, level, health):
self.level = level # This calls __set__ on the descriptor
self.health = health # This calls __set__ on the descriptor
print("--- Creating characters ---")
hero = GameCharacter(10, 500)
print(f"Hero Level: {hero.level}, Health: {hero.health}")
villain = GameCharacter(99, 900)
print(f"Villain Level: {villain.level}, Health: {villain.health}")
print("n--- Testing validation ---")
try:
hero.level = 101 # Too high, will raise ValueError
except ValueError as e:
print(f"Error setting hero level: {e}")
try:
hero.health = "low" # Not an int, will raise ValueError
except ValueError as e:
print(f"Error setting hero health: {e}")
try:
del hero.level # Attempt to delete, will raise AttributeError
except AttributeError as e:
print(f"Error deleting hero level: {e}")
print(f"nHero (after attempts): Level: {hero.level}, Health: {hero.health}")
```
Output example:
```
--- Creating characters ---
Hero Level: 10, Health: 500
Villain Level: 99, Health: 900
--- Testing validation ---
Error setting hero level: 'level' must be between 1 and 100.
Error setting hero health: 'health' must be an integer.
Error deleting hero level: 'level' cannot be deleted.
Hero (after attempts): Level: 10, Health: 500
```
💡 **Challenge:** Modify the `ValidatedInt` descriptor to also accept an optional `default_value` in its `__init__`. If the attribute is accessed but hasn't been set yet (i.e., `getattr(obj, self.private_name, None)` returns `None`), it should return this `default_value` instead of raising an `AttributeError`.
---
Here's a quick quiz to test your descriptor knowledge! 👇
🚀 Day 42 is here, and we're tackling one of Python's most debated topics: the Global Interpreter Lock (GIL)! 🔐 Ever wondered why your multi-threaded CPU-bound Python code doesn't run faster? The GIL is the answer, restricting CPython to execute only one thread at a time.
Today, we deconstruct its impact, explore its origins, and search for true parallelism! Get ready for an ultimate question! 🤔
```python
import time
import threading
import multiprocessing
import os
# A CPU-bound task that performs calculations
def cpu_bound_task(n):
count = 0
for i in range(n):
count += i * i # Simple calculation to keep CPU busy
return count
# Function to run multiple CPU-bound tasks using threads
def run_with_threads(n_tasks, iterations):
start_time = time.perf_counter()
threads = []
print(f" Starting {n_tasks} threads in PID: {os.getpid()}")
for _ in range(n_tasks):
thread = threading.Thread(target=cpu_bound_task, args=(iterations,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
end_time = time.perf_counter()
print(f" Threads finished in: {end_time - start_time:.4f} seconds")
# Function to run multiple CPU-bound tasks using processes
def run_with_processes(n_tasks, iterations):
start_time = time.perf_counter()
processes = []
print(f" Starting {n_tasks} processes (each with its own GIL)")
for _ in range(n_tasks):
process = multiprocessing.Process(target=cpu_bound_task, args=(iterations,))
processes.append(process)
process.start()
for process in processes:
process.join()
end_time = time.perf_counter()
print(f" Processes finished in: {end_time - start_time:.4f} seconds")
if __name__ == "__main__":
NUM_TASKS = 4 # Number of threads/processes to create
ITERATIONS = 10_000_000 # Complexity for each CPU-bound task
print("--- Testing with Threads (affected by GIL) ---")
run_with_threads(NUM_TASKS, ITERATIONS)
print("n--- Testing with Processes (bypasses GIL) ---")
run_with_processes(NUM_TASKS, ITERATIONS)
print("nObservation: For CPU-bound tasks in CPython, multi-processing typically shows better performance, as each process gets its own interpreter and GIL, allowing true parallel execution.")
```
Output example:
```
--- Testing with Threads (affected by GIL) ---
Starting 4 threads in PID: 12345
Threads finished in: 3.1234 seconds
--- Testing with Processes (bypasses GIL) ---
Starting 4 processes (each with its own GIL)
Processes finished in: 0.8765 seconds
Observation: For CPU-bound tasks in CPython, multi-processing typically shows better performance, as each process gets its own interpreter and GIL, allowing true parallel execution.
```
Small challenge:
Challenge: Modify the `cpu_bound_task` to simulate an I/O-bound operation (e.g., replace `count += i * i` with `time.sleep(0.01)` inside the loop, or better, `time.sleep(0.1)` outside the loop once). How do the execution times for threads vs. processes change? Why? 👇 Let us know your insights!
Today, we deconstruct its impact, explore its origins, and search for true parallelism! Get ready for an ultimate question! 🤔
```python
import time
import threading
import multiprocessing
import os
# A CPU-bound task that performs calculations
def cpu_bound_task(n):
count = 0
for i in range(n):
count += i * i # Simple calculation to keep CPU busy
return count
# Function to run multiple CPU-bound tasks using threads
def run_with_threads(n_tasks, iterations):
start_time = time.perf_counter()
threads = []
print(f" Starting {n_tasks} threads in PID: {os.getpid()}")
for _ in range(n_tasks):
thread = threading.Thread(target=cpu_bound_task, args=(iterations,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
end_time = time.perf_counter()
print(f" Threads finished in: {end_time - start_time:.4f} seconds")
# Function to run multiple CPU-bound tasks using processes
def run_with_processes(n_tasks, iterations):
start_time = time.perf_counter()
processes = []
print(f" Starting {n_tasks} processes (each with its own GIL)")
for _ in range(n_tasks):
process = multiprocessing.Process(target=cpu_bound_task, args=(iterations,))
processes.append(process)
process.start()
for process in processes:
process.join()
end_time = time.perf_counter()
print(f" Processes finished in: {end_time - start_time:.4f} seconds")
if __name__ == "__main__":
NUM_TASKS = 4 # Number of threads/processes to create
ITERATIONS = 10_000_000 # Complexity for each CPU-bound task
print("--- Testing with Threads (affected by GIL) ---")
run_with_threads(NUM_TASKS, ITERATIONS)
print("n--- Testing with Processes (bypasses GIL) ---")
run_with_processes(NUM_TASKS, ITERATIONS)
print("nObservation: For CPU-bound tasks in CPython, multi-processing typically shows better performance, as each process gets its own interpreter and GIL, allowing true parallel execution.")
```
Output example:
```
--- Testing with Threads (affected by GIL) ---
Starting 4 threads in PID: 12345
Threads finished in: 3.1234 seconds
--- Testing with Processes (bypasses GIL) ---
Starting 4 processes (each with its own GIL)
Processes finished in: 0.8765 seconds
Observation: For CPU-bound tasks in CPython, multi-processing typically shows better performance, as each process gets its own interpreter and GIL, allowing true parallel execution.
```
Small challenge:
Challenge: Modify the `cpu_bound_task` to simulate an I/O-bound operation (e.g., replace `count += i * i` with `time.sleep(0.01)` inside the loop, or better, `time.sleep(0.1)` outside the loop once). How do the execution times for threads vs. processes change? Why? 👇 Let us know your insights!
Which of the following methods, when implemented in a class, makes an instance of that class a 'descriptor' in Python?
Anonymous Quiz
0%
A) Only `__init__`
0%
B) At least one of `__get__`, `__set__`, or `__delete__`
0%
C) Only `__call__`
0%
D) At least one of `__enter__` or `__exit__`
🚀 **Day 44: The Zen of Zero-Copy with `memoryview` and Protocols!** 🚀
Ever wondered how to handle large datasets in Python without constantly copying memory? Today, we unlock a powerful secret: `memoryview` and Python's Buffer Protocol! 💡 This advanced topic allows you to create "views" into an existing object's memory, enabling high-performance, low-overhead data manipulation. Forget unnecessary copying; manipulate data directly, efficiently, and with incredible speed, especially when dealing with bytes, bytearrays, or integrating with C extensions. Let's make your Python code truly fly! 🚀
```python
import array
# 1. Start with a mutable data source (e.g., bytearray)
original_data = bytearray(b'Hello Python Learners!')
print(f"Original data: {original_data}")
# 2. Create a memoryview of the data
mv = memoryview(original_data)
print(f"Memoryview as string (initial): {mv.tobytes().decode()}")
# 3. Modify the data through the memoryview - no copy is made!
# The changes directly reflect in the original_data.
mv[6:12] = b'WORLD!' # Change 'Python' to 'WORLD!'
mv[0] = ord('J') # Change 'H' to 'J'
print(f"Memoryview as string (modified): {mv.tobytes().decode()}")
print(f"Original data after memoryview modification: {original_data}")
# 4. Memoryviews can be sliced, and these slices are also views!
sub_mv = mv[6:12]
print(f"Sliced memoryview (sub_mv): {sub_mv.tobytes().decode()}")
# 5. Modifying the sub-view also modifies the original data
sub_mv[0] = ord('W') # Change 'W' to 'W' (no visible change, but demonstrates ability)
print(f"Original data after sub_mv modification: {original_data}")
# 6. Attempting to create a memoryview of an immutable object (like bytes)
# and then trying to modify it will raise an error.
immutable_bytes = b'Immutable'
mv_immutable = memoryview(immutable_bytes)
try:
# This line would raise an error if uncommented
# mv_immutable[0] = ord('X')
pass
except TypeError as e:
print(f"nAttempting to modify immutable bytes via memoryview leads to: {e}")
```
Output example:
```
Original data: bytearray(b'Hello Python Learners!')
Memoryview as string (initial): Hello Python Learners!
Memoryview as string (modified): Jello WORLD! Learners!
Original data after memoryview modification: bytearray(b'Jello WORLD! Learners!')
Sliced memoryview (sub_mv): WORLD!
Original data after sub_mv modification: bytearray(b'Jello WORLD! Learners!')
Attempting to modify immutable bytes via memoryview leads to: memoryview: underlying object is not writable
```
💡 **Challenge:**
Imagine you have a `bytearray` containing a large stream of sensor data, and you need to process only the segments where a specific flag is set. How would you use `memoryview` to efficiently iterate through these segments, creating views for each, without creating multiple copies of data? Share your approach! 👇
---
Ever wondered how to handle large datasets in Python without constantly copying memory? Today, we unlock a powerful secret: `memoryview` and Python's Buffer Protocol! 💡 This advanced topic allows you to create "views" into an existing object's memory, enabling high-performance, low-overhead data manipulation. Forget unnecessary copying; manipulate data directly, efficiently, and with incredible speed, especially when dealing with bytes, bytearrays, or integrating with C extensions. Let's make your Python code truly fly! 🚀
```python
import array
# 1. Start with a mutable data source (e.g., bytearray)
original_data = bytearray(b'Hello Python Learners!')
print(f"Original data: {original_data}")
# 2. Create a memoryview of the data
mv = memoryview(original_data)
print(f"Memoryview as string (initial): {mv.tobytes().decode()}")
# 3. Modify the data through the memoryview - no copy is made!
# The changes directly reflect in the original_data.
mv[6:12] = b'WORLD!' # Change 'Python' to 'WORLD!'
mv[0] = ord('J') # Change 'H' to 'J'
print(f"Memoryview as string (modified): {mv.tobytes().decode()}")
print(f"Original data after memoryview modification: {original_data}")
# 4. Memoryviews can be sliced, and these slices are also views!
sub_mv = mv[6:12]
print(f"Sliced memoryview (sub_mv): {sub_mv.tobytes().decode()}")
# 5. Modifying the sub-view also modifies the original data
sub_mv[0] = ord('W') # Change 'W' to 'W' (no visible change, but demonstrates ability)
print(f"Original data after sub_mv modification: {original_data}")
# 6. Attempting to create a memoryview of an immutable object (like bytes)
# and then trying to modify it will raise an error.
immutable_bytes = b'Immutable'
mv_immutable = memoryview(immutable_bytes)
try:
# This line would raise an error if uncommented
# mv_immutable[0] = ord('X')
pass
except TypeError as e:
print(f"nAttempting to modify immutable bytes via memoryview leads to: {e}")
```
Output example:
```
Original data: bytearray(b'Hello Python Learners!')
Memoryview as string (initial): Hello Python Learners!
Memoryview as string (modified): Jello WORLD! Learners!
Original data after memoryview modification: bytearray(b'Jello WORLD! Learners!')
Sliced memoryview (sub_mv): WORLD!
Original data after sub_mv modification: bytearray(b'Jello WORLD! Learners!')
Attempting to modify immutable bytes via memoryview leads to: memoryview: underlying object is not writable
```
💡 **Challenge:**
Imagine you have a `bytearray` containing a large stream of sensor data, and you need to process only the segments where a specific flag is set. How would you use `memoryview` to efficiently iterate through these segments, creating views for each, without creating multiple copies of data? Share your approach! 👇
---