Python basic to advance
43 subscribers
131 photos
98 links
Learn Python daily with bite-sized lessons, code examples, output previews, and fun quizzes. Perfect for beginners and enthusiasts!
Download Telegram
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! ๐Ÿš€

---
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!

---
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)
๐Ÿš€ 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! ๐Ÿง™โ€โ™‚๏ธ

---
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!

---
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!
๐Ÿš€ 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! ๐Ÿ•ต๏ธโ€โ™‚๏ธ

---
๐Ÿš€ 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?

---
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)
๐Ÿš€ **Day 56: Surgical Code Transformation with Python AST!** ๐Ÿ Ever wondered what happens *behind the scenes* when Python executes your code? The Abstract Syntax Tree (AST) is your answer! It's a fundamental, hierarchical representation of your code's structure. Today, we're not just looking at it; we're *hacking* it! ๐Ÿ› ๏ธ Learn to programmatically analyze, modify, and even generate Python code by manipulating its AST. Unlock powerful capabilities for linters, refactoring tools, and advanced metaprogramming! Get ready to dissect your code like never before. ๐Ÿ’ก

```python
import ast

# 1. Original code as a string
original_code_str = """
def calculate_price(quantity):
base_price = 10
total = base_price * quantity + 5 # Let's change this '5'
return total
"""

print("--- Original Code ---")
print(original_code_str)

# 2. Parse the code into an AST
tree = ast.parse(original_code_str)

# 3. Define a custom NodeTransformer to modify the AST
class ConstantModifier(ast.NodeTransformer):
"""
Changes all integer constants with value '5' to '500'.
"""
def visit_Constant(self, node):
# ast.Constant is used for literals like numbers, strings, etc. (Python 3.8+)
if isinstance(node.value, int) and node.value == 5:
# Create a new Constant node with the desired value
return ast.Constant(value=500, kind=None)
# For all other nodes, ensure children are visited
return self.generic_visit(node)

# 4. Apply the transformer to modify the AST
modified_tree = ConstantModifier().visit(tree)

# 5. Unparse the modified AST back into a code string
modified_code_str = ast.unparse(modified_tree)

print("n--- Modified Code AST ---")
print(modified_code_str)

# 6. Execute both versions of the code to see the difference
print("n--- Execution Results ---")

# Execute original code
exec_globals_original = {}
exec(original_code_str, exec_globals_original)
original_calculate_price = exec_globals_original['calculate_price']
print(f"calculate_price(2) (original): {original_calculate_price(2)}") # Expected: (10*2)+5 = 25

# Execute modified code
exec_globals_modified = {}
exec(modified_code_str, exec_globals_modified)
modified_calculate_price = exec_globals_modified['calculate_price']
print(f"calculate_price(2) (modified): {modified_calculate_price(2)}") # Expected: (10*2)+500 = 520
```

Output example:
```
--- Original Code ---

def calculate_price(quantity):
base_price = 10
total = base_price * quantity + 5 # Let's change this '5'
return total

--- Modified Code AST ---

def calculate_price(quantity):
base_price = 10
total = base_price * quantity + 500
return total

--- Execution Results ---
calculate_price(2) (original): 25
calculate_price(2) (modified): 520
```

**Small Challenge:**
Instead of changing a constant, can you modify the `calculate_price` function's AST to accept an additional argument `tax_rate` and add `total * tax_rate` to the return value? ๐Ÿคฏ (Hint: You'll need to add a new `arg` to `FunctionDef.args.args` and modify the `Return` statement's `value` to be a new `BinOp`). Share your solution! ๐Ÿง 

---
Here's your Telegram post for Day 57 of the Python learning series!

---

๐Ÿ”ฅ Day 57: Async Seams! ๐Ÿงต Ever wondered how to integrate the speed of `asyncio` into an existing Python codebase full of blocking, synchronous calls? ๐Ÿค” Today, we're diving into 'Asynchronous Seams' โ€“ a crucial technique for gracefully weaving non-blocking I/O into legacy synchronous codebases! โœจ Learn to leverage `asyncio.to_thread()` (or `loop.run_in_executor`) to run blocking operations in a separate thread, keeping your event loop responsive and your application blazing fast. No more freezing your async tasks with a slow database query or a heavy file read!

