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

---

Hey Pythonistas! 👋 Day 59 is here, and we're diving deep into the very core of how Python works! 🚀

Ever wondered what Python does *before* it executes your code? It compiles it into an intermediate representation called **bytecode**. Understanding this isn't just for language geeks; it offers profound insights into performance, optimization, and even how to build powerful metaprogramming tools. Today, we'll peek under the hood with Python's built-in `dis` module! 🔍 Get ready to see the machine code that powers your scripts.

```python
import dis

def process_data(value):
"""
A simple function demonstrating string concatenation and a conditional.
"""
if isinstance(value, str):
message = "Input is a string: " + value
else:
message = f"Input is not a string, type: {type(value).__name__}"
print(message)
return message

print("✨ Disassembling the 'process_data' function: ✨n")
dis.dis(process_data)
```

Output example:
```
✨ Disassembling the 'process_data' function: ✨

7 0 LOAD_GLOBAL 0 (isinstance)
2 LOAD_FAST 0 (value)
4 LOAD_GLOBAL 1 (str)
6 CALL_FUNCTION 2
8 POP_JUMP_IF_FALSE 20

8 10 LOAD_CONST 1 ('Input is a string: ')
12 LOAD_FAST 0 (value)
14 BINARY_ADD
16 STORE_FAST 1 (message)
18 JUMP_FORWARD 30 (to 50)

10 >> 20 LOAD_CONST 2 ('Input is not a string, type: ')
22 LOAD_GLOBAL 2 (type)
24 LOAD_FAST 0 (value)
26 CALL_FUNCTION 1
28 LOAD_ATTR 3 (__name__)
30 FORMAT_VALUE 0
32 BUILD_STRING 2
34 STORE_FAST 1 (message)

11 >> 36 LOAD_GLOBAL 4 (print)
38 LOAD_FAST 1 (message)
40 CALL_FUNCTION 1
42 POP_TOP

12 44 LOAD_FAST 1 (message)
46 RETURN_VALUE
```

⚙️ **Small Challenge:** The output above shows operations like `POP_JUMP_IF_FALSE` (for the `if` statement), `BINARY_ADD` (for string concatenation), and `FORMAT_VALUE`/`BUILD_STRING` (for f-strings). Define your own simple Python function that includes a `for` loop or a `while` loop. Use `dis.dis()` on it and try to identify the corresponding opcodes like `SETUP_LOOP`, `FOR_ITER`, `JUMP_ABSOLUTE`, or `GET_ITER`! What other interesting opcodes do you discover?

---
Here's your Telegram post for Day 60, diving into the advanced world of Python descriptors!

---

✨ Day 60: The Descriptor Protocol Unleashed! ✨

Today, we're diving deep into one of Python's most powerful, yet often misunderstood, mechanisms: The Descriptor Protocol. Forget simple `@property`! Descriptors allow you to create dynamic attribute behavior, implement robust validation pipelines, or even inject dependencies directly into your classes. 🚀

We'll explore how `__get__`, `__set__`, and `__delete__` methods, combined with `__set_name__`, unlock a new level of control over attribute access. Get ready to sculpt your objects with precision! 💪 Let's build reusable attribute "factories" that make your code both powerful and maintainable.

```python
class TypeChecked:
"""A descriptor for enforcing attribute types."""
def __init__(self, expected_type):
self.expected_type = expected_type
self.private_name = None # Will store the actual attribute name prefixed with '_'

def __set_name__(self, owner, name):
"""Called once when the descriptor is assigned to a class attribute."""
self.private_name = f'_{name}'
print(f"Descriptor assigned to '{name}' in class '{owner.__name__}'")

def __get__(self, obj, obj_type=None):
"""Called when accessing the attribute."""
if obj is None:
return self # Accessing descriptor directly via class (e.g., Product.name)
# Return the value stored in the instance's dictionary
return obj.__dict__.get(self.private_name)

def __set__(self, obj, value):
"""Called when setting the attribute."""
if not isinstance(value, self.expected_type):
raise TypeError(f"Expected {self.expected_type.__name__}, got {type(value).__name__} for attribute '{self.private_name}'")
# Store the value in the instance's dictionary using the private name
obj.__dict__[self.private_name] = value

class Product:
name = TypeChecked(str)
price = TypeChecked(float)
quantity = TypeChecked(int)

def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity

# --- Usage Example ---
print("--- Creating a valid product ---")
product1 = Product("Laptop", 1200.50, 10)
print(f"Product 1: Name={product1.name}, Price={product1.price}, Quantity={product1.quantity}")

print("n--- Attempting to create an invalid product ---")
try:
product2 = Product("Monitor", 300, "five") # Incorrect type for quantity
except TypeError as e:
print(f"Caught expected error: {e}")

print("n--- Accessing descriptor directly ---")
print(f"Product.name descriptor: {Product.name}")
```

