What is the primary purpose of a metaclass in Python?
Anonymous Quiz
0%
To define how classes are created and behave.
0%
To create multiple instances of a class efficiently.
0%
To manage the inheritance hierarchy of objects.
0%
To optimize memory usage for large data structures.
Day 43, Pythonistas! 🚀 Ready to break through the Pythonic abstraction layer? Today, we're diving deep into the system's core, unlocking direct low-level control with `ctypes` and `mmap`. 🐍 Learn how to interface with C libraries and manage shared memory directly, bypassing Python's usual safety nets for extreme performance, precise system interaction, or just to peek behind the curtain. Get ready to truly understand what's happening under the hood! 🛠️
```python
import ctypes
import mmap
import os
import struct
print("--- ctypes: Calling C functions ---")
# Load the standard C library (libc)
# This module allows Python to call functions from dynamic link libraries (DLLs/SOs)
libc = None
try:
if os.name == "posix": # Linux or macOS
try:
libc = ctypes.CDLL("libc.so.6") # Common for Linux
except OSError:
libc = ctypes.CDLL("libc.dylib") # Common for macOS
else: # Windows
libc = ctypes.CDLL("msvcrt.dll")
except OSError as e:
print(f"Could not load libc. Ensure it's available on your system. Error: {e}")
if libc:
# Define the argument and return types for printf (good practice for clarity)
# printf's first argument is a C string (char*), which maps to ctypes.c_char_p
# It returns an integer (number of characters printed)
libc.printf.argtypes = [ctypes.c_char_p]
libc.printf.restype = ctypes.c_int
# Call the printf function directly from the C library
message = "Hello from libc's printf! Python and C dancing together. (Day 43)n"
# C functions expect bytes, so we encode the Python string
libc.printf(message.encode('utf-8'))
else:
print("ctypes example skipped due to libc loading failure.")
print("n--- mmap: Direct memory manipulation ---")
# The mmap module allows you to treat a file or anonymous memory region as a sequence of bytes,
# providing direct, low-level access to memory.
# Create a temporary file to demonstrate file-backed mmap (cross-platform friendly)
temp_file_name = "mmap_data.bin"
with open(temp_file_name, "wb") as f:
f.write(b'x00' * 32) # Initialize with 32 null bytes
with open(temp_file_name, "r+b") as f:
# Map the entire file into memory for read-write access
mm = mmap.mmap(f.fileno(), 0) # length=0 maps the entire file
# Write an integer (e.g., 12345) at the beginning of the mapped memory
# '!i' is network byte order (big-endian) integer
mm.seek(0)
mm.write(struct.pack('!i', 12345))
# Write a short string at offset 4
mm.seek(4)
mm.write(b"MMap rocks!")
# Read back the integer from the mapped memory
mm.seek(0)
read_int = struct.unpack('!i', mm.read(4))[0]
# Read back the string from the mapped memory
mm.seek(4)
read_str_bytes = mm.read(11) # Read 11 bytes for "MMap rocks!"
read_str = read_str_bytes.decode('utf-8')
print(f"Content of mmap (first 16 bytes in hex): {mm[:16].hex()}")
print(f"Integer read from mmap: {read_int}")
print(f"String read from mmap: '{read_str}'")
mm.close() # Unmap the memory region
os.remove(temp_file_name) # Clean up the temporary file
```
Output example:
```
--- ctypes: Calling C functions ---
Hello from libc's printf! Python and C dancing together. (Day 43)
--- mmap: Direct memory manipulation ---
Content of mmap (first 16 bytes in hex): 000030394d4d617020726f636b732100
Integer read from mmap: 12345
String read from mmap: 'MMap rocks!'
```
Challenge: Modify the `ctypes` example to call `libc.time(None)` to get the current Unix timestamp directly from C, and print it. For `mmap`, try mapping a larger region and storing different data types (e.g., floats, more strings) at specific offsets! 🧠
```python
import ctypes
import mmap
import os
import struct
print("--- ctypes: Calling C functions ---")
# Load the standard C library (libc)
# This module allows Python to call functions from dynamic link libraries (DLLs/SOs)
libc = None
try:
if os.name == "posix": # Linux or macOS
try:
libc = ctypes.CDLL("libc.so.6") # Common for Linux
except OSError:
libc = ctypes.CDLL("libc.dylib") # Common for macOS
else: # Windows
libc = ctypes.CDLL("msvcrt.dll")
except OSError as e:
print(f"Could not load libc. Ensure it's available on your system. Error: {e}")
if libc:
# Define the argument and return types for printf (good practice for clarity)
# printf's first argument is a C string (char*), which maps to ctypes.c_char_p
# It returns an integer (number of characters printed)
libc.printf.argtypes = [ctypes.c_char_p]
libc.printf.restype = ctypes.c_int
# Call the printf function directly from the C library
message = "Hello from libc's printf! Python and C dancing together. (Day 43)n"
# C functions expect bytes, so we encode the Python string
libc.printf(message.encode('utf-8'))
else:
print("ctypes example skipped due to libc loading failure.")
print("n--- mmap: Direct memory manipulation ---")
# The mmap module allows you to treat a file or anonymous memory region as a sequence of bytes,
# providing direct, low-level access to memory.
# Create a temporary file to demonstrate file-backed mmap (cross-platform friendly)
temp_file_name = "mmap_data.bin"
with open(temp_file_name, "wb") as f:
f.write(b'x00' * 32) # Initialize with 32 null bytes
with open(temp_file_name, "r+b") as f:
# Map the entire file into memory for read-write access
mm = mmap.mmap(f.fileno(), 0) # length=0 maps the entire file
# Write an integer (e.g., 12345) at the beginning of the mapped memory
# '!i' is network byte order (big-endian) integer
mm.seek(0)
mm.write(struct.pack('!i', 12345))
# Write a short string at offset 4
mm.seek(4)
mm.write(b"MMap rocks!")
# Read back the integer from the mapped memory
mm.seek(0)
read_int = struct.unpack('!i', mm.read(4))[0]
# Read back the string from the mapped memory
mm.seek(4)
read_str_bytes = mm.read(11) # Read 11 bytes for "MMap rocks!"
read_str = read_str_bytes.decode('utf-8')
print(f"Content of mmap (first 16 bytes in hex): {mm[:16].hex()}")
print(f"Integer read from mmap: {read_int}")
print(f"String read from mmap: '{read_str}'")
mm.close() # Unmap the memory region
os.remove(temp_file_name) # Clean up the temporary file
```
Output example:
```
--- ctypes: Calling C functions ---
Hello from libc's printf! Python and C dancing together. (Day 43)
--- mmap: Direct memory manipulation ---
Content of mmap (first 16 bytes in hex): 000030394d4d617020726f636b732100
Integer read from mmap: 12345
String read from mmap: 'MMap rocks!'
```
Challenge: Modify the `ctypes` example to call `libc.time(None)` to get the current Unix timestamp directly from C, and print it. For `mmap`, try mapping a larger region and storing different data types (e.g., floats, more strings) at specific offsets! 🧠
What is the primary purpose of Python's `ctypes` module?
Anonymous Quiz
50%
A) To create high-level graphical user interfaces (GUIs).
0%
B) To enable Python to call functions in shared libraries (DLLs/SOs) and manipulate C data types.
50%
C) To provide a framework for asynchronous programming.
0%
D) To automatically convert Python code into C for faster execution.
🚀 Ready to peel back the layers of Python's execution engine? On Day 44 of our Advanced Python series, we're diving deep into **Dynamic Bytecode Manipulation in CPython**! 🐍 Ever wondered what your Python code *really* looks like to the interpreter? We'll unveil the compiler's secrets, exploring how to inspect (and hint at modifying!) the low-level instructions that power your scripts. Prepare for some serious metaprogramming insights! ✨
```python
import dis
import types
def greet_original(name):
"""A simple function to greet someone."""
message = f"Hello, {name}!"
print(message)
return message
# Inspect the bytecode of our simple function
print("--- Bytecode of 'greet_original' function ---")
dis.dis(greet_original)
# --- Demonstrating a form of runtime bytecode manipulation (via code object swap) ---
# This simulates patching a function's behavior at runtime.
# In real advanced scenarios, you might modify the bytecode array directly!
def greet_patched(name):
"""A 'patched' version of the greet function."""
msg = f"Hola, {name}! (Patched)"
print(msg)
return msg
print("n--- Original greet call ---")
greet_original("Alice")
# WARNING: Directly manipulating `__code__` should be done with extreme care.
# This is for illustrative purposes to show how the underlying code can change.
# Assigning the code object of `greet_patched` to `greet_original` effectively changes its behavior.
greet_original.__code__ = greet_patched.__code__
print("n--- Modified greet call (via original function name) ---")
greet_original("Bob")
print("n--- Bytecode of 'greet_original' after modification ---")
# Now, inspecting `greet_original` shows the bytecode of `greet_patched`
dis.dis(greet_original)
```
Output example:
```
--- Bytecode of 'greet_original' function ---
5 0 LOAD_CONST 1 ('Hello, ')
2 LOAD_FAST 0 (name)
4 FORMAT_VALUE 0
6 LOAD_CONST 2 ('!')
8 BUILD_STRING 3
10 STORE_FAST 1 (message)
6 12 LOAD_GLOBAL 0 (print)
14 LOAD_FAST 1 (message)
16 CALL_FUNCTION 1
18 POP_TOP
7 20 LOAD_FAST 1 (message)
22 RETURN_VALUE
--- Original greet call ---
Hello, Alice!
--- Modified greet call (via original function name) ---
Hola, Bob! (Patched)
--- Bytecode of 'greet_original' after modification ---
13 0 LOAD_CONST 1 ('Hola, ')
2 LOAD_FAST 0 (name)
4 FORMAT_VALUE 0
6 LOAD_CONST 2 ('! (Patched)')
8 BUILD_STRING 3
10 STORE_FAST 1 (msg)
14 12 LOAD_GLOBAL 0 (print)
14 LOAD_FAST 1 (msg)
16 CALL_FUNCTION 1
18 POP_TOP
15 20 LOAD_FAST 1 (msg)
22 RETURN_VALUE
```
💡 **Challenge:**
1. Try running `dis.dis()` on a function that includes a `for` loop, an `if` statement, and a `try-except` block. How do control flow opcodes like `JUMP_ABSOLUTE`, `POP_JUMP_IF_FALSE`, and exception handling opcodes appear in the bytecode?
2. What does the `LOAD_GLOBAL` opcode typically retrieve during execution?
---
```python
import dis
import types
def greet_original(name):
"""A simple function to greet someone."""
message = f"Hello, {name}!"
print(message)
return message
# Inspect the bytecode of our simple function
print("--- Bytecode of 'greet_original' function ---")
dis.dis(greet_original)
# --- Demonstrating a form of runtime bytecode manipulation (via code object swap) ---
# This simulates patching a function's behavior at runtime.
# In real advanced scenarios, you might modify the bytecode array directly!
def greet_patched(name):
"""A 'patched' version of the greet function."""
msg = f"Hola, {name}! (Patched)"
print(msg)
return msg
print("n--- Original greet call ---")
greet_original("Alice")
# WARNING: Directly manipulating `__code__` should be done with extreme care.
# This is for illustrative purposes to show how the underlying code can change.
# Assigning the code object of `greet_patched` to `greet_original` effectively changes its behavior.
greet_original.__code__ = greet_patched.__code__
print("n--- Modified greet call (via original function name) ---")
greet_original("Bob")
print("n--- Bytecode of 'greet_original' after modification ---")
# Now, inspecting `greet_original` shows the bytecode of `greet_patched`
dis.dis(greet_original)
```
Output example:
```
--- Bytecode of 'greet_original' function ---
5 0 LOAD_CONST 1 ('Hello, ')
2 LOAD_FAST 0 (name)
4 FORMAT_VALUE 0
6 LOAD_CONST 2 ('!')
8 BUILD_STRING 3
10 STORE_FAST 1 (message)
6 12 LOAD_GLOBAL 0 (print)
14 LOAD_FAST 1 (message)
16 CALL_FUNCTION 1
18 POP_TOP
7 20 LOAD_FAST 1 (message)
22 RETURN_VALUE
--- Original greet call ---
Hello, Alice!
--- Modified greet call (via original function name) ---
Hola, Bob! (Patched)
--- Bytecode of 'greet_original' after modification ---
13 0 LOAD_CONST 1 ('Hola, ')
2 LOAD_FAST 0 (name)
4 FORMAT_VALUE 0
6 LOAD_CONST 2 ('! (Patched)')
8 BUILD_STRING 3
10 STORE_FAST 1 (msg)
14 12 LOAD_GLOBAL 0 (print)
14 LOAD_FAST 1 (msg)
16 CALL_FUNCTION 1
18 POP_TOP
15 20 LOAD_FAST 1 (msg)
22 RETURN_VALUE
```
💡 **Challenge:**
1. Try running `dis.dis()` on a function that includes a `for` loop, an `if` statement, and a `try-except` block. How do control flow opcodes like `JUMP_ABSOLUTE`, `POP_JUMP_IF_FALSE`, and exception handling opcodes appear in the bytecode?
2. What does the `LOAD_GLOBAL` opcode typically retrieve during execution?
---
What is the primary purpose of the `dis` module in Python?
Anonymous Quiz
0%
A. To convert Python source code into machine code.
0%
B. To disassemble Python bytecode into a human-readable form.
0%
C. To compile Python code just-in-time (JIT).
0%
D. To optimize Python code for faster execution.
Here's your Telegram post for Day 45 of your Python learning series!
---
Day 45: Ever wondered how Python attributes can do more than just store values? 🤔 Today, we dive into **Advanced Descriptors**! Forget simple `property` – we'll learn to craft "self-aware" attributes that can validate input, trigger side effects, and perform introspection automatically. Unleash the true power behind Python's object model and build more robust, expressive classes! 🚀 #PythonLearning #AdvancedPython #Descriptors
```python
class PositiveNumber:
"""
A descriptor that ensures the attribute it manages is a positive number.
It also prints messages on access and modification.
"""
def __set_name__(self, owner, name):
# __set_name__ allows the descriptor to know the name of the attribute
# it's managing on the owner class.
self.public_name = name
self.private_name = '_' + name # Store the actual value in a private attribute
def __get__(self, obj, obj_type=None):
if obj is None:
return self # Accessing the descriptor itself via the class
print(f"🔍 Accessing attribute '{self.public_name}'...")
# Retrieve the value from the instance's private attribute
return getattr(obj, self.private_name)
def __set__(self, obj, value):
print(f"✏️ Attempting to set attribute '{self.public_name}' to {value}...")
if not isinstance(value, (int, float)):
raise TypeError(f"Error: '{self.public_name}' must be a number, got {type(value).__name__}.")
if value <= 0:
raise ValueError(f"Error: '{self.public_name}' must be a positive number.")
# Store the validated value in the instance's private attribute
setattr(obj, self.private_name, value)
print(f"✅ Successfully set '{self.public_name}' to {value}.")
class Product:
# Assign descriptors to class attributes
price = PositiveNumber()
quantity = PositiveNumber()
def __init__(self, name, price, quantity):
self.name = name
# Setting these attributes triggers the __set__ method of our descriptors
self.price = price
self.quantity = quantity
print("--- Initializing Product ---")
product1 = Product("Laptop", 1200, 5)
print(f"nProduct details: {product1.name}, Price: {product1.price}, Quantity: {product1.quantity}")
print("n--- Testing Invalid Assignments ---")
try:
product1.price = -100 # This should raise a ValueError
except ValueError as e:
print(e)
try:
product1.quantity = "ten" # This should raise a TypeError
except TypeError as e:
print(e)
print("n--- Testing Valid Assignment ---")
product1.price = 1250.75
print(f"nUpdated Price: {product1.price}")
```
Output example:
```
--- Initializing Product ---
✏️ Attempting to set attribute 'price' to 1200...
✅ Successfully set 'price' to 1200.
✏️ Attempting to set attribute 'quantity' to 5...
✅ Successfully set 'quantity' to 5.
🔍 Accessing attribute 'price'...
🔍 Accessing attribute 'quantity'...
Product details: Laptop, Price: 1200, Quantity: 5
--- Testing Invalid Assignments ---
✏️ Attempting to set attribute 'price' to -100...
Error: 'price' must be a positive number.
✏️ Attempting to set attribute 'quantity' to ten...
Error: 'quantity' must be a number, got str.
--- Testing Valid Assignment ---
✏️ Attempting to set attribute 'price' to 1250.75...
✅ Successfully set 'price' to 1250.75.
🔍 Accessing attribute 'price'...
Updated Price: 1250.75
```
Small challenge:
How would you modify the `PositiveNumber` descriptor to also keep a history of all previously set values for that attribute in a list? Share your ideas! 👇
---
---
Day 45: Ever wondered how Python attributes can do more than just store values? 🤔 Today, we dive into **Advanced Descriptors**! Forget simple `property` – we'll learn to craft "self-aware" attributes that can validate input, trigger side effects, and perform introspection automatically. Unleash the true power behind Python's object model and build more robust, expressive classes! 🚀 #PythonLearning #AdvancedPython #Descriptors
```python
class PositiveNumber:
"""
A descriptor that ensures the attribute it manages is a positive number.
It also prints messages on access and modification.
"""
def __set_name__(self, owner, name):
# __set_name__ allows the descriptor to know the name of the attribute
# it's managing on the owner class.
self.public_name = name
self.private_name = '_' + name # Store the actual value in a private attribute
def __get__(self, obj, obj_type=None):
if obj is None:
return self # Accessing the descriptor itself via the class
print(f"🔍 Accessing attribute '{self.public_name}'...")
# Retrieve the value from the instance's private attribute
return getattr(obj, self.private_name)
def __set__(self, obj, value):
print(f"✏️ Attempting to set attribute '{self.public_name}' to {value}...")
if not isinstance(value, (int, float)):
raise TypeError(f"Error: '{self.public_name}' must be a number, got {type(value).__name__}.")
if value <= 0:
raise ValueError(f"Error: '{self.public_name}' must be a positive number.")
# Store the validated value in the instance's private attribute
setattr(obj, self.private_name, value)
print(f"✅ Successfully set '{self.public_name}' to {value}.")
class Product:
# Assign descriptors to class attributes
price = PositiveNumber()
quantity = PositiveNumber()
def __init__(self, name, price, quantity):
self.name = name
# Setting these attributes triggers the __set__ method of our descriptors
self.price = price
self.quantity = quantity
print("--- Initializing Product ---")
product1 = Product("Laptop", 1200, 5)
print(f"nProduct details: {product1.name}, Price: {product1.price}, Quantity: {product1.quantity}")
print("n--- Testing Invalid Assignments ---")
try:
product1.price = -100 # This should raise a ValueError
except ValueError as e:
print(e)
try:
product1.quantity = "ten" # This should raise a TypeError
except TypeError as e:
print(e)
print("n--- Testing Valid Assignment ---")
product1.price = 1250.75
print(f"nUpdated Price: {product1.price}")
```
Output example:
```
--- Initializing Product ---
✏️ Attempting to set attribute 'price' to 1200...
✅ Successfully set 'price' to 1200.
✏️ Attempting to set attribute 'quantity' to 5...
✅ Successfully set 'quantity' to 5.
🔍 Accessing attribute 'price'...
🔍 Accessing attribute 'quantity'...
Product details: Laptop, Price: 1200, Quantity: 5
--- Testing Invalid Assignments ---
✏️ Attempting to set attribute 'price' to -100...
Error: 'price' must be a positive number.
✏️ Attempting to set attribute 'quantity' to ten...
Error: 'quantity' must be a number, got str.
--- Testing Valid Assignment ---
✏️ Attempting to set attribute 'price' to 1250.75...
✅ Successfully set 'price' to 1250.75.
🔍 Accessing attribute 'price'...
Updated Price: 1250.75
```
Small challenge:
How would you modify the `PositiveNumber` descriptor to also keep a history of all previously set values for that attribute in a list? Share your ideas! 👇
---
What is the primary purpose of the `__set_name__(self, owner, name)` method in a Python descriptor?
Anonymous Quiz
0%
To automatically assign the attribute's name and the owner class to the descriptor instance.
100%
To define the initial value of the attribute when the class is instantiated.
0%
To prevent direct modification of the attribute by external code.
0%
To retrieve the descriptor instance from the class's `__dict__`.
Which special method in the Descriptor Protocol is invoked when you attempt to *retrieve* an attribute (e.g., `obj.my_attr`) on an object instance?
Anonymous Quiz
0%
A. `__set__`
100%
B. `__get__`
0%
C. `__delete__`
0%
D. `__set_name__`
Here's your Telegram post for Day 49 of the Python learning series!
---
🚀 Day 49: Ever wondered how Python *itself* understands your code? Or how powerful linters and static analyzers work? Today, we're diving deep into Python's Abstract Syntax Tree (AST)! 🤯
The `ast` module lets us deconstruct, manipulate, and even generate Python code programmatically. This is the ultimate metaprogramming tool, letting you craft Domain-Specific Languages (DSLs) and perform advanced code transformations. Get ready to peek behind the curtain of Python's execution! ✨
```python
import ast
# 1. Our original Python code as a string
code_string = """
def calculate_tax(amount):
# This is a simple tax calculation
tax_rate = 0.05
tax = amount * tax_rate + 100
return tax
"""
# 2. Parse the code into an AST (Abstract Syntax Tree)
tree = ast.parse(code_string)
# 3. Define an AST transformer to modify the tree
class ConstantDoubler(ast.NodeTransformer):
"""
A transformer that finds all numeric constants (int, float)
and doubles their value.
"""
def visit_Constant(self, node):
if isinstance(node.value, (int, float)):
# Create a new Constant node with the doubled value
return ast.Constant(node.value * 2)
return node # Return node unchanged if not a number
# For Python versions older than 3.8, you might need to use visit_Num
# def visit_Num(self, node):
# return ast.Num(node.n * 2)
# 4. Apply the transformation
transformer = ConstantDoubler()
new_tree = transformer.visit(tree)
# 5. Fix line numbers and other metadata in the new tree
# This is important if you want to compile or execute the modified AST
ast.fix_missing_locations(new_tree)
# 6. Unparse the modified AST back into Python code (Python 3.9+)
modified_code = ast.unparse(new_tree)
print("--- Original Code ---")
print(code_string)
print("n--- Modified Code (Numbers Doubled) ---")
print(modified_code)
# You can even compile and execute the modified code!
# exec(compile(new_tree, filename='<ast>', mode='exec'))
```
Output example:
```
--- Original Code ---
def calculate_tax(amount):
# This is a simple tax calculation
tax_rate = 0.05
tax = amount * tax_rate + 100
return tax
--- Modified Code (Numbers Doubled) ---
def calculate_tax(amount):
# This is a simple tax calculation
tax_rate = 0.1
tax = amount * tax_rate + 200
return tax
```
💡 **Challenge:** Modify the `ConstantDoubler` transformer to instead replace all occurrences of the variable name `tax_rate` with `base_rate` within the AST, then unparse the result! (Hint: Look for `ast.Name` nodes and check their `id` attribute.)
---
---
🚀 Day 49: Ever wondered how Python *itself* understands your code? Or how powerful linters and static analyzers work? Today, we're diving deep into Python's Abstract Syntax Tree (AST)! 🤯
The `ast` module lets us deconstruct, manipulate, and even generate Python code programmatically. This is the ultimate metaprogramming tool, letting you craft Domain-Specific Languages (DSLs) and perform advanced code transformations. Get ready to peek behind the curtain of Python's execution! ✨
```python
import ast
# 1. Our original Python code as a string
code_string = """
def calculate_tax(amount):
# This is a simple tax calculation
tax_rate = 0.05
tax = amount * tax_rate + 100
return tax
"""
# 2. Parse the code into an AST (Abstract Syntax Tree)
tree = ast.parse(code_string)
# 3. Define an AST transformer to modify the tree
class ConstantDoubler(ast.NodeTransformer):
"""
A transformer that finds all numeric constants (int, float)
and doubles their value.
"""
def visit_Constant(self, node):
if isinstance(node.value, (int, float)):
# Create a new Constant node with the doubled value
return ast.Constant(node.value * 2)
return node # Return node unchanged if not a number
# For Python versions older than 3.8, you might need to use visit_Num
# def visit_Num(self, node):
# return ast.Num(node.n * 2)
# 4. Apply the transformation
transformer = ConstantDoubler()
new_tree = transformer.visit(tree)
# 5. Fix line numbers and other metadata in the new tree
# This is important if you want to compile or execute the modified AST
ast.fix_missing_locations(new_tree)
# 6. Unparse the modified AST back into Python code (Python 3.9+)
modified_code = ast.unparse(new_tree)
print("--- Original Code ---")
print(code_string)
print("n--- Modified Code (Numbers Doubled) ---")
print(modified_code)
# You can even compile and execute the modified code!
# exec(compile(new_tree, filename='<ast>', mode='exec'))
```
Output example:
```
--- Original Code ---
def calculate_tax(amount):
# This is a simple tax calculation
tax_rate = 0.05
tax = amount * tax_rate + 100
return tax
--- Modified Code (Numbers Doubled) ---
def calculate_tax(amount):
# This is a simple tax calculation
tax_rate = 0.1
tax = amount * tax_rate + 200
return tax
```
💡 **Challenge:** Modify the `ConstantDoubler` transformer to instead replace all occurrences of the variable name `tax_rate` with `base_rate` within the AST, then unparse the result! (Hint: Look for `ast.Name` nodes and check their `id` attribute.)
---
🥳 **Day 50: The Invisible Hand! Unveiling Python's Descriptors!** 🚀
Ever wondered how `property()`, `classmethod()`, or `staticmethod()` work their magic behind the scenes? Meet *Descriptors* – Python's powerful, yet often overlooked, mechanism that defines how attributes are accessed, set, and deleted. ✨
Descriptors allow you to inject custom logic into attribute lookups, making your objects smarter and your code more robust. Today, we'll craft a `ValidatedProperty` to ensure data integrity with elegance. Let's unravel this fascinating internal mechanism! 🔍
```python
class ValidatedProperty:
"""
A descriptor that validates attribute values using a provided function.
"""
def __init__(self, validator=None):
self.validator = validator
self.private_name = None # Stores the actual attribute name prefixed with '_'
def __set_name__(self, owner, name):
"""
Called when the descriptor is assigned to a class attribute.
Helps in dynamically determining the internal storage name.
"""
self.private_name = '_' + name
def __get__(self, obj, objtype=None):
"""
Called when the attribute is accessed (e.g., instance.attribute).
"""
if obj is None:
return self # Accessing the descriptor from the class (e.g., Class.attribute)
if not hasattr(obj, self.private_name):
raise AttributeError(f"'{objtype.__name__}' object has no attribute '{self.private_name[1:]}'")
return getattr(obj, self.private_name)
def __set__(self, obj, value):
"""
Called when the attribute is assigned (e.g., instance.attribute = value).
"""
if self.validator:
if not self.validator(value):
raise ValueError(f"Invalid value for '{self.private_name[1:]}': {value}")
setattr(obj, self.private_name, value)
def __delete__(self, obj):
"""
Called when the attribute is deleted (e.g., del instance.attribute).
"""
delattr(obj, self.private_name)
# --- Usage Example ---
def is_positive_int(value):
return isinstance(value, int) and value > 0
def is_valid_email(email_str):
return isinstance(email_str, str) and "@" in email_str and "." in email_str
class User:
age = ValidatedProperty(is_positive_int)
email = ValidatedProperty(is_valid_email)
def __init__(self, age, email):
self.age = age
self.email = email
print("--- Creating valid user ---")
u1 = User(age=30, email="alice@example.com")
print(f"User 1: Age={u1.age}, Email='{u1.email}'")
print("n--- Trying to set invalid age (negative) ---")
try:
u1.age = -5
except ValueError as e:
print(f"Error: {e}")
print("n--- Trying to set invalid email (missing @) ---")
try:
u1.email = "bob_example.com"
except ValueError as e:
print(f"Error: {e}")
print("n--- Deleting an attribute ---")
del u1.age
try:
print(u1.age) # Accessing after deletion
except AttributeError as e:
print(f"Error: {e}")
```
**Output example:**
```
--- Creating valid user ---
User 1: Age=30, Email='alice@example.com'
--- Trying to set invalid age (negative) ---
Error: Invalid value for 'age': -5
--- Trying to set invalid email (missing @) ---
Error: Invalid value for 'email': bob_example.com
--- Deleting an attribute ---
Error: 'User' object has no attribute 'age'
```
💡 **Your Challenge:**
Modify our `ValidatedProperty`!
1. Enhance `__get__` to accept an optional `default_value` in the constructor. If the attribute hasn't been set on the instance (i.e., `hasattr(obj, self.private_name)` is False), return this `default_value` instead of raising an `AttributeError`.
2. Alternatively, create a brand new `TypeCheckedProperty` that only allows values of a specific `type` (e.g., `str`, `int`) to be assigned. Happy coding! 👩💻👨💻
---
Ever wondered how `property()`, `classmethod()`, or `staticmethod()` work their magic behind the scenes? Meet *Descriptors* – Python's powerful, yet often overlooked, mechanism that defines how attributes are accessed, set, and deleted. ✨
Descriptors allow you to inject custom logic into attribute lookups, making your objects smarter and your code more robust. Today, we'll craft a `ValidatedProperty` to ensure data integrity with elegance. Let's unravel this fascinating internal mechanism! 🔍
```python
class ValidatedProperty:
"""
A descriptor that validates attribute values using a provided function.
"""
def __init__(self, validator=None):
self.validator = validator
self.private_name = None # Stores the actual attribute name prefixed with '_'
def __set_name__(self, owner, name):
"""
Called when the descriptor is assigned to a class attribute.
Helps in dynamically determining the internal storage name.
"""
self.private_name = '_' + name
def __get__(self, obj, objtype=None):
"""
Called when the attribute is accessed (e.g., instance.attribute).
"""
if obj is None:
return self # Accessing the descriptor from the class (e.g., Class.attribute)
if not hasattr(obj, self.private_name):
raise AttributeError(f"'{objtype.__name__}' object has no attribute '{self.private_name[1:]}'")
return getattr(obj, self.private_name)
def __set__(self, obj, value):
"""
Called when the attribute is assigned (e.g., instance.attribute = value).
"""
if self.validator:
if not self.validator(value):
raise ValueError(f"Invalid value for '{self.private_name[1:]}': {value}")
setattr(obj, self.private_name, value)
def __delete__(self, obj):
"""
Called when the attribute is deleted (e.g., del instance.attribute).
"""
delattr(obj, self.private_name)
# --- Usage Example ---
def is_positive_int(value):
return isinstance(value, int) and value > 0
def is_valid_email(email_str):
return isinstance(email_str, str) and "@" in email_str and "." in email_str
class User:
age = ValidatedProperty(is_positive_int)
email = ValidatedProperty(is_valid_email)
def __init__(self, age, email):
self.age = age
self.email = email
print("--- Creating valid user ---")
u1 = User(age=30, email="alice@example.com")
print(f"User 1: Age={u1.age}, Email='{u1.email}'")
print("n--- Trying to set invalid age (negative) ---")
try:
u1.age = -5
except ValueError as e:
print(f"Error: {e}")
print("n--- Trying to set invalid email (missing @) ---")
try:
u1.email = "bob_example.com"
except ValueError as e:
print(f"Error: {e}")
print("n--- Deleting an attribute ---")
del u1.age
try:
print(u1.age) # Accessing after deletion
except AttributeError as e:
print(f"Error: {e}")
```
**Output example:**
```
--- Creating valid user ---
User 1: Age=30, Email='alice@example.com'
--- Trying to set invalid age (negative) ---
Error: Invalid value for 'age': -5
--- Trying to set invalid email (missing @) ---
Error: Invalid value for 'email': bob_example.com
--- Deleting an attribute ---
Error: 'User' object has no attribute 'age'
```
💡 **Your Challenge:**
Modify our `ValidatedProperty`!
1. Enhance `__get__` to accept an optional `default_value` in the constructor. If the attribute hasn't been set on the instance (i.e., `hasattr(obj, self.private_name)` is False), return this `default_value` instead of raising an `AttributeError`.
2. Alternatively, create a brand new `TypeCheckedProperty` that only allows values of a specific `type` (e.g., `str`, `int`) to be assigned. Happy coding! 👩💻👨💻
---
Which of the following special methods is *not* considered a fundamental part of the Python descriptor protocol for controlling attribute access?
Anonymous Quiz
0%
__get__
50%
__set__
0%
__call__
50%
__delete__
🌟 Day 53: Beyond Inheritance – Dive into Protocol-Oriented Design! 🚀
Tired of rigid class hierarchies? Today, we're unlocking the power of `typing.Protocol` to build incredibly flexible and pluggable APIs. This advanced concept lets you define "what an object can *do*" rather than "what it *is*," enabling true duck-typing with the safety net of static type checking. Craft systems that are easier to extend, test, and understand, fostering unparalleled modularity. Get ready to elevate your Python design patterns! 👇
```python
from typing import Protocol, List, Dict, Any
class DataSource(Protocol):
"""
A protocol defining the interface for any data source.
Objects adhering to this protocol can fetch and process data.
"""
def fetch_data(self, query: str) -> List[Dict[str, Any]]:
"""Fetches data based on a query."""
... # Ellipsis indicates an abstract method, to be implemented by concrete classes
def process_data(self, raw_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Processes raw data into a standardized format."""
...
class DatabaseSource:
"""A concrete data source interacting with a database."""
def fetch_data(self, query: str) -> List[Dict[str, Any]]:
print(f"🌐 Fetching from Database with query: '{query}'")
# Simulate fetching from a DB
if "users" in query:
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
return []
def process_data(self, raw_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
print("⚙️ Processing database data...")
return [{"db_id": item["id"], "db_name": item["name"].upper()} for item in raw_data]
class APISource:
"""A concrete data source interacting with an external API."""
def fetch_data(self, query: str) -> List[Dict[str, Any]]:
print(f"☁️ Fetching from API with query: '{query}'")
# Simulate fetching from an API
if "products" in query:
return [{"pid": "P001", "name": "Laptop", "price": 1200}, {"pid": "P002", "name": "Mouse", "price": 25}]
return []
def process_data(self, raw_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
print("⚙️ Processing API data...")
return [{"product_id": item["pid"], "product_name": item["name"].capitalize(), "price_usd": item["price"]} for item in raw_data]
def analyze_data(source: DataSource, query: str) -> None:
"""
A function that can work with ANY object that adheres to the DataSource protocol.
Note: DatabaseSource and APISource don't explicitly 'inherit' from DataSource,
but they implicitly implement its methods, satisfying the protocol! ✨
"""
print(f"n--- 📊 Analyzing data from {source.__class__.__name__} ---")
raw = source.fetch_data(query)
processed = source.process_data(raw)
print("Final Processed Data:", processed)
print("------------------------------------------")
# Demonstrate with different sources
db_source = DatabaseSource()
api_source = APISource()
analyze_data(db_source, "users")
analyze_data(api_source, "products")
```
Output example:
```
--- 📊 Analyzing data from DatabaseSource ---
🌐 Fetching from Database with query: 'users'
⚙️ Processing database data...
Final Processed Data: [{'db_id': 1, 'db_name': 'ALICE'}, {'db_id': 2, 'db_name': 'BOB'}]
------------------------------------------
--- 📊 Analyzing data from APISource ---
☁️ Fetching from API with query: 'products'
⚙️ Processing API data...
Final Processed Data: [{'product_id': 'P001', 'product_name': 'Laptop', 'price_usd': 1200}, {'product_id': 'P002', 'product_name': 'Mouse', 'price_usd': 25}]
------------------------------------------
```
🧠 **Small Challenge:**
Create a new `Loggable` protocol with a `log(message: str)` method. Implement this protocol in a `FileLogger` class (which prints to console for simplicity) and a `ConsoleLogger` class. Then, write a function `debug_message(logger: Loggable, msg: str)` that uses your protocol to send a debug message. How flexible is your new logging system? 🚀
Tired of rigid class hierarchies? Today, we're unlocking the power of `typing.Protocol` to build incredibly flexible and pluggable APIs. This advanced concept lets you define "what an object can *do*" rather than "what it *is*," enabling true duck-typing with the safety net of static type checking. Craft systems that are easier to extend, test, and understand, fostering unparalleled modularity. Get ready to elevate your Python design patterns! 👇
```python
from typing import Protocol, List, Dict, Any
class DataSource(Protocol):
"""
A protocol defining the interface for any data source.
Objects adhering to this protocol can fetch and process data.
"""
def fetch_data(self, query: str) -> List[Dict[str, Any]]:
"""Fetches data based on a query."""
... # Ellipsis indicates an abstract method, to be implemented by concrete classes
def process_data(self, raw_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Processes raw data into a standardized format."""
...
class DatabaseSource:
"""A concrete data source interacting with a database."""
def fetch_data(self, query: str) -> List[Dict[str, Any]]:
print(f"🌐 Fetching from Database with query: '{query}'")
# Simulate fetching from a DB
if "users" in query:
return [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
return []
def process_data(self, raw_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
print("⚙️ Processing database data...")
return [{"db_id": item["id"], "db_name": item["name"].upper()} for item in raw_data]
class APISource:
"""A concrete data source interacting with an external API."""
def fetch_data(self, query: str) -> List[Dict[str, Any]]:
print(f"☁️ Fetching from API with query: '{query}'")
# Simulate fetching from an API
if "products" in query:
return [{"pid": "P001", "name": "Laptop", "price": 1200}, {"pid": "P002", "name": "Mouse", "price": 25}]
return []
def process_data(self, raw_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
print("⚙️ Processing API data...")
return [{"product_id": item["pid"], "product_name": item["name"].capitalize(), "price_usd": item["price"]} for item in raw_data]
def analyze_data(source: DataSource, query: str) -> None:
"""
A function that can work with ANY object that adheres to the DataSource protocol.
Note: DatabaseSource and APISource don't explicitly 'inherit' from DataSource,
but they implicitly implement its methods, satisfying the protocol! ✨
"""
print(f"n--- 📊 Analyzing data from {source.__class__.__name__} ---")
raw = source.fetch_data(query)
processed = source.process_data(raw)
print("Final Processed Data:", processed)
print("------------------------------------------")
# Demonstrate with different sources
db_source = DatabaseSource()
api_source = APISource()
analyze_data(db_source, "users")
analyze_data(api_source, "products")
```
Output example:
```
--- 📊 Analyzing data from DatabaseSource ---
🌐 Fetching from Database with query: 'users'
⚙️ Processing database data...
Final Processed Data: [{'db_id': 1, 'db_name': 'ALICE'}, {'db_id': 2, 'db_name': 'BOB'}]
------------------------------------------
--- 📊 Analyzing data from APISource ---
☁️ Fetching from API with query: 'products'
⚙️ Processing API data...
Final Processed Data: [{'product_id': 'P001', 'product_name': 'Laptop', 'price_usd': 1200}, {'product_id': 'P002', 'product_name': 'Mouse', 'price_usd': 25}]
------------------------------------------
```
🧠 **Small Challenge:**
Create a new `Loggable` protocol with a `log(message: str)` method. Implement this protocol in a `FileLogger` class (which prints to console for simplicity) and a `ConsoleLogger` class. Then, write a function `debug_message(logger: Loggable, msg: str)` that uses your protocol to send a debug message. How flexible is your new logging system? 🚀
🎉 Day 54: Mastering Advanced Python! 🎉
Today, we're diving deep into a powerful feature that brings type-safety to Python's beloved duck typing: **Structural Subtyping with `Protocols`**. 🦆🛠️ `typing.Protocol` (PEP 544) allows you to formally define an interface based on a class's structure, not its inheritance. This means more robust static analysis, clearer code, and fewer runtime surprises, even when classes don't explicitly inherit from a common base. Let's make our "duck typing" explicitly checked! ✨
```python
from typing import Protocol
# 1. Define a Protocol
class Printable(Protocol):
"""
A protocol for objects that can print their details.
Must have a 'title' attribute and a 'print_details' method.
"""
title: str # Protocol expects this attribute
def print_details(self) -> None:
... # Ellipsis indicates no implementation required in the protocol
# 2. Implementations that implicitly conform to the Protocol
class Book:
def __init__(self, title: str, author: str):
self.title = title
self.author = author
def print_details(self) -> None:
print(f"Book: '{self.title}' by {self.author}")
class Movie:
def __init__(self, title: str, director: str, year: int):
self.title = title
self.director = director
self.year = year
def print_details(self) -> None:
print(f"Movie: '{self.title}' ({self.year}) directed by {self.director}")
# 3. A function that expects the Protocol type
def display_item(item: Printable) -> None:
print(f"--- Displaying '{item.title}' ---")
item.print_details()
print("--------------------")
# Using our conforming objects
my_book = Book("The Hitchhiker's Guide to the Galaxy", "Douglas Adams")
my_movie = Movie("Inception", "Christopher Nolan", 2010)
display_item(my_book)
display_item(my_movie)
# Example of what mypy would flag (if uncommented):
# class SimpleObject:
# # Missing 'print_details' method
# def __init__(self, title: str):
# self.title = title
#
# invalid_item = SimpleObject("Just a title")
# # mypy would error here: Argument "item" has incompatible type "SimpleObject"; expected "Printable"
# # display_item(invalid_item)
```
Output example:
```
--- Displaying 'The Hitchhiker's Guide to the Galaxy' ---
Book: 'The Hitchhiker's Guide to the Galaxy' by Douglas Adams
--------------------
--- Displaying 'Inception' ---
Movie: 'Inception' (2010) directed by Christopher Nolan
--------------------
```
**Small Challenge:**
Create a new class, `Song`, with `title` and `artist` attributes, and a `print_details` method. Pass an instance of `Song` to `display_item`. What happens if you remove the `print_details` method from `Song` and then run `mypy` on your code? (Hint: `pip install mypy` and then run `mypy your_script_name.py`)
---
Today, we're diving deep into a powerful feature that brings type-safety to Python's beloved duck typing: **Structural Subtyping with `Protocols`**. 🦆🛠️ `typing.Protocol` (PEP 544) allows you to formally define an interface based on a class's structure, not its inheritance. This means more robust static analysis, clearer code, and fewer runtime surprises, even when classes don't explicitly inherit from a common base. Let's make our "duck typing" explicitly checked! ✨
```python
from typing import Protocol
# 1. Define a Protocol
class Printable(Protocol):
"""
A protocol for objects that can print their details.
Must have a 'title' attribute and a 'print_details' method.
"""
title: str # Protocol expects this attribute
def print_details(self) -> None:
... # Ellipsis indicates no implementation required in the protocol
# 2. Implementations that implicitly conform to the Protocol
class Book:
def __init__(self, title: str, author: str):
self.title = title
self.author = author
def print_details(self) -> None:
print(f"Book: '{self.title}' by {self.author}")
class Movie:
def __init__(self, title: str, director: str, year: int):
self.title = title
self.director = director
self.year = year
def print_details(self) -> None:
print(f"Movie: '{self.title}' ({self.year}) directed by {self.director}")
# 3. A function that expects the Protocol type
def display_item(item: Printable) -> None:
print(f"--- Displaying '{item.title}' ---")
item.print_details()
print("--------------------")
# Using our conforming objects
my_book = Book("The Hitchhiker's Guide to the Galaxy", "Douglas Adams")
my_movie = Movie("Inception", "Christopher Nolan", 2010)
display_item(my_book)
display_item(my_movie)
# Example of what mypy would flag (if uncommented):
# class SimpleObject:
# # Missing 'print_details' method
# def __init__(self, title: str):
# self.title = title
#
# invalid_item = SimpleObject("Just a title")
# # mypy would error here: Argument "item" has incompatible type "SimpleObject"; expected "Printable"
# # display_item(invalid_item)
```
Output example:
```
--- Displaying 'The Hitchhiker's Guide to the Galaxy' ---
Book: 'The Hitchhiker's Guide to the Galaxy' by Douglas Adams
--------------------
--- Displaying 'Inception' ---
Movie: 'Inception' (2010) directed by Christopher Nolan
--------------------
```
**Small Challenge:**
Create a new class, `Song`, with `title` and `artist` attributes, and a `print_details` method. Pass an instance of `Song` to `display_item`. What happens if you remove the `print_details` method from `Song` and then run `mypy` on your code? (Hint: `pip install mypy` and then run `mypy your_script_name.py`)
---
What is the primary benefit of using `typing.Protocol` in Python?
Anonymous Quiz
0%
To enforce structural subtyping and provide better static type checking for duck-typed objects.
0%
To create abstract base classes that require explicit inheritance and method implementation.
0%
To define runtime interfaces that prevent method calls on non-conforming objects.
0%
To generate dynamic proxy classes that intercept method calls at runtime.
🚀 Day 56: Ever wondered how Python frameworks magically add methods or enforce rules on your classes *before* they even exist? Enter the "Invisible Hand" of Metaclasses! 👻 They're the ultimate runtime architects, allowing you to intercept and modify class creation itself. Today, we'll peel back the layers to see how metaclasses shape your code's behavior dynamically, going far beyond simple inheritance. Get ready to control the very definition of your classes! 🛠️
```python
class EnforcingMeta(type):
"""
A metaclass that enforces the presence of specific attributes
in any class that uses it.
"""
def __new__(mcs, name, bases, namespace):
# Get the list of required attributes from the class's namespace
required_attributes = namespace.get('REQUIRED_ATTRIBUTES', ())
# Ensure REQUIRED_ATTRIBUTES is a tuple or list
if not isinstance(required_attributes, (list, tuple)):
raise TypeError(
f"Class '{name}' using EnforcingMeta must define "
"'REQUIRED_ATTRIBUTES' as a list or tuple of strings."
)
# Check if all required attributes are present in the class's namespace
for attr in required_attributes:
if attr not in namespace:
raise AttributeError(
f"Class '{name}' is missing the required attribute: '{attr}'."
)
# Create the class using the standard type.__new__ if all checks pass
return super().__new__(mcs, name, bases, namespace)
# --- Example Usage ---
print("--- Creating ValidConfig ---")
class ValidConfig(metaclass=EnforcingMeta):
REQUIRED_ATTRIBUTES = ('database_url', 'api_key')
database_url = "sqlite:///app.db"
api_key = "your_secret_key"
def __init__(self, some_value):
self.some_value = some_value
config_instance = ValidConfig(123)
print(f"ValidConfig created successfully! DB URL: {config_instance.database_url}")
print(f"API Key: {config_instance.api_key}")
print("n")
print("--- Attempting to create InvalidConfig (expecting error) ---")
try:
class InvalidConfig(metaclass=EnforcingMeta):
REQUIRED_ATTRIBUTES = ('database_url', 'api_key', 'server_port')
database_url = "postgresql://user:pass@host/db"
api_key = "another_secret"
# 'server_port' is intentionally missing, causing an error
# This line should not be reached
print("InvalidConfig created unexpectedly!")
except AttributeError as e:
print(f"Caught expected error: {e}")
except TypeError as e:
print(f"Caught unexpected TypeError: {e}")
print("n--- Attempting to create MalformedConfig (expecting error for bad REQUIRED_ATTRIBUTES type) ---")
try:
class MalformedConfig(metaclass=EnforcingMeta):
# REQUIRED_ATTRIBUTES is not a tuple/list, causing a TypeError
REQUIRED_ATTRIBUTES = "database_url"
database_url = "mongo_url"
print("MalformedConfig created unexpectedly!")
except TypeError as e:
print(f"Caught expected error: {e}")
except AttributeError as e:
print(f"Caught unexpected AttributeError: {e}")
```
Output example:
```
--- Creating ValidConfig ---
ValidConfig created successfully! DB URL: sqlite:///app.db
API Key: your_secret_key
--- Attempting to create InvalidConfig (expecting error) ---
Caught expected error: Class 'InvalidConfig' is missing the required attribute: 'server_port'.
--- Attempting to create MalformedConfig (expecting error for bad REQUIRED_ATTRIBUTES type) ---
Caught expected error: Class 'MalformedConfig' using EnforcingMeta must define 'REQUIRED_ATTRIBUTES' as a list or tuple of strings.
```
💡 **Your Challenge:** Modify `EnforcingMeta` to automatically set a default value (e.g., `None` or an empty string) for any `REQUIRED_ATTRIBUTES` that are missing in the class, instead of raising an `AttributeError`. Alternatively, make the metaclass automatically register every class it creates into a global dictionary keyed by the class name.
---
```python
class EnforcingMeta(type):
"""
A metaclass that enforces the presence of specific attributes
in any class that uses it.
"""
def __new__(mcs, name, bases, namespace):
# Get the list of required attributes from the class's namespace
required_attributes = namespace.get('REQUIRED_ATTRIBUTES', ())
# Ensure REQUIRED_ATTRIBUTES is a tuple or list
if not isinstance(required_attributes, (list, tuple)):
raise TypeError(
f"Class '{name}' using EnforcingMeta must define "
"'REQUIRED_ATTRIBUTES' as a list or tuple of strings."
)
# Check if all required attributes are present in the class's namespace
for attr in required_attributes:
if attr not in namespace:
raise AttributeError(
f"Class '{name}' is missing the required attribute: '{attr}'."
)
# Create the class using the standard type.__new__ if all checks pass
return super().__new__(mcs, name, bases, namespace)
# --- Example Usage ---
print("--- Creating ValidConfig ---")
class ValidConfig(metaclass=EnforcingMeta):
REQUIRED_ATTRIBUTES = ('database_url', 'api_key')
database_url = "sqlite:///app.db"
api_key = "your_secret_key"
def __init__(self, some_value):
self.some_value = some_value
config_instance = ValidConfig(123)
print(f"ValidConfig created successfully! DB URL: {config_instance.database_url}")
print(f"API Key: {config_instance.api_key}")
print("n")
print("--- Attempting to create InvalidConfig (expecting error) ---")
try:
class InvalidConfig(metaclass=EnforcingMeta):
REQUIRED_ATTRIBUTES = ('database_url', 'api_key', 'server_port')
database_url = "postgresql://user:pass@host/db"
api_key = "another_secret"
# 'server_port' is intentionally missing, causing an error
# This line should not be reached
print("InvalidConfig created unexpectedly!")
except AttributeError as e:
print(f"Caught expected error: {e}")
except TypeError as e:
print(f"Caught unexpected TypeError: {e}")
print("n--- Attempting to create MalformedConfig (expecting error for bad REQUIRED_ATTRIBUTES type) ---")
try:
class MalformedConfig(metaclass=EnforcingMeta):
# REQUIRED_ATTRIBUTES is not a tuple/list, causing a TypeError
REQUIRED_ATTRIBUTES = "database_url"
database_url = "mongo_url"
print("MalformedConfig created unexpectedly!")
except TypeError as e:
print(f"Caught expected error: {e}")
except AttributeError as e:
print(f"Caught unexpected AttributeError: {e}")
```
Output example:
```
--- Creating ValidConfig ---
ValidConfig created successfully! DB URL: sqlite:///app.db
API Key: your_secret_key
--- Attempting to create InvalidConfig (expecting error) ---
Caught expected error: Class 'InvalidConfig' is missing the required attribute: 'server_port'.
--- Attempting to create MalformedConfig (expecting error for bad REQUIRED_ATTRIBUTES type) ---
Caught expected error: Class 'MalformedConfig' using EnforcingMeta must define 'REQUIRED_ATTRIBUTES' as a list or tuple of strings.
```
💡 **Your Challenge:** Modify `EnforcingMeta` to automatically set a default value (e.g., `None` or an empty string) for any `REQUIRED_ATTRIBUTES` that are missing in the class, instead of raising an `AttributeError`. Alternatively, make the metaclass automatically register every class it creates into a global dictionary keyed by the class name.
---
What is the primary purpose of a Python metaclass?
Anonymous Quiz
0%
To define static methods that belong to a class.
0%
To control and customize the creation of classes.
0%
To manage instances of a class after they are created.
0%
To handle exceptions that occur within class methods.
🚀 Day 57: Dive deep into Python's object model! Today, we're not just using `@property`, we're crafting the very fabric of attribute interaction. Get ready to master **Descriptors**! 🛠️ These powerful objects allow you to define custom logic for what happens when an attribute is accessed, modified, or deleted. Think robust validation, lazy loading, or even dynamic attribute generation, all neatly encapsulated. Elevate your object design skills and build truly sophisticated Python classes! ✨
```python
class AgeValidator:
"""
A descriptor to ensure an 'age' attribute is an integer and non-negative.
It manages the actual storage in a private attribute.
"""
def __set_name__(self, owner, name):
# This method is called when the descriptor is assigned to an attribute
# It's useful for knowing the name of the attribute it will manage.
self.public_name = name
self.private_name = '_' + name # Store the actual value privately
def __get__(self, obj, objtype=None):
if obj is None:
return self # Accessing the descriptor itself via the class
print(f"Accessing '{self.public_name}'...")
# Retrieve the value from the instance's private attribute
return getattr(obj, self.private_name)
def __set__(self, obj, value):
print(f"Attempting to set '{self.public_name}' to {value}...")
if not isinstance(value, int):
raise ValueError(f"'{self.public_name}' must be an integer.")
if value < 0:
raise ValueError(f"'{self.public_name}' cannot be negative.")
# Store the validated value in the instance's private attribute
setattr(obj, self.private_name, value)
print(f"Successfully set '{self.public_name}' to {value}.")
def __delete__(self, obj):
print(f"Deleting '{self.public_name}'...")
# Delete the private attribute from the instance
delattr(obj, self.private_name)
class Person:
age = AgeValidator() # Assigning the descriptor to the 'age' attribute
def __init__(self, name, age):
self.name = name
self.age = age # This assignment uses the descriptor's __set__
# --- Demonstrate usage ---
p = Person("Alice", 30)
print(f"Alice's age is currently: {p.age}n") # Access uses descriptor's __get__
# Try to set an invalid age
try:
p.age = -5
except ValueError as e:
print(f"Error: {e}n")
try:
p.age = "thirty"
except ValueError as e:
print(f"Error: {e}n")
print(f"Alice's age is still: {p.age}n") # It remains 30 because of validation
# Demonstrate deletion
del p.age # This uses the descriptor's __delete__
try:
print(p.age) # Accessing after deletion
except AttributeError as e:
print(f"Error: {e}n")
```
Output example:
```
Attempting to set 'age' to 30...
Successfully set 'age' to 30.
Accessing 'age'...
Alice's age is currently: 30
Attempting to set 'age' to -5...
Error: 'age' cannot be negative.
Attempting to set 'age' to thirty...
Error: 'age' must be an integer.
Accessing 'age'...
Alice's age is still: 30
Deleting 'age'...
Accessing 'age'...
Error: 'Person' object has no attribute '_age'
```
💡 **Challenge:** Enhance the `AgeValidator` descriptor!
1. Modify it to also validate that the `age` is within a reasonable range (e.g., 0-120).
2. Create another descriptor, `EmailValidator`, to ensure an email attribute contains both `@` and `.` characters. Then, use it in the `Person` class.
---
```python
class AgeValidator:
"""
A descriptor to ensure an 'age' attribute is an integer and non-negative.
It manages the actual storage in a private attribute.
"""
def __set_name__(self, owner, name):
# This method is called when the descriptor is assigned to an attribute
# It's useful for knowing the name of the attribute it will manage.
self.public_name = name
self.private_name = '_' + name # Store the actual value privately
def __get__(self, obj, objtype=None):
if obj is None:
return self # Accessing the descriptor itself via the class
print(f"Accessing '{self.public_name}'...")
# Retrieve the value from the instance's private attribute
return getattr(obj, self.private_name)
def __set__(self, obj, value):
print(f"Attempting to set '{self.public_name}' to {value}...")
if not isinstance(value, int):
raise ValueError(f"'{self.public_name}' must be an integer.")
if value < 0:
raise ValueError(f"'{self.public_name}' cannot be negative.")
# Store the validated value in the instance's private attribute
setattr(obj, self.private_name, value)
print(f"Successfully set '{self.public_name}' to {value}.")
def __delete__(self, obj):
print(f"Deleting '{self.public_name}'...")
# Delete the private attribute from the instance
delattr(obj, self.private_name)
class Person:
age = AgeValidator() # Assigning the descriptor to the 'age' attribute
def __init__(self, name, age):
self.name = name
self.age = age # This assignment uses the descriptor's __set__
# --- Demonstrate usage ---
p = Person("Alice", 30)
print(f"Alice's age is currently: {p.age}n") # Access uses descriptor's __get__
# Try to set an invalid age
try:
p.age = -5
except ValueError as e:
print(f"Error: {e}n")
try:
p.age = "thirty"
except ValueError as e:
print(f"Error: {e}n")
print(f"Alice's age is still: {p.age}n") # It remains 30 because of validation
# Demonstrate deletion
del p.age # This uses the descriptor's __delete__
try:
print(p.age) # Accessing after deletion
except AttributeError as e:
print(f"Error: {e}n")
```
Output example:
```
Attempting to set 'age' to 30...
Successfully set 'age' to 30.
Accessing 'age'...
Alice's age is currently: 30
Attempting to set 'age' to -5...
Error: 'age' cannot be negative.
Attempting to set 'age' to thirty...
Error: 'age' must be an integer.
Accessing 'age'...
Alice's age is still: 30
Deleting 'age'...
Accessing 'age'...
Error: 'Person' object has no attribute '_age'
```
💡 **Challenge:** Enhance the `AgeValidator` descriptor!
1. Modify it to also validate that the `age` is within a reasonable range (e.g., 0-120).
2. Create another descriptor, `EmailValidator`, to ensure an email attribute contains both `@` and `.` characters. Then, use it in the `Person` class.
---
What is the primary purpose of a Python descriptor?
Anonymous Quiz
0%
To define custom logic for attribute access (get, set, delete) for managed attributes.
0%
To create static methods and class methods within a class.
0%
To serialize and deserialize objects to and from JSON.
0%
To implement abstract base classes.
Here's your Telegram post for Day 58!
---
**Day 58: The Dynamic Type Factory: Metaclasses as Code Generators and Language Extensions** ✨
What if you could control how classes are born? 🤯 Metaclasses are Python's ultimate "type factory," allowing you to programmatically define how new classes are created. Forget `__init__`, we're talking about `__new__` for classes! On Day 58, we dive into how metaclasses can generate code, enforce patterns, and even extend Python's syntax, turning your classes into dynamic powerhouses. Get ready to build your own mini-language right within Python! 🚀
```python
# Day 58: The Dynamic Type Factory: Metaclasses as Code Generators
class AutoBoilerplateMeta(type):
"""
A metaclass that automatically adds a '_version' attribute and
a custom __str__ method to any class it creates, acting as
boilerplate code generation.
"""
def __new__(mcs, name, bases, namespace):
# 1. Inject a default attribute if not present
namespace['_version'] = namespace.get('_version', "1.0.0")
# 2. Inject a custom __str__ method if not already defined
if '__str__' not in namespace:
def custom_str(self):
return f"<{self.__class__.__name__} (v{self._version}) object>"
namespace['__str__'] = custom_str
# 3. Add a class method for introspection
if 'get_class_info' not in namespace:
@classmethod
def get_class_info(cls):
return f"Class '{cls.__name__}' created by AutoBoilerplateMeta (version: {cls._version})"
namespace['get_class_info'] = get_class_info
# Finally, call the base type.__new__ to actually create the class
cls = super().__new__(mcs, name, bases, namespace)
print(f"Metaclass created class: {cls.__name__}")
return cls
class MyService(metaclass=AutoBoilerplateMeta):
"""A service benefiting from metaclass-injected boilerplate."""
def __init__(self, task):
self.task = task
def execute(self):
return f"Executing '{self.task}' with service version {self._version}"
class DatabaseConnector(metaclass=AutoBoilerplateMeta):
"""Another component showing injected attributes and methods."""
# We can override _version here, if needed
_version = "2.1.0-alpha"
def __init__(self, config_str):
self.config = config_str
# --- Example Usage ---
service_instance = MyService("data processing")
db_instance = DatabaseConnector("prod_db_config")
print("n--- Instances ---")
print(service_instance)
print(service_instance.execute())
print(f"MyService class info: {MyService.get_class_info()}")
print("n--- Another Instance ---")
print(db_instance)
print(f"DatabaseConnector has version: {db_instance._version}")
print(f"DatabaseConnector class info: {DatabaseConnector.get_class_info()}")
# Notice how DatabaseConnector overrode the default _version, but still got the __str__ and get_class_info!
```
Output example:
```
Metaclass created class: MyService
Metaclass created class: DatabaseConnector
--- Instances ---
<MyService (v1.0.0) object>
Executing 'data processing' with service version 1.0.0
MyService class info: Class 'MyService' created by AutoBoilerplateMeta (version: 1.0.0)
--- Another Instance ---
<DatabaseConnector (v2.1.0-alpha) object>
DatabaseConnector has version: 2.1.0-alpha
DatabaseConnector class info: Class 'DatabaseConnector' created by AutoBoilerplateMeta (version: 2.1.0-alpha)
```
**Small Challenge:**
Can you modify `AutoBoilerplateMeta` to automatically add a *warning* to the `get_class_info` output if a class has *not* explicitly defined its `_version` attribute (i.e., it's using the default one from the metaclass)? 🕵️♀️
---
---
**Day 58: The Dynamic Type Factory: Metaclasses as Code Generators and Language Extensions** ✨
What if you could control how classes are born? 🤯 Metaclasses are Python's ultimate "type factory," allowing you to programmatically define how new classes are created. Forget `__init__`, we're talking about `__new__` for classes! On Day 58, we dive into how metaclasses can generate code, enforce patterns, and even extend Python's syntax, turning your classes into dynamic powerhouses. Get ready to build your own mini-language right within Python! 🚀
```python
# Day 58: The Dynamic Type Factory: Metaclasses as Code Generators
class AutoBoilerplateMeta(type):
"""
A metaclass that automatically adds a '_version' attribute and
a custom __str__ method to any class it creates, acting as
boilerplate code generation.
"""
def __new__(mcs, name, bases, namespace):
# 1. Inject a default attribute if not present
namespace['_version'] = namespace.get('_version', "1.0.0")
# 2. Inject a custom __str__ method if not already defined
if '__str__' not in namespace:
def custom_str(self):
return f"<{self.__class__.__name__} (v{self._version}) object>"
namespace['__str__'] = custom_str
# 3. Add a class method for introspection
if 'get_class_info' not in namespace:
@classmethod
def get_class_info(cls):
return f"Class '{cls.__name__}' created by AutoBoilerplateMeta (version: {cls._version})"
namespace['get_class_info'] = get_class_info
# Finally, call the base type.__new__ to actually create the class
cls = super().__new__(mcs, name, bases, namespace)
print(f"Metaclass created class: {cls.__name__}")
return cls
class MyService(metaclass=AutoBoilerplateMeta):
"""A service benefiting from metaclass-injected boilerplate."""
def __init__(self, task):
self.task = task
def execute(self):
return f"Executing '{self.task}' with service version {self._version}"
class DatabaseConnector(metaclass=AutoBoilerplateMeta):
"""Another component showing injected attributes and methods."""
# We can override _version here, if needed
_version = "2.1.0-alpha"
def __init__(self, config_str):
self.config = config_str
# --- Example Usage ---
service_instance = MyService("data processing")
db_instance = DatabaseConnector("prod_db_config")
print("n--- Instances ---")
print(service_instance)
print(service_instance.execute())
print(f"MyService class info: {MyService.get_class_info()}")
print("n--- Another Instance ---")
print(db_instance)
print(f"DatabaseConnector has version: {db_instance._version}")
print(f"DatabaseConnector class info: {DatabaseConnector.get_class_info()}")
# Notice how DatabaseConnector overrode the default _version, but still got the __str__ and get_class_info!
```
Output example:
```
Metaclass created class: MyService
Metaclass created class: DatabaseConnector
--- Instances ---
<MyService (v1.0.0) object>
Executing 'data processing' with service version 1.0.0
MyService class info: Class 'MyService' created by AutoBoilerplateMeta (version: 1.0.0)
--- Another Instance ---
<DatabaseConnector (v2.1.0-alpha) object>
DatabaseConnector has version: 2.1.0-alpha
DatabaseConnector class info: Class 'DatabaseConnector' created by AutoBoilerplateMeta (version: 2.1.0-alpha)
```
**Small Challenge:**
Can you modify `AutoBoilerplateMeta` to automatically add a *warning* to the `get_class_info` output if a class has *not* explicitly defined its `_version` attribute (i.e., it's using the default one from the metaclass)? 🕵️♀️
---
What is the primary role of a metaclass in Python?
Anonymous Quiz
0%
To define the instance methods and attributes for objects.
0%
To control and customize the creation process of classes themselves.
100%
To manage memory allocation and deallocation for Python objects.
0%
To implement asynchronous programming patterns using `asyncio`.