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
Which of the following built-in functions is primarily used in Python for dynamically creating new class types at runtime?
Anonymous Quiz
0%
`exec()`
0%
`eval()`
0%
`type()`
0%
`compile()`
Which of the following magic methods must a descriptor implement to control attribute assignment (setting a value)?
Anonymous Quiz
0%
__set__(self, instance, value)
0%
__get__(self, instance, owner)
0%
__delete__(self, instance)
0%
__init__(self, ...)
Here's your Telegram post for Day 55, focusing on "Zero-Copy Python"!

---

**Day 55: Zero-Copy Python: Leveraging `mmap` and `memoryview` for High-Throughput Data Pipelines 🚀**

Ever needed to process massive datasets without unnecessary memory overhead? Today, we're diving into `mmap` and `memoryview` – your power tools for high-throughput data pipelines! Learn how to directly access and manipulate data buffers, bypassing costly data copies. This technique is crucial for performance-critical applications, from network programming to large-scale data processing. Get ready to supercharge your Python code with true zero-copy operations! ⚡💾

```python
import mmap
import os
import tempfile

# 1. Create a temporary file and mmap it
FILE_SIZE = 64 # Small size for demo purposes
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
filepath = tmp_file.name
tmp_file.write(b'x00' * FILE_SIZE) # Pre-allocate space
tmp_file.flush() # Ensure data is written to disk

try:
with open(filepath, 'rb+') as f:
# Map the entire file into memory
with mmap.mmap(f.fileno(), 0) as mm:
print(f"Initial mmap content (first 10 bytes): {mm[:10]}")

# 2. Create a memoryview for zero-copy access
mv = memoryview(mm)

# Write data directly through the memoryview buffer
data_to_write = b"Hello, Zero-Copy World!"
mv[:len(data_to_write)] = data_to_write
print(f"Content after writing (through mv): {mv[:len(data_to_write)]}")

# 3. Create a sliced memoryview – still no copy!
# Let's extract "Zero-Copy" part
zero_copy_slice = mv[7:17]
print(f"Sliced view content ('Zero-Copy'): {zero_copy_slice.tobytes().decode()}")

# 4. Prove zero-copy: Modify the original mmap,
# and observe the change directly in the slice
mm[7:8] = b'X' # Change 'Z' to 'X' in "Zero-Copy"
print(f"Slice reflects mmap change ('Xero-Copy'): {zero_copy_slice.tobytes().decode()}")

# Read directly from mmap again to confirm its current state
print(f"Original mmap (start) confirms change: {mm[:len(data_to_write)].decode()}")

finally:
os.remove(filepath) # Clean up the temporary file
```

**Output example:**
```
Initial mmap content (first 10 bytes): b'x00x00x00x00x00x00x00x00x00x00'
Content after writing (through mv): b'Hello, Zero-Copy World!'
Sliced view content ('Zero-Copy'): Zero-Copy
Slice reflects mmap change ('Xero-Copy'): Xero-Copy
Original mmap (start) confirms change: Hello, Xero-Copy World!
```

**Small challenge:**
Explore how `memoryview` can wrap `bytes`, `bytearray`, `array.array`, and `numpy` arrays. How does this `buffer protocol` enhance interoperability for zero-copy operations across different data types? Share your findings! 👇

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

---

✨ **Day 56: Unveiling the Metaclass!** 🤯

Ready to peek behind Python's curtain? Today, we're diving into Metaclasses – the classes of classes! While `type` is Python's built-in metaclass, you can define your own to gain ultimate control over how classes are created. Think of it as intercepting the `class` statement itself! This allows for incredible power, from enforcing design patterns to injecting behavior at the class level. Let's unlock this advanced concept and design custom object creation beyond typical class definitions! 🛠️