Output example:
```
Descriptor assigned to 'name' in class 'Product'
Descriptor assigned to 'price' in class 'Product'
Descriptor assigned to 'quantity' in class 'Product'
--- Creating a valid product ---
Product 1: Name=Laptop, Price=1200.5, Quantity=10

--- Attempting to create an invalid product ---
Caught expected error: Expected int, got str for attribute '_quantity'

--- Accessing descriptor directly ---
Product.name descriptor: <__main__.TypeChecked object at 0x...>
```

Small Challenge: Can you enhance our `TypeChecked` descriptor? 🤔
Modify it to accept an optional `default` argument in its `__init__`. If a value isn't explicitly set for an attribute, `__get__` should return this default. Hint: You might need to check if `self.private_name` exists in `obj.__dict__` and, if not, return the `default`.

---
Which special method in a Python descriptor is primarily responsible for allowing the descriptor to know the name of the attribute it was assigned to within the owner class?
Anonymous Quiz
0%
A. __get__
0%
B. __set__
0%
C. __set_name__
0%
D. __delete__
Here's your Telegram post for Day 63 of your Python learning series! 🚀

---

📚 Day 63: The Quantum Leap – Navigating CPython's Bytecode and Frame Objects for Runtime Introspection 🔬✨

Ready to peek under Python's hood? Today, we're not just using Python; we're exploring *how* Python works internally! Understanding CPython's bytecode and frame objects unlocks incredible powers for advanced debugging, profiling, and even building custom DSLs. We'll use the `inspect` module to explore active execution frames and `dis` to peer into the machine-like instructions Python executes. Get ready to truly understand your code's runtime behavior!

```python
import sys
import inspect
import dis

def calculate_sum(a, b):
# This function's bytecode we will inspect
x = a + 1
y = b * 2
result = x + y
return result

def analyze_runtime_context(arg1, arg2):
# This function demonstrates frame introspection
local_context_var = "I am in analyze_runtime_context"

print(f"Executing analyze_runtime_context with args: {arg1}, {arg2}")

# 1. Get the current frame
current_frame = inspect.currentframe()
print(f"n🔬 Current Frame Details ({current_frame.f_code.co_name}):")
print(f" Line number: {current_frame.f_lineno}")
print(f" Local variables: {current_frame.f_locals}")

# 2. Get the caller's frame (the one that called analyze_runtime_context)
# sys._getframe(1) is a common way, or inspect.getouterframes
outer_frames = inspect.getouterframes(current_frame)
if len(outer_frames) > 1:
caller_frame_info = outer_frames[1] # [0] is current, [1] is immediate caller
caller_frame = caller_frame_info.frame
print(f"n🔍 Caller's Frame Details ({caller_frame.f_code.co_name}):")
print(f" Line number: {caller_frame.f_lineno}")
# Note: f_locals for caller's frame might not always be fully populated due to optimization
print(f" Caller's visible locals: {{k:v for k,v in caller_frame.f_locals.items() if not k.startswith('_')}}")

# 3. Disassemble the bytecode of calculate_sum
print(f"n📜 Bytecode for 'calculate_sum' function:")
dis.dis(calculate_sum)

# The main entry point
def main_program():
main_val_x = 10
main_val_y = 20
analyze_runtime_context(main_val_x, main_val_y)
_ = calculate_sum(1, 2) # Call it to make sure it's compiled, though dis() works on func object
print("nIntrospection complete! ✨")

if __name__ == "__main__":
main_program()
```