```python
import asyncio
import time
import functools

# --- Simulate a "legacy" synchronous blocking I/O operation ---
def sync_blocking_io(task_id: int, duration: int):
"""Simulates a blocking operation that takes 'duration' seconds."""
print(f"[{time.time():.2f}] ๐Ÿข Blocking Task {task_id} starting for {duration}s...")
time.sleep(duration) # Simulates a blocking call (e.g., network, disk I/O, heavy computation)
print(f"[{time.time():.2f}] โœ… Blocking Task {task_id} finished.")
return f"Result from Blocking Task {task_id} (duration: {duration}s)"

# --- Regular async tasks ---
async def async_worker(name: str, delay: int):
"""An asynchronous task that pauses for 'delay' seconds."""
print(f"[{time.time():.2f}] ๐Ÿš€ Async Worker {name} starting for {delay}s...")
await asyncio.sleep(delay)
print(f"[{time.time():.2f}] โญ Async Worker {name} finished.")
return f"Result from Async Worker {name} (delay: {delay}s)"

async def main():
print("--- Starting main async process ---")
start_time = time.time()

# We use asyncio.to_thread() (Python 3.9+) to run the blocking function
# in a separate thread, preventing it from blocking the main event loop.
# For older Python versions, you'd typically use:
# loop = asyncio.get_running_loop()
# blocking_task_coro = loop.run_in_executor(None, functools.partial(sync_blocking_io, 1, 3))

blocking_task_coro = asyncio.to_thread(sync_blocking_io, 1, 3)

# Run multiple async tasks and the "threaded" blocking task concurrently
results = await asyncio.gather(
async_worker("A", 1),
async_worker("B", 2),
blocking_task_coro, # The blocking task is now awaitable!
async_worker("C", 0.5)
)

print("n--- All tasks completed ---")
for res in results:
print(f"Final Result: {res}")
print(f"Total elapsed time: {time.time() - start_time:.2f}s")

if __name__ == "__main__":
asyncio.run(main())
```

Output example:
```
--- Starting main async process ---
[1678886400.00] ๐Ÿš€ Async Worker A starting for 1s...
[1678886400.00] ๐Ÿš€ Async Worker B starting for 2s...
[1678886400.00] ๐Ÿš€ Async Worker C starting for 0.5s...
[1678886400.00] ๐Ÿข Blocking Task 1 starting for 3s...
[1678886400.50] โญ Async Worker C finished.
[1678886401.00] โญ Async Worker A finished.
[1678886402.00] โญ Async Worker B finished.
[1678886403.00] โœ… Blocking Task 1 finished.

--- All tasks completed ---
Final Result: Result from Async Worker A (delay: 1s)
Final Result: Result from Async Worker B (delay: 2s)
Final Result: Result from Blocking Task 1 (duration: 3s)
Final Result: Result from Async Worker C (delay: 0.5s)
Total elapsed time: 3.00s
```
Notice how the async workers finish *before* the blocking task, even though the blocking task started first. This demonstrates that `asyncio.to_thread` prevented the blocking call from freezing the event loop!

๐Ÿ’ก Small Challenge: Take your `asyncio` skills further! Try wrapping a synchronous network request library (like `requests`) using `asyncio.to_thread` or `run_in_executor` to make an external API call from within an `asyncio` application without blocking the event loop. Share your async masterpiece! ๐Ÿš€

---
Hello Every one WE will Restart from day 1
Here's your Telegram post for Day 3 of the Python learning series!

---

Hey Python Learners! ๐Ÿ‘‹ Ready for Day 3? Today we're diving into the powerful world of *Operators*! ๐Ÿš€ Operators are special symbols that perform operations on values and variables. From basic math to complex logic, they are the building blocks of any program. Let's see how they work and level up our coding skills! ๐Ÿ‘จโ€๐Ÿ’ป

```python
# Day 3: Operators in Python - Let's compute! โœจ

# Arithmetic Operators
a = 10
b = 3
print(f"a + b = {a + b}") # Addition
print(f"a - b = {a - b}") # Subtraction
print(f"a * b = {a * b}") # Multiplication
print(f"a / b = {a / b}") # Division (float result)
print(f"a // b = {a // b}") # Floor Division (integer result)
print(f"a % b = {a % b}") # Modulus (remainder)
print(f"a ** b = {a ** b}") # Exponentiation

# Comparison Operators
x = 5
y = 8
print(f"x == y is {x == y}") # Equal to
print(f"x < y is {x < y}") # Less than
print(f"x >= y is {x >= y}") # Greater than or Equal to

# Logical Operators
is_logged_in = True
has_permission = False
print(f"Logged in AND has permission: {is_logged_in and has_permission}")
print(f"Logged in OR has permission: {is_logged_in or has_permission}")
print(f"NOT logged in: {not is_logged_in}")
```

Output example:
```
a + b = 13
a - b = 7
a * b = 30
a / b = 3.3333333333333335
a // b = 3
a % b = 1
a ** b = 1000
x == y is False
x < y is True
x >= y is False
Logged in AND has permission: False
Logged in OR has permission: True
NOT logged in: False
```

๐Ÿ’ก *Challenge Time!* What will be the output of the expression `(15 % 4) * (2 ** 3) + 7 // 2`? Try it in your interpreter and share your answer! ๐Ÿค” Let's see who gets it right! ๐Ÿ‘‡

---