```python
class EnforceMethodsMetaclass(type):
"""
A metaclass that enforces all methods in a class to start with 'do_'.
It also adds a custom class attribute.
"""
def __new__(mcs, name, bases, dct):
# 1. Enforce a custom class attribute
dct['_created_by_metaclass'] = True
dct['_metaclass_version'] = '1.0'

# 2. Iterate through the dict to check method names
for attr_name, attr_value in dct.items():
# Check if it's a callable (method) and not a special method
if callable(attr_value) and not attr_name.startswith('__'):
if not attr_name.startswith('do_'):
raise TypeError(
f"Method '{attr_name}' in class '{name}' "
"must start with 'do_'"
)

# Call the base type's __new__ to actually create the class object
cls = super().__new__(mcs, name, bases, dct)
return cls

# Using our custom metaclass
class MyDataProcessor(metaclass=EnforceMethodsMetaclass):
def __init__(self, data):
self.data = data

def do_process_data(self):
"""Processes the internal data."""
return [item * 2 for item in self.data]

def do_display_results(self, processed_data):
"""Displays the processed data."""
return f"Processed results: {processed_data}"

# This class would raise a TypeError because 'calculate' doesn't start with 'do_'
# class BadProcessor(metaclass=EnforceMethodsMetaclass):
# def calculate(self, x):
# return x * x

# Instantiate and use the class
processor = MyDataProcessor([1, 2, 3])
processed = processor.do_process_data()
print(processor.do_display_results(processed))

# Access attributes added by the metaclass
print(f"Created by metaclass: {MyDataProcessor._created_by_metaclass}")
print(f"Metaclass version: {MyDataProcessor._metaclass_version}")

```

Output example:
```
Processed results: [2, 4, 6]
Created by metaclass: True
Metaclass version: 1.0
```

**Challenge:** Modify `EnforceMethodsMetaclass` to automatically add a `__repr__` method to any class it creates. This `__repr__` method should return a string like `<ClassName object with data: [...]>`. 🚀

---
Here's your Telegram post for Day 58!

---

🌟 **Day 58: Metaclasses as Runtime Policy Engines: Enforcing Constraints Before `__init__`** 🚀

Ready to dive deep into Python's object model? Today, we're unlocking the true power of **metaclasses**! Forget simple class decorators; metaclasses allow you to become a "factory for classes" themselves. We'll explore how they can act as powerful runtime policy engines, enforcing architectural rules, validating class structures, or injecting behavior *before a class is even fully defined*, let alone instantiated. This is where truly robust and self-validating systems are born! Let's build classes that *cannot* be badly formed. ✨

```python
# 1. Define our Metaclass
class EnforceMethodsMeta(type):
"""
A metaclass that enforces the presence of specific methods in classes
that use it.
"""
REQUIRED_METHODS = ['process_data', 'validate_input']

def __new__(mcs, name, bases, namespace):
# We need to check the 'namespace' (the class's dict) for methods.
# This runs *before* the class object is fully created.
for method_name in mcs.REQUIRED_METHODS:
if method_name not in namespace or not callable(namespace[method_name]):
raise TypeError(
f"Class '{name}' must implement the '{method_name}' method."
)

# If all checks pass, proceed to create the class
return super().__new__(mcs, name, bases, namespace)

# 2. A class that correctly adheres to the policy
class ValidProcessor(metaclass=EnforceMethodsMeta):
def process_data(self, data):
print(f"Processing: {data}")

def validate_input(self, data):
print(f"Validating: {data}")
return True

# 3. A class that violates the policy
try:
class InvalidProcessor(metaclass=EnforceMethodsMeta):
def process_data(self, data):
print(f"Processing only: {data}")
# Missing 'validate_input' method!

except TypeError as e:
print(f"nCaught expected error: {e}")

# 4. Using the valid class
print("n--- Instantiating ValidProcessor ---")
vp = ValidProcessor()
vp.process_data("sample input")
vp.validate_input("sample input")
```

**Output example:**
```
Caught expected error: Class 'InvalidProcessor' must implement the 'validate_input' method.

--- Instantiating ValidProcessor ---
Processing: sample input
Validating: sample input
```