Output example:
```
Executing analyze_runtime_context with args: 10, 20

🔬 Current Frame Details (analyze_runtime_context):
Line number: 20
Local variables: {'arg1': 10, 'arg2': 20, 'local_context_var': 'I am in analyze_runtime_context', 'current_frame': <frame object at 0x...>}

🔍 Caller's Frame Details (main_program):
Line number: 40
Caller's visible locals: {'main_val_x': 10, 'main_val_y': 20}

📜 Bytecode for 'calculate_sum' function:
11 0 LOAD_FAST 0 (a)
2 LOAD_CONST 1 (1)
4 BINARY_ADD
6 STORE_FAST 2 (x)

12 8 LOAD_FAST 1 (b)
10 LOAD_CONST 2 (2)
12 BINARY_MULTIPLY
14 STORE_FAST 3 (y)

13 16 LOAD_FAST 2 (x)
18 LOAD_FAST 3 (y)
20 BINARY_ADD
22 STORE_FAST 4 (result)

14 24 LOAD_FAST 4 (result)
26 RETURN_VALUE

Introspection complete! ✨
```

💡 **Small Challenge:**
Modify the `analyze_runtime_context` function. Instead of just showing the immediate caller's locals, try to traverse up the call stack using `inspect.getouterframes()` to find and print the names of *all* functions in the current call stack, from the innermost to the outermost! Can you also identify which line number in `main_program` calls `analyze_runtime_context` from the frame info?

---
🚀 Day 64: Ever wondered how Python truly works under the hood? Today, we're diving deep into the very blueprint of your code: the Abstract Syntax Tree (AST)! 🤯 Learn to unmask, transform, and even generate Python code at runtime by directly manipulating its AST. This isn't just theory; it's a powerful technique for advanced metaprogramming, dynamic code generation, and building highly adaptable systems. Prepare to truly understand your code's structure! ✨

In this example, we'll parse a simple Python function, modify its AST to inject a logging `print` statement at the beginning of the function, and then execute the transformed code. See how we can alter code's behavior without touching the source string directly!

```python
import ast
import inspect

# Original Python code as a string
original_code = """
def calculate_sum(a, b):
# This is the original function body
result = a + b
return result

# This will print the result using the original function definition
print(f"Original sum result: {calculate_sum(5, 3)}")
"""

class FunctionLoggerTransformer(ast.NodeTransformer):
"""
A custom AST transformer that adds a print statement to the beginning
of any function it processes, logging its execution.
"""
def visit_FunctionDef(self, node):
# Create a new print statement node: print(f"--- Executing function: '{node.name}' ---")
# We embed the function's name (from AST transformation time) directly into the string.
log_message = ast.Constant(value=f"--- Executing function: '{node.name}' ---")

print_statement = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()), # Call to the 'print' built-in
args=[log_message], # Argument is our logging message
keywords=[]
)
)

# Prepend the new print statement to the function's body
node.body.insert(0, print_statement)

# Important: Call generic_visit to ensure child nodes are also visited and potentially transformed
return self.generic_visit(node)

print("Original code before transformation:n" + original_code)

# 1. Parse the original code into an AST
tree = ast.parse(original_code)

# 2. Transform the AST using our custom transformer
transformer = FunctionLoggerTransformer()
new_tree = transformer.visit(tree)

# 3. Fix missing line numbers and column offsets in the new tree.
# This is crucial for compilation to work correctly and prevent errors.
ast.fix_missing_locations(new_tree)

# 4. Compile the modified AST back into executable code
# filename='<string>' is standard for in-memory code
# mode='exec' for a sequence of statements or a module
compiled_code = compile(new_tree, filename='<string>', mode='exec')

print("n--- Executing Transformed Code ---")

# 5. Execute the compiled code in a new scope
# This will run the `print` statement that calls `calculate_sum` from the original_code,
# but now `calculate_sum` has our injected logger.
exec_scope = {} # Define a scope for execution to catch definitions if needed
exec(compiled_code, exec_scope)
```

