Which of the following built-in Python features *primarily* relies on the descriptor protocol for its functionality?
Anonymous Quiz
0%
A) The `property()` decorator
0%
B) The `__getattr__` special method
0%
C) The `try...except` block for error handling
0%
D) List comprehensions
🚀 **Day 40: Synthesizing Reality - Dynamic Code Generation with AST Manipulation!** 🤯
We've reached the apex! For our grand finale, let's explore one of Python's most mind-bending capabilities: **dynamic code generation**. Forget just defining classes; today, we're building and executing code *from scratch* at runtime using Python's Abstract Syntax Tree (AST) and the mighty `exec` function. This isn't just theory; it's the foundation for DSLs, high-performance runtime optimizations, and even self-modifying programs! Are you ready to dive into the ultimate metaprogramming challenge? 🛠️✨
```python
import ast
import inspect
import sys
# Our goal: Dynamically create a function like this:
# def greet_dynamic(name):
# print(f"Hello, {name}! This code was generated on the fly.")
# Step 1: Build the Abstract Syntax Tree (AST) for the function.
# This part manually constructs the AST nodes representing the function.
func_name_node = ast.Name(id='greet_dynamic', ctx=ast.Store())
arg_node = ast.arg(arg='name', annotation=None)
args_node = ast.arguments(posonlyargs=[], args=[arg_node], vararg=None,
kwonlyargs=[], kw_defaults=[], defaults=[])
# The f-string: f"Hello, {name}! This code was generated on the fly."
f_string_parts = [
ast.Constant(value="Hello, "),
ast.FormattedValue(value=ast.Name(id='name', ctx=ast.Load()), conversion=-1, format_spec=None),
ast.Constant(value="! This code was generated on the fly.")
]
f_string_node = ast.JoinedStr(values=f_string_parts)
# The print statement: print(...)
print_call_node = ast.Expr(value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[f_string_node],
keywords=[]
))
# The function body
func_body_nodes = [print_call_node]
# The function definition node
func_def_node = ast.FunctionDef(
name=func_name_node.id,
args=args_node,
body=func_body_nodes,
decorator_list=[],
returns=None,
type_comment=None
)
# Step 2: Create a module AST that contains our function definition.
module_node = ast.Module(body=[func_def_node], type_ignores=[])
# Step 3: Compile the AST into a code object.
# The '<string>' argument is the filename for traceback purposes.
compiled_code_obj = compile(module_node, '<string>', 'exec')
# Step 4: Execute the compiled code in a new environment.
# `exec_env` will store the definitions created by `exec`.
exec_env = {}
exec(compiled_code_obj, exec_env)
# Step 5: Retrieve and use the dynamically created function.
dynamic_function = exec_env['greet_dynamic']
print("--- Dynamically Generated Code ---")
# Use ast.unparse (Python 3.9+) to show what the AST represents
# For older Python versions, this part needs manual reconstruction or a custom visitor.
if sys.version_info >= (3, 9):
print(ast.unparse(module_node))
else:
# Fallback for Python versions < 3.9
print("# ast.unparse requires Python 3.9+ to directly visualize the AST.")
print("def greet_dynamic(name):")
print(" print(f"Hello, {name}! This code was generated on the fly.")")
print("----------------------------------")
print("n--- Executing Generated Function ---")
# Call the function
dynamic_function("Pythonista")
```
Output example:
```
--- Dynamically Generated Code ---
def greet_dynamic(name):
print(f'Hello, {name}! This code was generated on the fly.')
----------------------------------
--- Executing Generated Function ---
Hello, Pythonista! This code was generated on the fly.
```
✨ **Challenge for Day 40:**
Can you modify the AST to make `greet_dynamic` also print the current timestamp *before* its greeting? (Hint: You'll need to add an `ast.Import` for `datetime` and an additional `ast.Expr` node containing a call to `datetime.datetime.now()` to the function body! Remember to add the `ast.Import` at the module level.)
We've reached the apex! For our grand finale, let's explore one of Python's most mind-bending capabilities: **dynamic code generation**. Forget just defining classes; today, we're building and executing code *from scratch* at runtime using Python's Abstract Syntax Tree (AST) and the mighty `exec` function. This isn't just theory; it's the foundation for DSLs, high-performance runtime optimizations, and even self-modifying programs! Are you ready to dive into the ultimate metaprogramming challenge? 🛠️✨
```python
import ast
import inspect
import sys
# Our goal: Dynamically create a function like this:
# def greet_dynamic(name):
# print(f"Hello, {name}! This code was generated on the fly.")
# Step 1: Build the Abstract Syntax Tree (AST) for the function.
# This part manually constructs the AST nodes representing the function.
func_name_node = ast.Name(id='greet_dynamic', ctx=ast.Store())
arg_node = ast.arg(arg='name', annotation=None)
args_node = ast.arguments(posonlyargs=[], args=[arg_node], vararg=None,
kwonlyargs=[], kw_defaults=[], defaults=[])
# The f-string: f"Hello, {name}! This code was generated on the fly."
f_string_parts = [
ast.Constant(value="Hello, "),
ast.FormattedValue(value=ast.Name(id='name', ctx=ast.Load()), conversion=-1, format_spec=None),
ast.Constant(value="! This code was generated on the fly.")
]
f_string_node = ast.JoinedStr(values=f_string_parts)
# The print statement: print(...)
print_call_node = ast.Expr(value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[f_string_node],
keywords=[]
))
# The function body
func_body_nodes = [print_call_node]
# The function definition node
func_def_node = ast.FunctionDef(
name=func_name_node.id,
args=args_node,
body=func_body_nodes,
decorator_list=[],
returns=None,
type_comment=None
)
# Step 2: Create a module AST that contains our function definition.
module_node = ast.Module(body=[func_def_node], type_ignores=[])
# Step 3: Compile the AST into a code object.
# The '<string>' argument is the filename for traceback purposes.
compiled_code_obj = compile(module_node, '<string>', 'exec')
# Step 4: Execute the compiled code in a new environment.
# `exec_env` will store the definitions created by `exec`.
exec_env = {}
exec(compiled_code_obj, exec_env)
# Step 5: Retrieve and use the dynamically created function.
dynamic_function = exec_env['greet_dynamic']
print("--- Dynamically Generated Code ---")
# Use ast.unparse (Python 3.9+) to show what the AST represents
# For older Python versions, this part needs manual reconstruction or a custom visitor.
if sys.version_info >= (3, 9):
print(ast.unparse(module_node))
else:
# Fallback for Python versions < 3.9
print("# ast.unparse requires Python 3.9+ to directly visualize the AST.")
print("def greet_dynamic(name):")
print(" print(f"Hello, {name}! This code was generated on the fly.")")
print("----------------------------------")
print("n--- Executing Generated Function ---")
# Call the function
dynamic_function("Pythonista")
```
Output example:
```
--- Dynamically Generated Code ---
def greet_dynamic(name):
print(f'Hello, {name}! This code was generated on the fly.')
----------------------------------
--- Executing Generated Function ---
Hello, Pythonista! This code was generated on the fly.
```
✨ **Challenge for Day 40:**
Can you modify the AST to make `greet_dynamic` also print the current timestamp *before* its greeting? (Hint: You'll need to add an `ast.Import` for `datetime` and an additional `ast.Expr` node containing a call to `datetime.datetime.now()` to the function body! Remember to add the `ast.Import` at the module level.)
What is the primary purpose of Python's `ast` module?
Anonymous Quiz
0%
To execute Python code in a sandboxed environment.
0%
To represent Python code as an Abstract Syntax Tree for analysis and modification.
0%
To convert Python source code directly into bytecode for faster execution.
0%
To manage package dependencies and virtual environments.
Which of the following methods are part of Python's Descriptor Protocol, allowing customization of attribute access?
Anonymous Quiz
0%
A. `__iter__`, `__next__`
0%
B. `__enter__`, `__exit__`
0%
C. `__get__`, `__set__`, `__delete__`
0%
D. `__str__`, `__repr__`
Here's your Telegram post for Day 43!
---
🌟 **Day 43: The Zen of Descriptors - Crafting Intuitive & Efficient Attribute Access** 🌟
Ever wondered how `@property`, methods, or even `staticmethod`s work their magic under the hood? ✨ The secret often lies with **descriptors**! These powerful Python objects allow you to customize attribute access, setting, and deletion for class instances.
Descriptors empower you to design cleaner APIs, enforce validation rules, implement caching, or create sophisticated attribute behaviors with elegant reuse. It's a key concept for building robust and "Pythonic" code, often used implicitly in popular frameworks!
Let's dive into creating our own descriptor for validated attributes! 👇
```python
import numbers
class ValidatedNumber:
"""A descriptor that ensures an attribute is a positive number."""
def __set_name__(self, owner, name):
# __set_name__ is called when the descriptor is assigned to a class attribute.
# It's perfect for knowing the attribute's name in the owner class.
self.public_name = name
self.private_name = '_' + name # Store value in a private attribute
def __get__(self, obj, obj_type=None):
if obj is None: # Accessing descriptor directly from class (e.g., Product.price)
return self
return getattr(obj, self.private_name)
def __set__(self, obj, value):
if not isinstance(value, numbers.Number):
raise TypeError(f"'{self.public_name}' must be a number.")
if value <= 0:
raise ValueError(f"'{self.public_name}' must be positive.")
setattr(obj, self.private_name, value)
def __delete__(self, obj):
delattr(obj, self.private_name)
class Product:
price = ValidatedNumber()
quantity = ValidatedNumber()
def __init__(self, price: float, quantity: int):
self.price = price
self.quantity = quantity
def get_total_cost(self) -> float:
return self.price * self.quantity
```
**Output example:**
```
# ✅ Valid product creation
laptop = Product(price=1200.50, quantity=2)
print(f"Laptop cost: ${laptop.get_total_cost()}")
print(f"Laptop price: {laptop.price}, quantity: {laptop.quantity}")
# ❌ Trying to set an invalid price (negative)
try:
phone = Product(price=-500, quantity=1)
except ValueError as e:
print(f"nError creating phone: {e}")
# ❌ Trying to set an invalid quantity (not a number)
try:
tablet = Product(price=300, quantity="one")
except TypeError as e:
print(f"nError creating tablet: {e}")
# 🔄 Modifying an existing attribute with invalid value
try:
laptop.price = 0
except ValueError as e:
print(f"nError modifying laptop price: {e}")
# 🗑️ Deleting an attribute
del laptop.quantity
try:
print(laptop.quantity)
except AttributeError as e:
print(f"nError accessing deleted quantity: {e}")
```
**Output:**
```
Laptop cost: $2401.0
Laptop price: 1200.5, quantity: 2
Error creating phone: 'price' must be positive.
Error creating tablet: 'quantity' must be a number.
Error modifying laptop price: 'price' must be positive.
Error accessing deleted quantity: '_quantity'
```
---
💡 **Small Challenge:**
Enhance the `ValidatedNumber` descriptor! Modify it so that it can accept an optional `max_value` argument during its creation (e.g., `price = ValidatedNumber(max_value=5000)`). Ensure that the set value is always less than or equal to `max_value` if it's provided. Good luck! 🚀
---
---
🌟 **Day 43: The Zen of Descriptors - Crafting Intuitive & Efficient Attribute Access** 🌟
Ever wondered how `@property`, methods, or even `staticmethod`s work their magic under the hood? ✨ The secret often lies with **descriptors**! These powerful Python objects allow you to customize attribute access, setting, and deletion for class instances.
Descriptors empower you to design cleaner APIs, enforce validation rules, implement caching, or create sophisticated attribute behaviors with elegant reuse. It's a key concept for building robust and "Pythonic" code, often used implicitly in popular frameworks!
Let's dive into creating our own descriptor for validated attributes! 👇
```python
import numbers
class ValidatedNumber:
"""A descriptor that ensures an attribute is a positive number."""
def __set_name__(self, owner, name):
# __set_name__ is called when the descriptor is assigned to a class attribute.
# It's perfect for knowing the attribute's name in the owner class.
self.public_name = name
self.private_name = '_' + name # Store value in a private attribute
def __get__(self, obj, obj_type=None):
if obj is None: # Accessing descriptor directly from class (e.g., Product.price)
return self
return getattr(obj, self.private_name)
def __set__(self, obj, value):
if not isinstance(value, numbers.Number):
raise TypeError(f"'{self.public_name}' must be a number.")
if value <= 0:
raise ValueError(f"'{self.public_name}' must be positive.")
setattr(obj, self.private_name, value)
def __delete__(self, obj):
delattr(obj, self.private_name)
class Product:
price = ValidatedNumber()
quantity = ValidatedNumber()
def __init__(self, price: float, quantity: int):
self.price = price
self.quantity = quantity
def get_total_cost(self) -> float:
return self.price * self.quantity
```
**Output example:**
```
# ✅ Valid product creation
laptop = Product(price=1200.50, quantity=2)
print(f"Laptop cost: ${laptop.get_total_cost()}")
print(f"Laptop price: {laptop.price}, quantity: {laptop.quantity}")
# ❌ Trying to set an invalid price (negative)
try:
phone = Product(price=-500, quantity=1)
except ValueError as e:
print(f"nError creating phone: {e}")
# ❌ Trying to set an invalid quantity (not a number)
try:
tablet = Product(price=300, quantity="one")
except TypeError as e:
print(f"nError creating tablet: {e}")
# 🔄 Modifying an existing attribute with invalid value
try:
laptop.price = 0
except ValueError as e:
print(f"nError modifying laptop price: {e}")
# 🗑️ Deleting an attribute
del laptop.quantity
try:
print(laptop.quantity)
except AttributeError as e:
print(f"nError accessing deleted quantity: {e}")
```
**Output:**
```
Laptop cost: $2401.0
Laptop price: 1200.5, quantity: 2
Error creating phone: 'price' must be positive.
Error creating tablet: 'quantity' must be a number.
Error modifying laptop price: 'price' must be positive.
Error accessing deleted quantity: '_quantity'
```
---
💡 **Small Challenge:**
Enhance the `ValidatedNumber` descriptor! Modify it so that it can accept an optional `max_value` argument during its creation (e.g., `price = ValidatedNumber(max_value=5000)`). Ensure that the set value is always less than or equal to `max_value` if it's provided. Good luck! 🚀
---
Which special method in a descriptor class is primarily responsible for receiving the name of the attribute it's assigned to in the owner class?
Anonymous Quiz
0%
A. `__get__`
100%
B. `__set__`
0%
C. `__set_name__`
0%
D. `__init__`
Which of the following special methods is NOT a core part of Python's descriptor protocol for controlling attribute access?
Anonymous Quiz
0%
__get__
0%
__set__
0%
__call__
0%
__delete__
🚀 Day 45: Ever wondered how `property()`, `classmethod()`, or `staticmethod()` work their magic in Python? ✨ Dive deep with us into the #DescriptorProtocol! This powerful, often-hidden mechanism is the secret sauce behind attribute access. Understanding descriptors unlocks a new level of control over how attributes behave, allowing you to create highly customized and reusable attribute logic. Let's peel back the curtain and see how you can harness this core Python feature! 🐍💡
```python
class ReadOnly:
"""
A descriptor that makes an attribute read-only after its initial assignment.
"""
def __init__(self, initial_value=None):
self.initial_value = initial_value
self._name = None # To be set by __set_name__
def __set_name__(self, owner, name):
# This method is called automatically when the descriptor is assigned
# to a class attribute. 'name' will be the attribute name (e.g., 'id').
self._name = name
def __get__(self, obj, obj_type=None):
if obj is None:
# Accessing from class, return the descriptor itself
return self
# Accessing from instance, return the instance's private attribute
# If not yet set, return the descriptor's initial_value
return getattr(obj, f'_{self._name}', self.initial_value)
def __set__(self, obj, value):
# Check if the attribute has already been set on the instance
if hasattr(obj, f'_{self._name}'):
raise AttributeError(f"'{self._name}' is a read-only attribute and cannot be modified.")
# Initial assignment
setattr(obj, f'_{self._name}', value)
class Product:
# 'product_id' and 'name' are read-only attributes using our descriptor
product_id = ReadOnly()
name = ReadOnly()
def __init__(self, p_id, p_name):
self.product_id = p_id # Calls ReadOnly.__set__ for initial assignment
self.name = p_name # Calls ReadOnly.__set__ for initial assignment
# --- Demonstration ---
print("Creating product1...")
product1 = Product("P001", "Laptop Pro")
print(f"Product1 ID: {product1.product_id}")
print(f"Product1 Name: {product1.name}")
print("nAttempting to modify product_id:")
try:
product1.product_id = "P002"
except AttributeError as e:
print(f"Error: {e}")
print("nCreating product2...")
product2 = Product("P003", "Mouse MX")
print(f"Product2 ID: {product2.product_id}")
print("nAccessing the descriptor directly from the class (not an instance):")
print(f"Product.product_id: {Product.product_id}")
```
Output example:
```
Creating product1...
Product1 ID: P001
Product1 Name: Laptop Pro
Attempting to modify product_id:
Error: 'product_id' is a read-only attribute and cannot be modified.
Creating product2...
Product2 ID: P003
Accessing the descriptor directly from the class (not an instance):
Product.product_id: <__main__.ReadOnly object at 0x...>
```
**Small challenge:**
Your challenge: Modify the `ReadOnly` descriptor to accept a `validator` function in its `__init__`. This `validator` function should be called within the `__set__` method *before* assigning the value, raising an error if the validation fails. This would allow for more complex validation logic beyond just read-only behavior!
---
```python
class ReadOnly:
"""
A descriptor that makes an attribute read-only after its initial assignment.
"""
def __init__(self, initial_value=None):
self.initial_value = initial_value
self._name = None # To be set by __set_name__
def __set_name__(self, owner, name):
# This method is called automatically when the descriptor is assigned
# to a class attribute. 'name' will be the attribute name (e.g., 'id').
self._name = name
def __get__(self, obj, obj_type=None):
if obj is None:
# Accessing from class, return the descriptor itself
return self
# Accessing from instance, return the instance's private attribute
# If not yet set, return the descriptor's initial_value
return getattr(obj, f'_{self._name}', self.initial_value)
def __set__(self, obj, value):
# Check if the attribute has already been set on the instance
if hasattr(obj, f'_{self._name}'):
raise AttributeError(f"'{self._name}' is a read-only attribute and cannot be modified.")
# Initial assignment
setattr(obj, f'_{self._name}', value)
class Product:
# 'product_id' and 'name' are read-only attributes using our descriptor
product_id = ReadOnly()
name = ReadOnly()
def __init__(self, p_id, p_name):
self.product_id = p_id # Calls ReadOnly.__set__ for initial assignment
self.name = p_name # Calls ReadOnly.__set__ for initial assignment
# --- Demonstration ---
print("Creating product1...")
product1 = Product("P001", "Laptop Pro")
print(f"Product1 ID: {product1.product_id}")
print(f"Product1 Name: {product1.name}")
print("nAttempting to modify product_id:")
try:
product1.product_id = "P002"
except AttributeError as e:
print(f"Error: {e}")
print("nCreating product2...")
product2 = Product("P003", "Mouse MX")
print(f"Product2 ID: {product2.product_id}")
print("nAccessing the descriptor directly from the class (not an instance):")
print(f"Product.product_id: {Product.product_id}")
```
Output example:
```
Creating product1...
Product1 ID: P001
Product1 Name: Laptop Pro
Attempting to modify product_id:
Error: 'product_id' is a read-only attribute and cannot be modified.
Creating product2...
Product2 ID: P003
Accessing the descriptor directly from the class (not an instance):
Product.product_id: <__main__.ReadOnly object at 0x...>
```
**Small challenge:**
Your challenge: Modify the `ReadOnly` descriptor to accept a `validator` function in its `__init__`. This `validator` function should be called within the `__set__` method *before* assigning the value, raising an error if the validation fails. This would allow for more complex validation logic beyond just read-only behavior!
---
Which of the following methods is called when you *access* an attribute (e.g., `obj.attribute`) that is managed by a descriptor in Python?
Anonymous Quiz
0%
__get__(self, obj, obj_type)
0%
__set__(self, obj, value)
0%
__call__(self, *args, **kwargs)
0%
__getattr__(self, name)
What is the primary purpose of a descriptor's `__set_name__` method in Python?
Anonymous Quiz
0%
A) To set the default value of the attribute it manages.
0%
B) To assign the name of the attribute the descriptor is assigned to in the owner class.
0%
C) To define how the descriptor's value is retrieved when accessed.
0%
D) To handle the deletion of the attribute from the owner instance.
🚀 Day 47 Alert! Ready to dive deep into Python's core? Today, we're unveiling a truly powerful "secret weapon": **Abstract Syntax Tree (AST) Manipulation**! 🤯 Forget just writing code; what if you could inspect, modify, or even *generate* Python code at runtime? This technique is the foundation for linters, static analyzers, code transformers, and even building your own Domain-Specific Languages (DSLs)! Get ready to become a Python sorcerer. ✨
```python
import ast
# 1. Define a string with our target Python code
code_str = """
def apply_op(a, b):
result = a + b
return result
"""
print(f"Original code string defines addition.")
# 2. Parse the code string into an Abstract Syntax Tree (AST)
tree = ast.parse(code_str)
# 3. Define an AST NodeTransformer to modify the tree
class AddToMulTransformer(ast.NodeTransformer):
def visit_BinOp(self, node):
# If the binary operation is addition, change it to multiplication
if isinstance(node.op, ast.Add):
print(f" --> Found '+' operation, changing to '*'")
node.op = ast.Mult()
# Ensure we visit child nodes to catch nested operations if any
return self.generic_visit(node)
# 4. Apply the transformation
transformer = AddToMulTransformer()
modified_tree = transformer.visit(tree)
# 5. Compile the modified AST into executable code
# The '<string>' argument is a filename for debugging purposes
# 'exec' mode means it's a sequence of statements
compiled_code = compile(modified_tree, '<transformed_code_string>', 'exec')
# 6. Execute the compiled code in a new, isolated namespace
transformed_namespace = {}
# Using empty globals/locals for isolation ensures our new function doesn't clash
exec(compiled_code, {}, transformed_namespace)
# 7. Retrieve the transformed function and call it
transformed_func = transformed_namespace['apply_op']
print(f"After AST transformation:")
print(f"apply_op(5, 3) now yields: {transformed_func(5, 3)}")
print("n--- Verifying AST Change ---")
# Optional: Inspect the original and transformed ASTs to confirm the change
original_tree = ast.parse(code_str)
# We need to find the specific operation node for clarity
for node in original_tree.body:
if isinstance(node, ast.FunctionDef) and node.name == 'apply_op':
for stmt in node.body:
if isinstance(stmt, ast.Assign) and
any(isinstance(t, ast.Name) and t.id == 'result' for t in stmt.targets) and
isinstance(stmt.value, ast.BinOp):
print(f"Original AST operation for 'result': {type(stmt.value.op).__name__}")
break
break
for node in modified_tree.body:
if isinstance(node, ast.FunctionDef) and node.name == 'apply_op':
for stmt in node.body:
if isinstance(stmt, ast.Assign) and
any(isinstance(t, ast.Name) and t.id == 'result' for t in stmt.targets) and
isinstance(stmt.value, ast.BinOp):
print(f"Transformed AST operation for 'result': {type(stmt.value.op).__name__}")
break
break
```
Output example:
```
Original code string defines addition.
--> Found '+' operation, changing to '*'
After AST transformation:
apply_op(5, 3) now yields: 15
--- Verifying AST Change ---
Original AST operation for 'result': Add
Transformed AST operation for 'result': Mult
```
Challenge: Can you modify the `AddToMulTransformer` to change *all* occurrences of the variable name `result` to `final_value` within the function definition? (Hint: You'll need to override `visit_Name`!) Good luck, Python magicians! 🧙♂️
---
```python
import ast
# 1. Define a string with our target Python code
code_str = """
def apply_op(a, b):
result = a + b
return result
"""
print(f"Original code string defines addition.")
# 2. Parse the code string into an Abstract Syntax Tree (AST)
tree = ast.parse(code_str)
# 3. Define an AST NodeTransformer to modify the tree
class AddToMulTransformer(ast.NodeTransformer):
def visit_BinOp(self, node):
# If the binary operation is addition, change it to multiplication
if isinstance(node.op, ast.Add):
print(f" --> Found '+' operation, changing to '*'")
node.op = ast.Mult()
# Ensure we visit child nodes to catch nested operations if any
return self.generic_visit(node)
# 4. Apply the transformation
transformer = AddToMulTransformer()
modified_tree = transformer.visit(tree)
# 5. Compile the modified AST into executable code
# The '<string>' argument is a filename for debugging purposes
# 'exec' mode means it's a sequence of statements
compiled_code = compile(modified_tree, '<transformed_code_string>', 'exec')
# 6. Execute the compiled code in a new, isolated namespace
transformed_namespace = {}
# Using empty globals/locals for isolation ensures our new function doesn't clash
exec(compiled_code, {}, transformed_namespace)
# 7. Retrieve the transformed function and call it
transformed_func = transformed_namespace['apply_op']
print(f"After AST transformation:")
print(f"apply_op(5, 3) now yields: {transformed_func(5, 3)}")
print("n--- Verifying AST Change ---")
# Optional: Inspect the original and transformed ASTs to confirm the change
original_tree = ast.parse(code_str)
# We need to find the specific operation node for clarity
for node in original_tree.body:
if isinstance(node, ast.FunctionDef) and node.name == 'apply_op':
for stmt in node.body:
if isinstance(stmt, ast.Assign) and
any(isinstance(t, ast.Name) and t.id == 'result' for t in stmt.targets) and
isinstance(stmt.value, ast.BinOp):
print(f"Original AST operation for 'result': {type(stmt.value.op).__name__}")
break
break
for node in modified_tree.body:
if isinstance(node, ast.FunctionDef) and node.name == 'apply_op':
for stmt in node.body:
if isinstance(stmt, ast.Assign) and
any(isinstance(t, ast.Name) and t.id == 'result' for t in stmt.targets) and
isinstance(stmt.value, ast.BinOp):
print(f"Transformed AST operation for 'result': {type(stmt.value.op).__name__}")
break
break
```
Output example:
```
Original code string defines addition.
--> Found '+' operation, changing to '*'
After AST transformation:
apply_op(5, 3) now yields: 15
--- Verifying AST Change ---
Original AST operation for 'result': Add
Transformed AST operation for 'result': Mult
```
Challenge: Can you modify the `AddToMulTransformer` to change *all* occurrences of the variable name `result` to `final_value` within the function definition? (Hint: You'll need to override `visit_Name`!) Good luck, Python magicians! 🧙♂️
---
What is the primary purpose of Python's `ast` module?
Anonymous Quiz
0%
To dynamically change the type of variables at runtime.
0%
To provide a high-level interface for CPython's garbage collector.
0%
To programmatically inspect, modify, and generate Python code's structure.
0%
To optimize the execution speed of Python scripts by pre-compilation.
Here's your Telegram post for Day 48!
---
Day 48 is here, and we're elevating our Python architecture game! 🚀 Ever wanted to build systems that are not just type-checked, but also incredibly flexible and reusable? Today, we dive into the true power of Python's `typing` module! We'll unlock `Protocol` for structural subtyping and master complex generic patterns to craft robust, future-proof codebases. Say goodbye to runtime surprises and hello to beautifully designed, type-safe Python! ✨ Let's build better together!
```python
from typing import Protocol, TypeVar, Iterable
# Define a Protocol for any object that has a 'name' attribute and a 'process' method
class Processor(Protocol):
name: str
def process(self, data: str) -> str:
... # Ellipsis indicates no implementation needed in protocol
# Implementations that adhere to the Processor Protocol
class SimpleProcessor:
def __init__(self, name: str):
self.name = name
def process(self, data: str) -> str:
return f"{self.name} processed: {data.upper()}"
class AdvancedProcessor:
def __init__(self, identifier: str):
self.name = f"Adv-{identifier}" # Provides 'name' attribute
def process(self, data: str) -> str:
return f"[{self.name}] handled: {data.lower()}"
# A generic function that accepts any type adhering to the Processor Protocol
T = TypeVar('T', bound=Processor) # T must be a Processor or a subtype
def run_processors(processors: Iterable[T], input_data: str) -> list[str]:
results = []
for p in processors:
print(f"Running {p.name}...")
results.append(p.process(input_data))
return results
# Let's test it!
simple_p = SimpleProcessor("Basic")
advanced_p = AdvancedProcessor("Complex")
# Create a list of objects that adhere to the Processor Protocol
processor_list: list[Processor] = [simple_p, advanced_p] # Type-checked!
print("n--- Running Processors ---")
output = run_processors(processor_list, "hello world")
print("nResults:")
for res in output:
print(f"- {res}")
# If we tried to add a non-conforming object and run a static type checker (like mypy),
# it would flag an error because it doesn't meet the Protocol's requirements.
# Example (uncomment and run mypy to see error):
# class BadProcessor:
# def execute(self, data: str) -> str: # No 'name' and method name mismatch
# return "Oops"
# bad_p = BadProcessor()
# processor_list.append(bad_p) # Mypy error: "BadProcessor" does not implement protocol "Processor"
```
Output example:
```
--- Running Processors ---
Running Basic...
Running Adv-Complex...
Results:
- Basic processed: HELLO WORLD
- [Adv-Complex] handled: hello world
```
**Challenge time!** 🎯
1. Modify `AdvancedProcessor` to *not* have a `name` attribute or `process` method, and observe what `mypy` reports if you try to use it with `run_processors`.
2. Can you create another class `Logger` that implements a `Loggable` `Protocol` (e.g., with a `log_message` method and `level` attribute), and then write a generic function that takes an `Iterable` of `Loggable` objects? Share your solutions!
---
---
Day 48 is here, and we're elevating our Python architecture game! 🚀 Ever wanted to build systems that are not just type-checked, but also incredibly flexible and reusable? Today, we dive into the true power of Python's `typing` module! We'll unlock `Protocol` for structural subtyping and master complex generic patterns to craft robust, future-proof codebases. Say goodbye to runtime surprises and hello to beautifully designed, type-safe Python! ✨ Let's build better together!
```python
from typing import Protocol, TypeVar, Iterable
# Define a Protocol for any object that has a 'name' attribute and a 'process' method
class Processor(Protocol):
name: str
def process(self, data: str) -> str:
... # Ellipsis indicates no implementation needed in protocol
# Implementations that adhere to the Processor Protocol
class SimpleProcessor:
def __init__(self, name: str):
self.name = name
def process(self, data: str) -> str:
return f"{self.name} processed: {data.upper()}"
class AdvancedProcessor:
def __init__(self, identifier: str):
self.name = f"Adv-{identifier}" # Provides 'name' attribute
def process(self, data: str) -> str:
return f"[{self.name}] handled: {data.lower()}"
# A generic function that accepts any type adhering to the Processor Protocol
T = TypeVar('T', bound=Processor) # T must be a Processor or a subtype
def run_processors(processors: Iterable[T], input_data: str) -> list[str]:
results = []
for p in processors:
print(f"Running {p.name}...")
results.append(p.process(input_data))
return results
# Let's test it!
simple_p = SimpleProcessor("Basic")
advanced_p = AdvancedProcessor("Complex")
# Create a list of objects that adhere to the Processor Protocol
processor_list: list[Processor] = [simple_p, advanced_p] # Type-checked!
print("n--- Running Processors ---")
output = run_processors(processor_list, "hello world")
print("nResults:")
for res in output:
print(f"- {res}")
# If we tried to add a non-conforming object and run a static type checker (like mypy),
# it would flag an error because it doesn't meet the Protocol's requirements.
# Example (uncomment and run mypy to see error):
# class BadProcessor:
# def execute(self, data: str) -> str: # No 'name' and method name mismatch
# return "Oops"
# bad_p = BadProcessor()
# processor_list.append(bad_p) # Mypy error: "BadProcessor" does not implement protocol "Processor"
```
Output example:
```
--- Running Processors ---
Running Basic...
Running Adv-Complex...
Results:
- Basic processed: HELLO WORLD
- [Adv-Complex] handled: hello world
```
**Challenge time!** 🎯
1. Modify `AdvancedProcessor` to *not* have a `name` attribute or `process` method, and observe what `mypy` reports if you try to use it with `run_processors`.
2. Can you create another class `Logger` that implements a `Loggable` `Protocol` (e.g., with a `log_message` method and `level` attribute), and then write a generic function that takes an `Iterable` of `Loggable` objects? Share your solutions!
---
Which of the following methods is NOT considered part of the *core* Python Descriptor Protocol for attribute access?
Anonymous Quiz
0%
__get__(self, instance, owner)
100%
__init__(self, *args, **kwargs)
0%
__set__(self, instance, value)
0%
__delete__(self, instance)
🚀 Day 50: **The Genesis Engine: Architecting Code Mutations with Python's Abstract Syntax Tree (AST)!** 🤯
Today, we go beyond writing code to *rewriting* it programmatically! The Abstract Syntax Tree (AST) allows you to dissect your Python code into its fundamental building blocks, inspect its structure, and even dynamically transform it. Imagine building custom linters, generating code, or even implementing domain-specific languages – all by manipulating the very fabric of Python itself! 🛠️ Get ready to delve deep into the compiler's mind.
```python
import ast
import inspect
# Original function we want to "mutate"
def simple_math(x, y):
result = x + y * 2
return result
# 1. Get the AST of the function
source_code = inspect.getsource(simple_math)
tree = ast.parse(source_code)
print("Original AST (simplified view):")
print(ast.dump(tree, indent=4))
# 2. Define an AST NodeTransformer to modify the tree
class MultiplyToAddTransformer(ast.NodeTransformer):
"""
Transforms multiplication operations (*) into addition operations (+).
"""
def visit_BinOp(self, node):
# Check if the operation is multiplication
if isinstance(node.op, ast.Mult):
print(f"n⚡ Transforming multiplication at line {node.lineno} to addition...")
node.op = ast.Add() # Change the operator from Mult to Add
# Ensure we visit child nodes as well
return self.generic_visit(node)
# 3. Apply the transformation
transformer = MultiplyToAddTransformer()
transformed_tree = transformer.visit(tree)
# ast.fix_missing_locations is important if you create new nodes
# or modify line/column numbers, ensuring the AST is valid for compilation.
ast.fix_missing_locations(transformed_tree)
print("nTransformed AST (simplified view):")
print(ast.dump(transformed_tree, indent=4))
# 4. Compile and execute the modified AST
# Compile the entire module represented by the transformed AST
compiled_code = compile(transformed_tree, '<string>', 'exec')
# Create a new namespace to execute the compiled code
mutated_namespace = {}
exec(compiled_code, mutated_namespace)
# The transformed function will now be in our mutated_namespace
mutated_simple_math = mutated_namespace['simple_math']
print("n--- Execution Results ---")
print(f"Original function 'simple_math(5, 3)': {simple_math(5, 3)}") # Expected: 5 + (3 * 2) = 11
print(f"Mutated function 'mutated_simple_math(5, 3)': {mutated_simple_math(5, 3)}") # Expected: 5 + (3 + 2) = 10 (because * became +)
```
Output example:
```
Original AST (simplified view):
Module(body=[FunctionDef(name='simple_math', args=arguments(posonlyargs=[], args=[arg(arg='x'), arg(arg='y')], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Assign(targets=[Name(id='result', ctx=Store())], value=BinOp(left=Name(id='x', ctx=Load()), op=Add(), right=BinOp(left=Name(id='y', ctx=Load()), op=Mult(), right=Constant(value=2)))), Return(value=Name(id='result', ctx=Load()))], decorator_list=[])], type_ignores=[])
⚡ Transforming multiplication at line 3 to addition...
Transformed AST (simplified view):
Module(body=[FunctionDef(name='simple_math', args=arguments(posonlyargs=[], args=[arg(arg='x'), arg(arg='y')], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Assign(targets=[Name(id='result', ctx=Store())], value=BinOp(left=Name(id='x', ctx=Load()), op=Add(), right=BinOp(left=Name(id='y', ctx=Load()), op=Add(), right=Constant(value=2)))), Return(value=Name(id='result', ctx=Load()))], decorator_list=[])], type_ignores=[])
--- Execution Results ---
Original function 'simple_math(5, 3)': 11
Mutated function 'mutated_simple_math(5, 3)': 10
```
💡 **Your Challenge:** Modify the `MultiplyToAddTransformer` to instead identify all `if` statements and wrap their bodies in a `try...except` block, catching a generic `Exception` and printing an error message. This is much harder but a fantastic dive into AST node creation!
Today, we go beyond writing code to *rewriting* it programmatically! The Abstract Syntax Tree (AST) allows you to dissect your Python code into its fundamental building blocks, inspect its structure, and even dynamically transform it. Imagine building custom linters, generating code, or even implementing domain-specific languages – all by manipulating the very fabric of Python itself! 🛠️ Get ready to delve deep into the compiler's mind.
```python
import ast
import inspect
# Original function we want to "mutate"
def simple_math(x, y):
result = x + y * 2
return result
# 1. Get the AST of the function
source_code = inspect.getsource(simple_math)
tree = ast.parse(source_code)
print("Original AST (simplified view):")
print(ast.dump(tree, indent=4))
# 2. Define an AST NodeTransformer to modify the tree
class MultiplyToAddTransformer(ast.NodeTransformer):
"""
Transforms multiplication operations (*) into addition operations (+).
"""
def visit_BinOp(self, node):
# Check if the operation is multiplication
if isinstance(node.op, ast.Mult):
print(f"n⚡ Transforming multiplication at line {node.lineno} to addition...")
node.op = ast.Add() # Change the operator from Mult to Add
# Ensure we visit child nodes as well
return self.generic_visit(node)
# 3. Apply the transformation
transformer = MultiplyToAddTransformer()
transformed_tree = transformer.visit(tree)
# ast.fix_missing_locations is important if you create new nodes
# or modify line/column numbers, ensuring the AST is valid for compilation.
ast.fix_missing_locations(transformed_tree)
print("nTransformed AST (simplified view):")
print(ast.dump(transformed_tree, indent=4))
# 4. Compile and execute the modified AST
# Compile the entire module represented by the transformed AST
compiled_code = compile(transformed_tree, '<string>', 'exec')
# Create a new namespace to execute the compiled code
mutated_namespace = {}
exec(compiled_code, mutated_namespace)
# The transformed function will now be in our mutated_namespace
mutated_simple_math = mutated_namespace['simple_math']
print("n--- Execution Results ---")
print(f"Original function 'simple_math(5, 3)': {simple_math(5, 3)}") # Expected: 5 + (3 * 2) = 11
print(f"Mutated function 'mutated_simple_math(5, 3)': {mutated_simple_math(5, 3)}") # Expected: 5 + (3 + 2) = 10 (because * became +)
```
Output example:
```
Original AST (simplified view):
Module(body=[FunctionDef(name='simple_math', args=arguments(posonlyargs=[], args=[arg(arg='x'), arg(arg='y')], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Assign(targets=[Name(id='result', ctx=Store())], value=BinOp(left=Name(id='x', ctx=Load()), op=Add(), right=BinOp(left=Name(id='y', ctx=Load()), op=Mult(), right=Constant(value=2)))), Return(value=Name(id='result', ctx=Load()))], decorator_list=[])], type_ignores=[])
⚡ Transforming multiplication at line 3 to addition...
Transformed AST (simplified view):
Module(body=[FunctionDef(name='simple_math', args=arguments(posonlyargs=[], args=[arg(arg='x'), arg(arg='y')], kwonlyargs=[], kw_defaults=[], defaults=[]), body=[Assign(targets=[Name(id='result', ctx=Store())], value=BinOp(left=Name(id='x', ctx=Load()), op=Add(), right=BinOp(left=Name(id='y', ctx=Load()), op=Add(), right=Constant(value=2)))), Return(value=Name(id='result', ctx=Load()))], decorator_list=[])], type_ignores=[])
--- Execution Results ---
Original function 'simple_math(5, 3)': 11
Mutated function 'mutated_simple_math(5, 3)': 10
```
💡 **Your Challenge:** Modify the `MultiplyToAddTransformer` to instead identify all `if` statements and wrap their bodies in a `try...except` block, catching a generic `Exception` and printing an error message. This is much harder but a fantastic dive into AST node creation!
🚀 Day 51: Beyond the surface! Ever wondered how linters, formatters, or even Python itself understands your code? Enter the Abstract Syntax Tree (AST)! 🌳 This powerful module lets you represent Python code as a tree structure, opening doors to programmatic analysis, modification, and generation of code.
It's like having X-ray vision for your scripts, allowing you to *programmatically reshape your Python code*. Ready to perform some code metamorphosis? Let's dive into the `ast` module and unlock its power! 💡
```python
import ast
import inspect
# Original Python code snippet
original_code = """
def greet(name):
print(f"Hello, {name}!")
return f"Welcome, {name}!"
class MyClass:
def __init__(self, value):
self.value = value
def display(self):
return f"Value: {self.value}"
"""
print("--- Original Code ---")
print(original_code)
# 1. Parse the code into an AST
tree = ast.parse(original_code)
# 2. Traverse and identify nodes (e.g., find function definitions)
print("n--- Identified Function Definitions ---")
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
print(f"Function found: '{node.name}' at line {node.lineno}")
# 3. Modify the AST: Add a print statement to the `greet` function
class FunctionModifier(ast.NodeTransformer):
"""
A transformer that adds a print statement to the beginning of the 'greet' function.
"""
def visit_FunctionDef(self, node):
if node.name == "greet":
# Create a new AST node for print("AST modified the greet function!")
new_print_call = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[ast.Constant(value='AST modified the greet function!')],
keywords=[]
)
)
# Add the new print statement to the beginning of the function body
node.body.insert(0, new_print_call)
return self.generic_visit(node) # Crucial: ensures child nodes are also visited
modified_tree = FunctionModifier().visit(tree)
# 4. Unparse the modified AST back to source code (requires Python 3.9+)
modified_code = ast.unparse(modified_tree)
print("n--- Modified Code ---")
print(modified_code)
# 5. Verify the modified code's behavior
print("n--- Modified Code Execution Example ---")
modified_exec_globals = {}
exec(modified_code, modified_exec_globals)
# Call the modified 'greet' function
print(modified_exec_globals['greet']("Bob"))
```
Output example:
```
--- Original Code ---
def greet(name):
print(f"Hello, {name}!")
return f"Welcome, {name}!"
class MyClass:
def __init__(self, value):
self.value = value
def display(self):
return f"Value: {self.value}"
--- Identified Function Definitions ---
Function found: 'greet' at line 2
Function found: '__init__' at line 8
Function found: 'display' at line 11
--- Modified Code ---
def greet(name):
print('AST modified the greet function!')
print(f"Hello, {name}!")
return f"Welcome, {name}!"
class MyClass:
def __init__(self, value):
self.value = value
def display(self):
return f"Value: {self.value}"
--- Modified Code Execution Example ---
AST modified the greet function!
Hello, Bob!
Welcome, Bob!
```
---
### Your Challenge! 🧑💻
Using the `ast` module and an `ast.NodeTransformer`, write a simple script that takes a Python code string and replaces all occurrences of the string literal `"secret_data"` with `"redacted"`. You'll need to look for `ast.Constant` nodes (or `ast.Str` for older Python versions) and check their `value` attribute. Good luck! 🕵️♂️
---
It's like having X-ray vision for your scripts, allowing you to *programmatically reshape your Python code*. Ready to perform some code metamorphosis? Let's dive into the `ast` module and unlock its power! 💡
```python
import ast
import inspect
# Original Python code snippet
original_code = """
def greet(name):
print(f"Hello, {name}!")
return f"Welcome, {name}!"
class MyClass:
def __init__(self, value):
self.value = value
def display(self):
return f"Value: {self.value}"
"""
print("--- Original Code ---")
print(original_code)
# 1. Parse the code into an AST
tree = ast.parse(original_code)
# 2. Traverse and identify nodes (e.g., find function definitions)
print("n--- Identified Function Definitions ---")
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
print(f"Function found: '{node.name}' at line {node.lineno}")
# 3. Modify the AST: Add a print statement to the `greet` function
class FunctionModifier(ast.NodeTransformer):
"""
A transformer that adds a print statement to the beginning of the 'greet' function.
"""
def visit_FunctionDef(self, node):
if node.name == "greet":
# Create a new AST node for print("AST modified the greet function!")
new_print_call = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[ast.Constant(value='AST modified the greet function!')],
keywords=[]
)
)
# Add the new print statement to the beginning of the function body
node.body.insert(0, new_print_call)
return self.generic_visit(node) # Crucial: ensures child nodes are also visited
modified_tree = FunctionModifier().visit(tree)
# 4. Unparse the modified AST back to source code (requires Python 3.9+)
modified_code = ast.unparse(modified_tree)
print("n--- Modified Code ---")
print(modified_code)
# 5. Verify the modified code's behavior
print("n--- Modified Code Execution Example ---")
modified_exec_globals = {}
exec(modified_code, modified_exec_globals)
# Call the modified 'greet' function
print(modified_exec_globals['greet']("Bob"))
```
Output example:
```
--- Original Code ---
def greet(name):
print(f"Hello, {name}!")
return f"Welcome, {name}!"
class MyClass:
def __init__(self, value):
self.value = value
def display(self):
return f"Value: {self.value}"
--- Identified Function Definitions ---
Function found: 'greet' at line 2
Function found: '__init__' at line 8
Function found: 'display' at line 11
--- Modified Code ---
def greet(name):
print('AST modified the greet function!')
print(f"Hello, {name}!")
return f"Welcome, {name}!"
class MyClass:
def __init__(self, value):
self.value = value
def display(self):
return f"Value: {self.value}"
--- Modified Code Execution Example ---
AST modified the greet function!
Hello, Bob!
Welcome, Bob!
```
---
### Your Challenge! 🧑💻
Using the `ast` module and an `ast.NodeTransformer`, write a simple script that takes a Python code string and replaces all occurrences of the string literal `"secret_data"` with `"redacted"`. You'll need to look for `ast.Constant` nodes (or `ast.Str` for older Python versions) and check their `value` attribute. Good luck! 🕵️♂️
---
The `ast` module provides tools for working with Abstract Syntax Trees. Which of the following is NOT a primary capability enabled by the `ast` module?
Anonymous Quiz
0%
A) Programmatically analyzing Python code structure.
0%
B) Modifying the bytecode of a running Python interpreter.
0%
C) Generating new Python source code from a tree representation.
0%
D) Implementing custom linters or static analysis tools.
Which of the following best describes the primary use case of a metaclass when crafting a Domain-Specific Language (DSL) in Python?
Anonymous Quiz
0%
To modify or extend the behavior of classes themselves during their creation.
0%
To define abstract methods that subclasses must implement.
0%
To dynamically add attributes to instances of a class after they are created.
0%
To optimize the performance of method calls within a class hierarchy.
🚀 Day 53: Quantum Types! 🤯 Ever felt limited by Python's generics when dealing with arbitrary numbers of types or complex function signatures? Dive into the cutting-edge of Python's type system with `TypeVarTuple` and `ParamSpec`! ✨
These powerful tools unlock "hyper-generics," allowing you to write incredibly flexible and type-safe code for functions that take variable arguments or need to preserve complex callable signatures. Get ready to stretch the boundaries of what you thought possible with type hints! 💡
```python
from typing import TypeVar, TypeVarTuple, ParamSpec, Callable, reveal_type
import time
# --- TypeVarTuple Example ---
# Ts represents an arbitrary number of types
Ts = TypeVarTuple('Ts')
def process_multiple_args(*args: *Ts) -> tuple[*Ts]:
"""Processes an arbitrary number of arguments, preserving their types."""
print(f"Received arguments: {args}")
return args
# --- ParamSpec Example ---
# P represents the parameter specification (args and kwargs) of a callable
P = ParamSpec('P')
R = TypeVar('R') # R represents the return type of the callable
def log_and_call_timed(func: Callable[P, R]) -> Callable[P, R]:
"""
A decorator that logs calls, measures execution time, and preserves
the decorated function's signature using ParamSpec.
"""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"⏱️ Calling '{func.__name__}' with args={args}, kwargs={kwargs}")
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"✅ '{func.__name__}' returned: {result} (took {end_time - start_time:.4f}s)")
return result
return wrapper
@log_and_call_timed
def add_numbers(a: int, b: int) -> int:
time.sleep(0.01) # Simulate some work
return a + b
@log_and_call_timed
def greet(name: str, *, greeting: str = "Hello") -> str:
time.sleep(0.005) # Simulate some work
return f"{greeting}, {name}!"
# Demonstration
print("n--- TypeVarTuple Demonstration ---")
result_tuple = process_multiple_args(1, "hello", True, 3.14)
# reveal_type(result_tuple) # Uncomment to see type checker's insight
print("n--- ParamSpec Demonstration ---")
sum_result = add_numbers(5, 3)
greet_result = greet("Alice", greeting="Hi there")
# reveal_type(add_numbers) # Uncomment to see type checker's insight on the decorated function
# reveal_type(greet)
```
Output example:
```
--- TypeVarTuple Demonstration ---
Received arguments: (1, 'hello', True, 3.14)
Note: If you uncomment `reveal_type(result_tuple)` and run a type checker like mypy, it might show:
Revealed type is "tuple[int, str, bool, float]"
--- ParamSpec Demonstration ---
⏱️ Calling 'add_numbers' with args=(5, 3), kwargs={}
✅ 'add_numbers' returned: 8 (took 0.01XXs)
⏱️ Calling 'greet' with args=('Alice',), kwargs={'greeting': 'Hi there'}
✅ 'greet' returned: Hi there, Alice! (took 0.00XXs)
Note: If you uncomment `reveal_type(add_numbers)` and `reveal_type(greet)`:
Revealed type is "def (a: builtins.int, b: builtins.int) -> builtins.int"
Revealed type is "def (name: builtins.str, *, greeting: builtins.str =) -> builtins.str"
```
💡 **Challenge Time!**
Create a generic `map_tuple` function using `TypeVarTuple` that takes a function `f` and an arbitrary tuple of arguments `*args`, applying `f` to each argument and returning a new tuple with the results. For example: `map_tuple(str, (1, 2, 3))` should return `('1', '2', '3')`. How would you type hint `f` if it takes a single argument and returns a single value?
---
These powerful tools unlock "hyper-generics," allowing you to write incredibly flexible and type-safe code for functions that take variable arguments or need to preserve complex callable signatures. Get ready to stretch the boundaries of what you thought possible with type hints! 💡
```python
from typing import TypeVar, TypeVarTuple, ParamSpec, Callable, reveal_type
import time
# --- TypeVarTuple Example ---
# Ts represents an arbitrary number of types
Ts = TypeVarTuple('Ts')
def process_multiple_args(*args: *Ts) -> tuple[*Ts]:
"""Processes an arbitrary number of arguments, preserving their types."""
print(f"Received arguments: {args}")
return args
# --- ParamSpec Example ---
# P represents the parameter specification (args and kwargs) of a callable
P = ParamSpec('P')
R = TypeVar('R') # R represents the return type of the callable
def log_and_call_timed(func: Callable[P, R]) -> Callable[P, R]:
"""
A decorator that logs calls, measures execution time, and preserves
the decorated function's signature using ParamSpec.
"""
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print(f"⏱️ Calling '{func.__name__}' with args={args}, kwargs={kwargs}")
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"✅ '{func.__name__}' returned: {result} (took {end_time - start_time:.4f}s)")
return result
return wrapper
@log_and_call_timed
def add_numbers(a: int, b: int) -> int:
time.sleep(0.01) # Simulate some work
return a + b
@log_and_call_timed
def greet(name: str, *, greeting: str = "Hello") -> str:
time.sleep(0.005) # Simulate some work
return f"{greeting}, {name}!"
# Demonstration
print("n--- TypeVarTuple Demonstration ---")
result_tuple = process_multiple_args(1, "hello", True, 3.14)
# reveal_type(result_tuple) # Uncomment to see type checker's insight
print("n--- ParamSpec Demonstration ---")
sum_result = add_numbers(5, 3)
greet_result = greet("Alice", greeting="Hi there")
# reveal_type(add_numbers) # Uncomment to see type checker's insight on the decorated function
# reveal_type(greet)
```
Output example:
```
--- TypeVarTuple Demonstration ---
Received arguments: (1, 'hello', True, 3.14)
Note: If you uncomment `reveal_type(result_tuple)` and run a type checker like mypy, it might show:
Revealed type is "tuple[int, str, bool, float]"
--- ParamSpec Demonstration ---
⏱️ Calling 'add_numbers' with args=(5, 3), kwargs={}
✅ 'add_numbers' returned: 8 (took 0.01XXs)
⏱️ Calling 'greet' with args=('Alice',), kwargs={'greeting': 'Hi there'}
✅ 'greet' returned: Hi there, Alice! (took 0.00XXs)
Note: If you uncomment `reveal_type(add_numbers)` and `reveal_type(greet)`:
Revealed type is "def (a: builtins.int, b: builtins.int) -> builtins.int"
Revealed type is "def (name: builtins.str, *, greeting: builtins.str =) -> builtins.str"
```
💡 **Challenge Time!**
Create a generic `map_tuple` function using `TypeVarTuple` that takes a function `f` and an arbitrary tuple of arguments `*args`, applying `f` to each argument and returning a new tuple with the results. For example: `map_tuple(str, (1, 2, 3))` should return `('1', '2', '3')`. How would you type hint `f` if it takes a single argument and returns a single value?
---
Which special method within a Python descriptor is responsible for defining the behavior when an attribute is *assigned* (e.g., `instance.attribute = value`)?
Anonymous Quiz
0%
__get__(self, instance, owner)
0%
__set__(self, instance, value)
0%
__set_name__(self, owner, name)
0%
__init__(self, name)