### Small Challenge:
Modify `EnforceMethodsMeta` to not only check for method existence but also to ensure that a method named `process_data` accepts exactly one positional argument (excluding `self`). Hint: You might need to inspect the method's signature within the metaclass's `__new__`. Good luck! 🧐

---
Here's your Telegram post for Day 60!

---

**Day 60: Event Loop Alchemy: Building Custom `asyncio` Schedulers and Executors**

Day 60: Ready for the ultimate Python deep dive? 🤯 Today, we're not just creating objects; we're *sculpting* the very blueprint of classes themselves! ✨ Unlock the mystical powers of **Custom Metaclasses** and the elegant **`__init_subclass__`** method. Learn how to dynamically alter class creation, inject methods, enforce patterns, and gain unparalleled control over your Python runtime. This is where Python truly becomes a malleable language in your hands. Let's transcend `type()`! 🚀

```python
class RegistryMeta(type):
"""
A custom metaclass that automatically registers classes and injects a 'report' method.
It acts as a factory for classes, not instances.
"""
_registered_classes = {} # A dictionary to keep track of all classes using this metaclass

def __new__(cls, name, bases, class_dict):
# 1. Create the new class object using the default type.__new__
new_class = super().__new__(cls, name, bases, class_dict)

# 2. Modify the new class: inject a custom method
def _report_method(instance):
"""A method injected into every class created by RegistryMeta."""
return f"Instance of {new_class.__name__} created by RegistryMeta."
setattr(new_class, 'report', _report_method) # Dynamically add 'report' method

# 3. Register the new class in our metaclass's internal registry
RegistryMeta._registered_classes[name] = new_class

# 4. Return the (potentially modified) new class
return new_class

# --- Using our Custom Metaclass ---

class MyService(metaclass=RegistryMeta):
"""A class using RegistryMeta, automatically gaining a 'report' method."""
def __init__(self, service_id):
self.service_id = service_id

def get_id(self):
return self.service_id

class DataProcessor(metaclass=RegistryMeta):
"""Another class benefiting from RegistryMeta."""
def process(self, data):
return f"Processing '{data}' with DataProcessor."

# --- Demonstrate the effects ---

service_instance = MyService("SVC001")
print(f"Service ID: {service_instance.get_id()}")
print(f"Service Report: {service_instance.report()}") # Method injected by metaclass

data_instance = DataProcessor()
print(f"Data Processor Report: {data_instance.report()}") # Method injected by metaclass
print(f"Processed Data: {data_instance.process('raw_log_entry')}")

print("n--- All Classes Registered by RegistryMeta ---")
for class_name, cls_obj in RegistryMeta._registered_classes.items():
print(f"- {class_name}: {cls_obj}")

```

Output example:
Service ID: SVC001
Service Report: Instance of MyService created by RegistryMeta.
Data Processor Report: Instance of DataProcessor created by RegistryMeta.
Processed Data: Processing 'raw_log_entry' with DataProcessor.

--- All Classes Registered by RegistryMeta ---
- MyService: <class '__main__.MyService'>
- DataProcessor: <class '__main__.DataProcessor'>

💡 **Your Advanced Challenge:**
Can you achieve a similar runtime class modification using `__init_subclass__`? 🕵️‍♀️ Create a base class `Auditable` that, when subclassed, automatically ensures the subclass has a `_version` attribute set to `1.0` if not already present. If a subclass *doesn't* define it, `__init_subclass__` should add it! (Hint: `__init_subclass__` is a class method called on the base class for new subclasses.)

---
🚀 Day 61: Ever wondered how `property` works, or how ORMs validate fields behind the scenes? 🤔 Welcome to the captivating world of Python Descriptors! These powerful objects allow you to customize attribute access (get, set, delete) on a class level. This means you can add validation, caching, lazy loading, or even complex ORM-like field definitions with elegant, reusable logic. Uncover the secret lives of these unsung heroes and start crafting smarter, more robust attributes! 🐍✨