Output example:
```
Original code before transformation:

def calculate_sum(a, b):
# This is the original function body
result = a + b
return result

# This will print the result using the original function definition
print(f"Original sum result: {calculate_sum(5, 3)}")

--- Executing Transformed Code ---
--- Executing function: 'calculate_sum' ---
Original sum result: 8
```

💡 **Your Challenge:** Modify the `FunctionLoggerTransformer` to not just log the function execution, but also log the *arguments* passed to the function! Hint: You'll need to inspect `node.args` within `visit_FunctionDef` and create an AST node for a dictionary or a formatted string that prints argument names and values. Good luck!

---
📚 Quiz Question:
Which Python module is primarily used for parsing Python code into an Abstract Syntax Tree (AST) for programmatic manipulation?
Anonymous Quiz
0%
A. `parser`
0%
B. `ast`
0%
C. `tokenize`
0%
D. `compiler`
🚀 Day 65: Ever wanted to bend Python's rules? 🤯 Today, we dive into the Abstract Syntax Tree (AST)! No, it's not just for linters. We'll learn how to programmatically *rewrite* your code before it even runs, opening doors to custom language features, powerful static analysis, or even runtime optimizations. Get ready to transform your Python! 🛠️✨

Let's craft a simple AST transformer to automatically log when any function is entered.

```python
import ast
import sys
from io import StringIO

# Original code to transform
original_code = """
def greet(name):
print(f"Hello, {name}!")
return f"Greeting for {name}."

def calculate_sum(a, b):
result = a + b
print(f"Sum is {result}")
return result

print("Before AST transformation")
greet("Alice")
calculate_sum(10, 20)
"""

class FunctionCallLogger(ast.NodeTransformer):
"""
AST transformer to inject a print statement at the beginning
of every function definition, logging function entry.
"""
def visit_FunctionDef(self, node):
# Create a print statement node to log function entry
log_message = ast.Constant(value=f"Entering function: {node.name}()")
print_call = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[log_message],
keywords=[]
)
)

# Insert the print statement at the beginning of the function's body
node.body.insert(0, print_call)

# Ensure we visit child nodes (e.g., nested functions)
return self.generic_visit(node)

# Parse the original code into an AST
tree = ast.parse(original_code)

# Apply the transformer to modify the AST
transformer = FunctionCallLogger()
new_tree = transformer.visit(tree)

# --- Compile and execute the transformed code ---
# Redirect stdout to capture the output for demonstration
old_stdout = sys.stdout
redirected_output = StringIO()
sys.stdout = redirected_output

try:
print("--- Rewritten Code Preview (Python 3.9+) ---")
# ast.unparse is great for viewing the transformed code,
# but only available in Python 3.9 and later.
try:
print(ast.unparse(new_tree))
except AttributeError:
print("# ast.unparse not available. The transformed code below will be executed.")

print("n--- Executing Transformed Code ---")
# Compile the modified AST back into executable code and run it
exec(compile(new_tree, '<string>', 'exec'))
except Exception as e:
print(f"Error during execution: {e}")
finally:
sys.stdout = old_stdout # Restore stdout

print(redirected_output.getvalue())
```

Output example:
```
--- Rewritten Code Preview (Python 3.9+) ---
def greet(name):
print('Entering function: greet()')
print(f"Hello, {name}!")
return f"Greeting for {name}."

def calculate_sum(a, b):
print('Entering function: calculate_sum()')
result = a + b
print(f"Sum is {result}")
return result
print('Before AST transformation')
greet('Alice')
calculate_sum(10, 20)

--- Executing Transformed Code ---
Before AST transformation
Entering function: greet()
Hello, Alice!
Entering function: calculate_sum()
Sum is 30
```

**Challenge:** Modify the `FunctionCallLogger` transformer to also inject a `print` statement at the *end* of each function, showing an "Exiting function: {func_name}()" message! 🔚