```python
class NonNegativeNumber:
"""
A descriptor that enforces an attribute to be a non-negative integer or float.
"""
def __set_name__(self, owner, name):
# Store the actual attribute name (e.g., 'price')
# We'll store the value in a 'private' attribute (e.g., '_price')
self._private_name = f'_{name}'
self._public_name = name

def __get__(self, obj, objtype=None):
# If accessed via the class (e.g., Product.price), return the descriptor itself.
# Otherwise, return the instance's value.
if obj is None:
return self
return getattr(obj, self._private_name)

def __set__(self, obj, value):
# Validation logic for setting the attribute
if not isinstance(value, (int, float)):
raise TypeError(
f"Attribute '{self._public_name}' must be an int or float, "
f"got {type(value).__name__}."
)
if value < 0:
raise ValueError(
f"Attribute '{self._public_name}' must be non-negative, "
f"got {value}."
)
# Store the validated value in the 'private' attribute
setattr(obj, self._private_name, value)

class Product:
price = NonNegativeNumber()
quantity = NonNegativeNumber()

def __init__(self, name: str, price: float, quantity: int):
self.name = name
# These assignments trigger the descriptor's __set__ method
self.price = price
self.quantity = quantity

print("--- Valid Assignments ---")
p1 = Product("Gaming Laptop", 1299.99, 10)
print(f"Product: {p1.name}, Price: ${p1.price}, Quantity: {p1.quantity}")

p2 = Product("USB Cable", 5.50, 100)
p2.price = 6.75 # This will also use the descriptor's __set__
print(f"Product: {p2.name}, Price: ${p2.price}, Quantity: {p2.quantity}")

print("n--- Invalid Type Assignment ---")
try:
p1.price = "expensive" # String is not allowed
except (TypeError, ValueError) as e:
print(f"Error: {e}")

print("n--- Invalid Value Assignment ---")
try:
p1.quantity = -5 # Negative value is not allowed
except (TypeError, ValueError) as e:
print(f"Error: {e}")

print("n--- Accessing Descriptor from Class ---")
print(f"Product.price is of type: {type(Product.price)}")
```

Output example:
```
--- Valid Assignments ---
Product: Gaming Laptop, Price: $1299.99, Quantity: 10
Product: USB Cable, Price: $6.75, Quantity: 100

--- Invalid Type Assignment ---
Error: Attribute 'price' must be an int or float, got str.

--- Invalid Value Assignment ---
Error: Attribute 'quantity' must be non-negative, got -5.

--- Accessing Descriptor from Class ---
Product.price is of type: <class '__main__.NonNegativeNumber'>
```

### Small Challenge 🧠
Your Challenge: Create a new descriptor called `StringLengthValidator` that takes `min_length` and `max_length` arguments in its `__init__` method. It should ensure that any string assigned to its attribute falls within the specified length range (inclusive). Test it on a `username` attribute for a `User` class! Good luck! 🚀

---
Which set of magic methods primarily defines the core functionality of a Python descriptor, controlling attribute access?
Anonymous Quiz
0%
A. `__get__`, `__set__`, `__delete__`
0%
B. `__init__`, `__call__`, `__str__`
0%
C. `__new__`, `__repr__`, `__eq__`
0%
D. `__enter__`, `__exit__`, `__next__`
🐍✨ **Day 64: Rewiring Python: Manipulating the AST for Runtime Language Extension** ✨🚀

Today, we're diving deep into the very structure of your Python code: the Abstract Syntax Tree (AST)! Forget simple `eval()` or `exec()`. By parsing your code into an AST, you can inspect, modify, and even generate Python code programmatically *before* it gets compiled into bytecode. This is the ultimate form of metaprogramming, allowing you to create runtime language extensions, custom compilers, or powerful code transformation tools. Unleash Python's full potential by bending its syntax to your will!