---
Which `ast` module class is specifically designed for visiting and *modifying* nodes within an Abstract Syntax Tree?
Anonymous Quiz
0%
ast.NodeTransformer
0%
ast.NodeVisitor
0%
ast.parse
0%
ast.dump
Which of the following methods is primarily responsible for *retrieving* an attribute's value when a descriptor is accessed on an instance (e.g., `obj.attribute`)?
Anonymous Quiz
0%
__set__
0%
__get__
0%
__call__
0%
__set_name__
Here's your Telegram post for the Python learning series!

---

🚀 Ready to truly command your Python types? Dive into the "Deep Magic" of how classes are born! ✨ This week, we're unraveling Metaclasses and the powerful `__init_subclass__` hook. Learn to architect runtime behavior, enforce design patterns, and inject custom logic *before* your classes even fully exist. Elevate your object-oriented design from consumer to creator! 🛠️

Let's see how they let us define a custom class creation pipeline:

```python
import datetime

# --- Part 1: Using __init_subclass__ for a simple hook ---
# This method is called on the *base class* when a new *subclass* is defined.
class BaseModel:
_registry = [] # A simple registry for demonstration

def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# Enforce a docstring for all subclasses
if not cls.__doc__ or not cls.__doc__.strip():
raise TypeError(f"Class '{cls.__name__}' must have a docstring!")

# Add a creation timestamp at class creation time
cls._creation_timestamp = datetime.datetime.now()
BaseModel._registry.append(cls)
print(f"[{cls.__name__}] registered and timestamped at {cls._creation_timestamp.strftime('%H:%M:%S')}")

class MyData(BaseModel):
"""Represents some user data."""
def __init__(self, value):
self.value = value

class AnotherData(BaseModel):
"""More data here."""
pass

# # Uncomment the class below to see the TypeError in action!
# class NoDocstring(BaseModel):
# pass

# --- Part 2: A simple Metaclass example ---
# A metaclass controls how classes (its instances) are created.
class EnforcingMeta(type):
def __new__(mcs, name, bases, namespace):
if 'ID_PREFIX' not in namespace:
raise AttributeError(f"Class '{name}' using EnforcingMeta must define 'ID_PREFIX'.")
# Ensure ID_PREFIX starts with 'APP_'
if not namespace['ID_PREFIX'].startswith('APP_'):
raise ValueError(f"ID_PREFIX for '{name}' must start with 'APP_'.")

# Add a default method if not present
if 'get_full_id' not in namespace:
def default_get_full_id(self):
return f"{self.ID_PREFIX}-{self.item_id}"
namespace['get_full_id'] = default_get_full_id

return super().__new__(mcs, name, bases, namespace)

class MyItem(metaclass=EnforcingMeta):
ID_PREFIX = "APP_ITEM"
def __init__(self, item_id):
self.item_id = item_id

# Metaclasses are inherited, but specifying it explicitly works too.
class MyProduct(MyItem, metaclass=EnforcingMeta):
ID_PREFIX = "APP_PRODUCT"
def __init__(self, item_id, price):
super().__init__(item_id)
self.price = price

print("n--- Testing Instances ---")
data1 = MyData("Hello")
print(f"MyData created at: {MyData._creation_timestamp.strftime('%H:%M:%S')}")

item1 = MyItem(123)
print(f"Item ID: {item1.get_full_id()}")

product1 = MyProduct(456, 99.99)
print(f"Product ID: {product1.get_full_id()} | Price: {product1.price}")

print("n--- Registry Check ---")
for cls in BaseModel._registry:
print(f"- Registered: {cls.__name__}")
```

Output example:
```
[MyData] registered and timestamped at 14:02:45
[AnotherData] registered and timestamped at 14:02:45

--- Testing Instances ---
MyData created at: 14:02:45
Item ID: APP_ITEM-123
Product ID: APP_PRODUCT-456 | Price: 99.99

--- Registry Check ---
- Registered: MyData
- Registered: AnotherData
```

💡 **Challenge:** Modify the `EnforcingMeta` to also automatically add a *class method* `get_class_prefix()` that returns the `ID_PREFIX` defined by the class. How would you ensure this method correctly reflects the `ID_PREFIX` of the *subclass* if it overrides it? Share your solution in the comments! 👇

---
Hey Pythonistas! 👋 It's Day 69, and we're diving deep into the very fabric of Python! 🚀 Ever wondered how frameworks define their own rules or how you could build a mini-language within Python? Today, we unlock the power of Metaclasses! 🏗️

Think of them as factories for classes – they let you define *how* classes are created, enforcing architectural patterns, injecting attributes, or even building Domain-Specific Languages (DSLs) directly. It's advanced, it's mind-bending, and incredibly powerful! ✨ Get ready to architect reality.

```python
class DomainMetaclass(type):
"""
A metaclass to enforce structure and add common behavior to domain objects.
Here, it ensures a 'key' attribute exists and creates a registry for instances.
"""
def __new__(mcs, name, bases, dct):
# 1. Enforce/Default an attribute: Ensure every class has a 'key'
if "key" not in dct:
dct["key"] = name.lower() # Default key if not provided

# 2. Add class-level attributes: A registry for elements of this type
dct["_registered_elements"] = []

# 3. Create the class using the modified dictionary
cls = super().__new__(mcs, name, bases, dct)

# 4. Add methods to the newly created class
def register_instance(instance):
"""Class method to register instances of this class."""
cls._registered_elements.append(instance)
print(f"✨ Registered '{instance}' with key '{instance.key}' in {cls.__name__}'s domain.")

# Attach the class method
cls.register_instance = classmethod(register_instance)

return cls

# --- Define Domain-Specific Classes using our Metaclass ---

class Command(metaclass=DomainMetaclass):
"""Represents a command in our DSL."""
# Metaclass ensures 'key' exists. We can override it:
key = "cmd"

def __init__(self, name, action):
self.name = name
self.action = action
# Use the metaclass-provided method to register this instance
self.register_instance(self)

def __repr__(self):
return f"<{self.__class__.__name__}: {self.name}>"

class Query(metaclass=DomainMetaclass):
"""Represents a query in our DSL."""
# Let the metaclass define the 'key' (will be 'query')

def __init__(self, statement):
self.statement = statement
self.register_instance(self)

def __repr__(self):
return f"<{self.__class__.__name__}: '{self.statement[:20]}...'>"

# --- Demonstrate the DSL-like behavior ---
print("--- Creating Domain Objects ---")
run_cmd = Command("RunScript", "Execute the main script logic")
stop_cmd = Command("StopService", "Gracefully halt a service")
get_users_query = Query("SELECT * FROM users WHERE status='active'")
count_products_query = Query("COUNT(*) FROM products")

print("n--- Inspecting Registered Elements ---")
print(f"Registered Commands (key='{Command.key}'): {Command._registered_elements}")
print(f"Registered Queries (key='{Query.key}'): {Query._registered_elements}")
```

Output example:
```
--- Creating Domain Objects ---
✨ Registered '<Command: RunScript>' with key 'cmd' in Command's domain.
✨ Registered '<Command: StopService>' with key 'cmd' in Command's domain.
✨ Registered '<Query: 'SELECT * FROM users...' with key 'query' in Query's domain.
✨ Registered '<Query: 'COUNT(*) FROM prod...' with key 'query' in Query's domain.

--- Inspecting Registered Elements ---
Registered Commands (key='cmd'): [<Command: RunScript>, <Command: StopService>]
Registered Queries (key='query'): [<Query: 'SELECT * FROM users...'>, <Query: 'COUNT(*) FROM prod...'>]
```

**Small challenge:**
Modify `DomainMetaclass` to automatically add a default `execute` method (printing "Executing...") to `Command` classes and a `fetch` method (printing "Fetching...") to `Query` classes. How would you ensure `Command` classes *must* have a `description` attribute (raising an error if missing) instead of just a default `key`?

---
Hey Pythonistas! 👋 Ready to unlock Python's deepest secrets?