```python
import ast
import inspect

class FunctionEntryLogger(ast.NodeTransformer):
"""
A NodeTransformer that inserts a print statement at the beginning
of every function definition to log when the function is entered.
"""
def visit_FunctionDef(self, node):
# Create a print statement: print(f"Entering function {node.name}()")
log_message = ast.JoinedStr(
values=[
ast.Constant(value="Entering function "),
ast.FormattedValue(
value=ast.Attribute(value=ast.Name(id='node', ctx=ast.Load()), attr='name', ctx=ast.Load()),
conversion=-1,
format_spec=None
),
ast.Constant(value="()")
]
)
print_call = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[log_message],
keywords=[]
)
)

# Insert the print call as the first statement in the function body
node.body.insert(0, print_call)

# Ensure we visit child nodes (e.g., statements within the function)
self.generic_visit(node)
return node

# Original Python code string
original_code = """
def greet(name):
message = f"Hello, {name}!"
return message

def calculate_sum(a, b):
result = a + b
return result

print(greet("Alice"))
print(calculate_sum(10, 20))
"""

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

# 2. Apply the transformation using our custom NodeTransformer
transformer = FunctionEntryLogger()
modified_tree = transformer.visit(tree)

# 3. (Optional) Unparse the modified AST back into Python code for inspection
# ast.unparse requires Python 3.9+
print("--- Modified Code (unparsed) ---")
print(ast.unparse(modified_tree))
print("------------------------------n")

# 4. Compile the modified AST into executable code and execute it
print("--- Execution Output ---")
compiled_code = compile(modified_tree, filename='<ast_rewired>', mode='exec')
exec(compiled_code)
print("------------------------")
```

Output example:
```
--- Modified Code (unparsed) ---
def greet(name):
print(f'Entering function {node.name}()')
message = f"Hello, {name}!"
return message

def calculate_sum(a, b):
print(f'Entering function {node.name}()')
result = a + b
return result

print(greet("Alice"))
print(calculate_sum(10, 20))
------------------------------

--- Execution Output ---
Entering function greet()
Hello, Alice!
Entering function calculate_sum()
30
------------------------
```

🧠 **Small Challenge:**
Modify the `FunctionEntryLogger` to not only log when a function is entered but also to log the values of its arguments. For instance, `print(f"Entering function {node.name}() with args: {inspect.signature(original_function).parameters}")`. (Hint: you'll need a way to get the argument names and values at runtime, potentially by modifying the function signature or inspecting the call).

---
Which `ast` module class is primarily designed for traversing and *modifying* nodes in an Abstract Syntax Tree?
Anonymous Quiz
0%
A) ast.NodeTransformer
0%
B) ast.NodeVisitor
0%
C) ast.AST
0%
D) ast.parse
🚀 Dive deeper than ever before into Python's core on Day 65 of our learning series! ✨ Today, we're not just writing code; we're *crafting* how Python creates classes, managing memory like a pro, and even peering into its bytecode soul. Get ready to unlock truly advanced concepts that empower you to build robust, high-performance, and custom-tailored Python applications. Let's make your code do more than just execute – let's make it smarter! 🧠

---

### **Topic Spotlight: Beyond `type`: Crafting Domain-Specific Class Creation with Metaclasses**

Here's how you can use metaclasses to enforce structure and perform actions when a class is *defined*, not just when an object is instantiated.