For Day 70 of our learning series, we're diving into the mind-bending world of **Python as a Self-Modifying Organism: Advanced AST Introspection for Runtime Rewrites!** 🐍🔬 Ever wondered if your code could change its own logic while running? Today, we'll explore Abstract Syntax Trees (ASTs) not just for static analysis, but for actively inspecting, transforming, and even generating Python code at runtime. This isn't just theoretical; it's a powerful technique for building custom DSLs, optimizing on-the-fly, or creating advanced metaprogramming tools! Let's manipulate the very structure of our programs! ✨

```python
import ast
import inspect

# 1. Define a simple function whose AST we want to modify
def calculate_expression(a, b):
# This function performs multiplication, addition, and subtraction
return a * b + 5 - 2

# 2. Convert the function's source code into an AST
# We need the source of just the function body for easier transformation.
# For demonstration, we'll use a simplified string to avoid inspect issues with decorators/indentation.
code_to_transform = """
def transformed_expression(a, b):
result = a * b + 5 - 2
return result
"""
tree = ast.parse(code_to_transform)

# 3. Create a NodeTransformer to replace specific operations
class MultiplyToAddTransformer(ast.NodeTransformer):
"""
Transforms multiplication operations (ast.Mult) into addition operations (ast.Add).
Also changes subtraction (ast.Sub) to division (ast.Div).
"""
def visit_BinOp(self, node):
if isinstance(node.op, ast.Mult):
# Replace multiplication with addition
node.op = ast.Add()
elif isinstance(node.op, ast.Sub):
# Replace subtraction with division
node.op = ast.Div()
# Ensure we visit child nodes to apply transformations recursively
self.generic_visit(node)
return node

# Apply the transformer to our AST
transformed_tree = MultiplyToAddTransformer().visit(tree)
ast.fix_missing_locations(transformed_tree) # Best practice for transformed ASTs

# 4. Compile and execute the transformed AST
# 'compile' converts the AST back into a code object.
# 'exec' runs the code object within a specified namespace.
compiled_code = compile(transformed_tree, filename='<transformed_ast>', mode='exec')

# Create a new scope for our transformed function
transformed_scope = {}
exec(compiled_code, transformed_scope)

# Get the newly defined function from our transformed scope
modified_expression_func = transformed_scope['transformed_expression']

# 5. Demonstrate the original vs. transformed behavior
val_a, val_b = 10, 2

print(f"Original logic (conceptually for calculate_expression({val_a}, {val_b})):")
print(f" {val_a} * {val_b} + 5 - 2 = {calculate_expression(val_a, val_b)}")

print("nTransformed logic (after AST rewrite):")
print(f" (Original 'a * b' became 'a + b', '5 - 2' became '5 / 2')")
# Conceptually: 10 + 2 + 5 / 2 -> 12 + 2.5 = 14.5
print(f" transformed_expression({val_a}, {val_b}) -> {modified_expression_func(val_a, val_b)}")
```

**Output example:**
```
Original logic (conceptually for calculate_expression(10, 2)):
10 * 2 + 5 - 2 = 23

Transformed logic (after AST rewrite):
(Original 'a * b' became 'a + b', '5 - 2' became '5 / 2')
transformed_expression(10, 2) -> 14.5
```

**Small Challenge:**
Can you modify the `MultiplyToAddTransformer` to instead replace all integer literals (e.g., `5`, `2`) in the `transformed_expression` function with a different fixed value, like `0`? (Hint: You'll need to look for `ast.Constant` nodes and check their `value` type.) Good luck, AST alchemist! 🧙‍♂️

---
Hey everyone! We're starting again from Day 1 so everyone can build a strong foundation by learning everything from the basics this time.
🚀 **Day 1: Your Python Journey Begins!** 🐍

Welcome to the very first step of our Python learning series! Today, we're diving straight into the basics with the legendary "Hello, World!" program. It's the simplest way to confirm your setup works and celebrate your first line of code. Get ready to write, run, and feel the magic! ✨ Let's make some noise!

Here's your first piece of Python code:

```python
print("Hello, World!")
```

Output example:
```
Hello, World!
```

💪 **Your challenge:**
1. Change the text inside the `print()` function to say "Hello, Python Learners!"
2. Try printing your own name!

---
Day 1: Introduction to Python with Hello World \- Code & Output