```python
class RegistryMeta(type):
"""
A metaclass that automatically registers classes that use it,
and ensures they define a 'name' attribute.
"""
_registry = {} # Stores all classes registered by this metaclass

def __new__(cls, name, bases, dct):
# 1. Validate mandatory attributes during class definition
if 'name' not in dct:
raise TypeError(f"Class '{name}' using RegistryMeta must define a 'name' attribute.")

# 2. Prevent duplicate names (simple example)
class_name_val = dct['name']
if class_name_val in cls._registry:
raise ValueError(f"Class with name '{class_name_val}' already registered.")

# 3. Create the actual class object
new_class = super().__new__(cls, name, bases, dct)

# 4. Perform custom action: Register the newly created class
cls._registry[class_name_val] = new_class
print(f"✅ Registered new class: {name} (via {class_name_val})")
return new_class

class BaseWorker(metaclass=RegistryMeta):
"""
Base class that will be managed by RegistryMeta.
"""
pass

class DataProcessor(BaseWorker):
name = "data_processor_v1"

def process(self, data):
return f"Processing '{data}' with {self.name}"

class ReportGenerator(BaseWorker):
name = "report_generator_v2"

def generate(self, user):
return f"Generating report for '{user}' with {self.name}"

# # This would raise a TypeError because 'name' is missing
# class MissingNameWorker(BaseWorker):
# def do_something(self):
# pass

print("n--- Current Registry ---")
for key, cls_obj in RegistryMeta._registry.items():
print(f" '{key}': {cls_obj.__name__}")

print("n--- Using Registered Classes ---")
processor = RegistryMeta._registry["data_processor_v1"]()
print(processor.process("sales_data.csv"))

generator = RegistryMeta._registry["report_generator_v2"]()
print(generator.generate("Alice"))
```

---

Output example:
```
✅ Registered new class: DataProcessor (via data_processor_v1)
✅ Registered new class: ReportGenerator (via report_generator_v2)

--- Current Registry ---
'data_processor_v1': DataProcessor
'report_generator_v2': ReportGenerator

--- Using Registered Classes ---
Processing 'sales_data.csv' with data_processor_v1
Generating report for 'Alice' with report_generator_v2
```

---

### Small Challenge:
Modify the `RegistryMeta` metaclass to also automatically add a `created_at` class attribute (e.g., using `datetime.now()`) to every class that uses it, during its definition. 🕰️

---

### Quiz Question:
🚀 Day 68: Dive deep into Python's hidden gems! Today, we're unmasking **Descriptors**! ✨ These aren't just fancy `property` alternatives; they're powerful mechanisms enabling self-configuring attributes, proxy objects, and much more. Ever wondered how `property`, `staticmethod`, or `classmethod` work under the hood? It's all about descriptors! Get ready to elevate your Python architectural skills. Let's craft some magic! 🎩

```python
import numbers

class PositiveInteger:
"""
A descriptor that ensures an attribute is a positive integer.
"""
def __set_name__(self, owner, name):
# Called automatically when the descriptor is assigned to a class attribute.
self.public_name = name
self.private_name = '_' + name # Store value in a private attribute

def __get__(self, obj, objtype=None):
if obj is None:
# Accessing descriptor from the class (e.g., Product.price)
return self
# Accessing descriptor from an instance (e.g., product1.price)
return getattr(obj, self.private_name)

def __set__(self, obj, value):
# Called when setting the attribute (e.g., product1.price = 100)
if not isinstance(value, numbers.Integral):
raise TypeError(f'Attribute "{self.public_name}" must be an integer.')
if value <= 0:
raise ValueError(f'Attribute "{self.public_name}" must be positive.')
setattr(obj, self.private_name, value)

def __delete__(self, obj):
# Called when deleting the attribute (e.g., del product1.price)
delattr(obj, self.private_name)

class Product:
price = PositiveInteger()
quantity = PositiveInteger()

def __init__(self, name, price, quantity):
self.name = name
self.price = price # Uses the descriptor's __set__
self.quantity = quantity # Uses the descriptor's __set__

def display_info(self):
print(f"Product: {self.name}, Price: ${self.price}, Quantity: {self.quantity}")

# --- Demonstration ---
print("--- Valid Product ---")
product1 = Product("Laptop", 1200, 5)
product1.display_info()
print(f"Initial price: {product1.price}")

print("n--- Modifying price (valid) ---")
product1.price = 1250
print(f"New price: {product1.price}")

print("n--- Trying invalid price (negative) ---")
try:
product1.price = -100
except ValueError as e:
print(f"Error: {e}")

print("n--- Trying invalid quantity (non-integer) ---")
try:
product2 = Product("Mouse", 25, 2.5)
except TypeError as e:
print(f"Error: {e}")

print("n--- Accessing descriptor from class ---")
print(f"Product.price is a {type(Product.price).__name__} object.")
```

Output example:
```
--- Valid Product ---
Product: Laptop, Price: $1200, Quantity: 5
Initial price: 1200

--- Modifying price (valid) ---
New price: 1250

--- Trying invalid price (negative) ---
Error: Attribute "price" must be positive.

--- Trying invalid quantity (non-integer) ---
Error: Attribute "quantity" must be an integer.

--- Accessing descriptor from class ---
Product.price is a PositiveInteger object.
```

💡 **Your Challenge:** Modify the `PositiveInteger` descriptor to also accept an optional `min_value` and `max_value` during its instantiation (e.g., `price = BoundedInteger(min_value=1, max_value=5000)`). How would you store and use these bounds within the descriptor? Share your solution! 👇
What special methods must a class implement to function as a data descriptor in Python, affecting both attribute reading and writing?
Anonymous Quiz
0%
`__get__` and `__set__`
0%
`__getattr__` and `__setattr__`
0%
`__getitem__` and `__setitem__`
0%
`__call__` and `__init__`
Hey Pythonistas! 👋 Ready to unlock a deeper understanding of how Python truly works?

On Day 69 of our deep-dive series, we're venturing into the compiler's lair! 🤯 We'll be **Deconstructing the AST: Building Compiler-Level Tools and Runtime Code Transformers**. Ever wondered how linters, static analyzers, or even IDEs understand your code? It's all thanks to the Abstract Syntax Tree (AST) – Python's internal, structured representation of your code. By learning to inspect, traverse, and even modify this tree, you gain immense power for custom static analysis, code generation, and runtime transformations. Let's peek under the hood! 🕵️‍♀️

```python
import ast

code_snippet = """
def calculate_discount(price, discount_percentage):
# This function calculates the discounted price.
if price > 100 and discount_percentage > 0.1:
discount_amount = price * discount_percentage
final_price = price - discount_amount
return final_price
else:
return price
"""

class ASTInspector(ast.NodeVisitor):
"""
A simple AST visitor to inspect function definitions, assignments,
and conditional statements within our code snippet.
"""
def visit_FunctionDef(self, node):
print(f"➡️ Function: '{node.name}' declared on line {node.lineno}")
self.generic_visit(node) # Important: visit children nodes too

def visit_Assign(self, node):
targets = [t.id for t in node.targets if isinstance(t, ast.Name)]
print(f" 📝 Assignment to '{', '.join(targets)}' on line {node.lineno}")
self.generic_visit(node)

def visit_If(self, node):
print(f" ❓ If statement starts on line {node.lineno}")
self.generic_visit(node)

def visit_Constant(self, node):
# We can even see literal values!
if isinstance(node.value, (int, float, str, bool, type(None))):
print(f" 🌟 Constant value: {repr(node.value)} on line {node.lineno}")
self.generic_visit(node)

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

# 2. Inspect the AST using our custom visitor
print("--- Beginning AST Inspection ---")
ASTInspector().visit(tree)
print("--- End AST Inspection ---")
```

Output example:
```
--- Beginning AST Inspection ---
➡️ Function: 'calculate_discount' declared on line 2
❓ If statement starts on line 4
🌟 Constant value: 100 on line 4
🌟 Constant value: 0.1 on line 4
📝 Assignment to 'discount_amount' on line 5
📝 Assignment to 'final_price' on line 6
❓ If statement starts on line 4
--- End AST Inspection ---
```

Challenge for today:
Modify the `ASTInspector` to count how many `ast.BinOp` (binary operations like `+`, `-`, `*`, `/`) nodes are present within the `calculate_discount` function. Hint: you'll need a `visit_BinOp` method and a counter! Good luck, ASTronaut! 🚀

---
Which Python module is primarily used to parse Python source code into an Abstract Syntax Tree (AST)?
Anonymous Quiz
67%
ast
0%
inspect
0%
compiler
33%
sys