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 special method in a descriptor is responsible for handling attribute assignment (e.g., `obj.attr = value`)?
Anonymous Quiz
0%
__get__
0%
__set__
0%
__delete__
0%
__set_name__
🐍✨ **Day 33: The Type System Strikes Back!** ✨🐍

Ready to supercharge your Python types? Today, we're diving deep beyond basic type hints into the advanced realms of generics, protocol-oriented design, and robust runtime type verification. Learn how to build more flexible, maintainable, and error-proof code by leveraging Python's powerful type system to define clear interfaces and validate data where it matters most – at runtime! Stop bugs before they even begin to crawl. Let's make our Python smarter! 🚀

```python
from typing import Protocol, TypeVar, Generic, List
from pydantic import BaseModel, ValidationError, Field

# 1. Protocol-Oriented Design
class Printable(Protocol):
"""A protocol for objects that can print themselves."""
def print_self(self) -> None:
...

class MyData:
def __init__(self, value: str):
self.value = value

def print_self(self) -> None:
print(f"MyData: {self.value}")

def process_printable(item: Printable) -> None:
item.print_self()

# 2. Advanced Generics
T = TypeVar('T')

class Box(Generic[T]):
def __init__(self, content: T):
self.content = content

def get_content(self) -> T:
return self.content

# 3. Runtime Type Verification with Pydantic
class UserProfile(BaseModel):
id: int = Field(..., gt=0, description="Unique user ID, must be positive")
username: str
email: str
is_active: bool = True
tags: List[str] = []

print("--- Protocol Example ---")
data_item = MyData("Hello Advanced Types!")
process_printable(data_item)

print("n--- Generics Example ---")
int_box = Box[int](123)
str_box = Box[str]("Python is awesome")
print(f"Int Box Content: {int_box.get_content()}")
print(f"Str Box Content: {str_box.get_content()}")

print("n--- Pydantic Runtime Validation ---")
valid_user_data = {
"id": 101,
"username": "type_master",
"email": "master@example.com"
}
try:
user = UserProfile(**valid_user_data)
print(f"Valid User: {user.model_dump_json(indent=2)}")
except ValidationError as e:
print(f"Validation Error: {e}")

invalid_user_data = {
"id": 0, # Should fail (gt=0)
"username": "buggy_user",
"email": "invalid" # Not a valid email format for Pydantic (though not explicitly set as EmailStr here, it might infer)
}
try:
user = UserProfile(**invalid_user_data)
print(f"Invalid User: {user.model_dump_json(indent=2)}")
except ValidationError as e:
print(f"Validation Error encountered:n{e.json(indent=2)}")

```

**Output example:**
```
--- Protocol Example ---
MyData: Hello Advanced Types!

--- Generics Example ---
Int Box Content: 123
Str Box Content: Python is awesome

--- Pydantic Runtime Validation ---
Valid User: {
"id": 101,
"username": "type_master",
"email": "master@example.com",
"is_active": true,
"tags": []
}
Validation Error encountered:
[
{
"type": "greater_than",
"loc": [
"id"
],
"msg": "Input should be greater than 0",
"input": 0,
"ctx": {
"gt": 0
},
"url": "https://errors.pydantic.dev/2.5/v/greater_than"
}
]
```

---
💡 **Small Challenge:**
Modify the `UserProfile` Pydantic model to explicitly use `EmailStr` for the `email` field and add a custom validator that ensures the `username` is not "admin". Observe how the validation errors change!

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

---

Hey Pythonistas! 👋 Day 34 is here, and we're diving deep into the very fabric of Python code itself! 🤯 Ever wondered how linters, optimizers, or even new language features are built? The secret often lies in Python's Abstract Syntax Tree (AST)! 🌳

Today, we'll learn to *programmatically rewrite our own code* by manipulating its AST. Get ready to sculpt Python at its core – this isn't just advanced; it's truly transformative. Let's peel back the layers! 🛠️

```python
import ast
import inspect

# Original Python code as a string
original_code_str = """
def calculate_square(x):
result = x * x
return result

def greet(name):
print(f"Hello, {name}!")
"""

class FunctionLoggerTransformer(ast.NodeTransformer):
"""
A transformer that adds a print statement at the beginning
of every function definition to log entry.
"""
def visit_FunctionDef(self, node):
# Create a new print statement node: print(f"Entering function '{node.name}'...")
# This constructs an AST node for: print("Entering function 'func_name'...")
log_message = ast.Constant(value=f"Entering function '{node.name}'...")
log_call = ast.Expr(
value=ast.Call(
func=ast.Name(id='print', ctx=ast.Load()),
args=[log_message],
keywords=[]
)
)

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

# Important: Call generic_visit to ensure child nodes are also visited
# in case there are other transformations to apply within the function.
return self.generic_visit(node)

# 1. Parse the original code into an Abstract Syntax Tree (AST)
tree = ast.parse(original_code_str)

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

# 3. Fix missing line numbers and other metadata in the new_tree.
# This is crucial for ast.unparse to work correctly, especially on modified nodes.
ast.fix_missing_locations(new_tree)

# 4. Unparse the modified AST back into Python source code (requires Python 3.9+)
transformed_code_str = ast.unparse(new_tree)

print("--- Original Code ---")
print(original_code_str)
print("n--- Transformed Code ---")
print(transformed_code_str)

# To actually execute the transformed code, you could use exec():
# print("n--- Executing Transformed Code ---")
# exec(transformed_code_str)
# calculate_square(10)
# greet("AST Explorer")
```

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

def calculate_square(x):
result = x * x
return result

def greet(name):
print(f"Hello, {name}!")

--- Transformed Code ---
def calculate_square(x):
print(f"Entering function 'calculate_square'...")
result = x * x
return result
def greet(name):
print(f"Entering function 'greet'...")
print(f"Hello, AST Explorer!")
```

**Small Challenge:**
Your turn! Can you modify the `FunctionLoggerTransformer` to rename the variable `result` to `squared_value` *only* within the `calculate_square` function? 🤔 (Hint: you'll need to check the function name in `visit_FunctionDef` and then potentially define a new transformer or specifically handle `ast.Name` nodes within that scope.)

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

---

Hey Pythonistas! 👋 Ready to peel back another layer of Python's magic? ✨

For Day 35, we're stepping into the control room of class creation with **Metaclass Maestro: Orchestrating Runtime Behavior with `type` and `__prepare__`**! 🎩🐍 Ever wondered how Python classes are truly born? Beyond `class MyClass:`, there's a fascinating world where `type` itself orchestrates their creation. Uncover the ultimate control over class definition, from customizing its dictionary *before* attributes are even added, to forging unique object models. This is where Python's magic truly reveals its mechanics!

```python
import collections

class CustomNamespaceMeta(type):
@classmethod
def __prepare__(metacls, name, bases, **kwargs):
"""
Called before the class body is executed.
Returns the mapping object that will serve as the class's namespace.
Using OrderedDict to preserve attribute definition order.
"""
print(f"[{name}] __prepare__ called: Creating an OrderedDict for class namespace.")
return collections.OrderedDict()

def __new__(metacls, name, bases, namespace, **kwargs):
"""
Called to create the new class object.
'namespace' is the mapping object returned by __prepare__,
now populated with attributes defined in the class body.
"""
print(f"[{name}] __new__ called: Namespace type is {type(namespace)}")
print(f"[{name}] Namespace keys (in order): {list(namespace.keys())}")

# We can inspect or even modify the namespace here before the class is fully created
namespace['meta_added_magic'] = f"Hello from {name}'s Metaclass!"

# Call the default type.__new__ to actually create the class object
return super().__new__(metacls, name, bases, namespace)

def __init__(cls, name, bases, namespace, **kwargs):
"""
Called after the class object has been created by __new__.
Used for post-creation setup.
"""
print(f"[{name}] __init__ called: Class '{name}' fully created.")
super().__init__(cls, name, bases, namespace)

class MyMetaclassedClass(metaclass=CustomNamespaceMeta):
# These attributes will populate the OrderedDict prepared by CustomNamespaceMeta
first_attr = 10
second_method = lambda self: "Method defined in class body"
third_value = [1, 2, 3]

# --- Demonstrate usage ---
print("n--- Inspecting MyMetaclassedClass ---")
# Access the attribute added by the metaclass
print(f"Value of 'meta_added_magic': {MyMetaclassedClass.meta_added_magic}")
# Check for a user-defined attribute
print(f"Is 'first_attr' present?: {'first_attr' in MyMetaclassedClass.__dict__}")

# Demonstrate the order if we iterate through __dict__ (which is a regular dict typically)
# The OrderedDict primarily affects the namespace passed to __new__
# The actual __dict__ of the class might not preserve order unless the metaclass explicitly copies it.
# However, the key is showing the *namespace* object in __new__ had the order.
```

Output example:
```
[MyMetaclassedClass] __prepare__ called: Creating an OrderedDict for class namespace.
[MyMetaclassedClass] __new__ called: Namespace type is <class 'collections.OrderedDict'>
[MyMetaclassedClass] Namespace keys (in order): ['__module__', '__qualname__', 'first_attr', 'second_method', 'third_value', 'meta_added_magic']
[MyMetaclassedClass] __init__ called: Class 'MyMetaclassedClass' fully created.

--- Inspecting MyMetaclassedClass ---
Value of 'meta_added_magic': Hello from MyMetaclassedClass's Metaclass!
Is 'first_attr' present?: True
```

### Challenge 🚀
Can you modify the `CustomNamespaceMeta` to automatically uppercase the names of all attributes (variables, not methods) defined in `MyMetaclassedClass`? For instance, `first_attr` should become `FIRST_ATTR`. Hint: You'll need to iterate and modify the `namespace` *before* calling `super().__new__`.

---
In Python's descriptor protocol, which special method is invoked when you attempt to set a value to an attribute that is managed by a descriptor (e.g., `instance.my_attribute = 'new_value'`)?
Anonymous Quiz
0%
__get__(self, instance, owner)
0%
__set__(self, instance, value)
0%
__delete__(self, instance)
0%
__set_name__(self, owner, name)
Here's your Telegram post for Day 38 of the Python learning series! 🚀

---

**Day 38: Static Contract Enforcement 🛡️ Mastering Protocols and Generic Typevars for Robust APIs**

Ever wanted to define an interface without inheritance, enforcing specific behaviors across diverse classes? Python's `typing.Protocol` is your answer! 🦆 It brings structural subtyping (duck typing) to the type checker, ensuring your APIs are robust and predictable. Combine this with `TypeVar` for truly generic, type-safe functions. Build libraries that scream reliability! ✨

```python
from typing import Protocol, TypeVar, runtime_checkable

# 1. Define a Protocol for objects that can be 'printed'.
# @runtime_checkable allows isinstance() checks at runtime, though Protocols
# are primarily for static analysis by tools like mypy.
@runtime_checkable
class Printable(Protocol):
"""
A protocol for objects that provide a string representation
via a 'to_string' method.
"""
def to_string(self) -> str:
# Ellipsis indicates an abstract method signature; no implementation needed here.
...

# 2. Implementations (implicit: no explicit inheritance from Printable required!)
class MyObject:
def __init__(self, value: int):
self.value = value

def to_string(self) -> str:
return f"MyObject(value={self.value})"

class AnotherType:
def __init__(self, name: str):
self.name = name

def to_string(self) -> str:
return f"AnotherType(name='{self.name}')"

class NotPrintable:
def __init__(self, data: list):
self.data = data
# This class *does not* have a 'to_string' method, so it does not conform.

# 3. A Generic Function that expects any type conforming to the Printable protocol.
# 'bound=Printable' ensures that P must be Printable or a subtype thereof.
P = TypeVar('P', bound=Printable)

def process_and_print(item: P) -> None:
"""
Takes any object conforming to the Printable protocol and prints its
string representation. Static type checkers will ensure 'item' has 'to_string'.
"""
print(f"Processing and printing: {item.to_string()}")

# 4. Demonstrate usage and type checking
if __name__ == "__main__":
obj1 = MyObject(123)
obj2 = AnotherType("Protocol Example")
obj_not_printable = NotPrintable([1, 2, 3])

process_and_print(obj1)
process_and_print(obj2)

# Check conformity at runtime (thanks to @runtime_checkable)
print(f"nIs obj1 a Printable? {isinstance(obj1, Printable)}")
print(f"Is obj_not_printable a Printable? {isinstance(obj_not_printable, Printable)}")

# If you tried to uncomment the line below and run mypy, it would flag a type error!
# process_and_print(obj_not_printable)
# At runtime, calling process_and_print with obj_not_printable would raise an AttributeError.
```

Output example:
```
Processing and printing: MyObject(value=123)
Processing and printing: AnotherType(name='Protocol Example')

Is obj1 a Printable? True
Is obj_not_printable a Printable? False
```

**Small challenge:**
Extend the `Printable` protocol to also require a `get_id()` method that returns an `int`. Then, create a new class that conforms to this extended protocol and try passing it to `process_and_print`. How would mypy react if `get_id()` were missing from one of your conforming classes?

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

---

🚀 **Day 40: Unlocking Python's Mighty Descriptor Protocol!** 🎩

Ready to peek behind the curtain of Python's object model? Today, we're diving deep into *Descriptors*! Far beyond simple `@property` decorators, descriptors are a powerful mechanism that allows you to customize *how* attributes are accessed, set, or deleted on an object. They're the secret sauce behind many advanced features, enabling robust validation, lazy loading, and dynamic behavior. Let's wield this mighty tool and craft smarter, more controlled classes! ✨

```python
class ValidatedInt:
"""
A descriptor that ensures an attribute is an integer within a specified range.
"""
def __set_name__(self, owner, name):
# Called when the descriptor is assigned. It lets us know its own name!
self.public_name = name
self.private_name = f'_{name}' # Convention for storing the actual data

def __init__(self, min_val, max_val):
self.min_val = min_val
self.max_val = max_val

def __get__(self, obj, objtype=None):
if obj is None:
# Accessing the descriptor from the class itself (e.g., GameCharacter.level)
return self

# Retrieve the value from the instance's dictionary
value = getattr(obj, self.private_name, None)
if value is None:
raise AttributeError(f"'{self.public_name}' has not been set.")
return value

def __set__(self, obj, value):
# Validate the value before setting
if not isinstance(value, int):
raise ValueError(f"'{self.public_name}' must be an integer.")
if not (self.min_val <= value <= self.max_val):
raise ValueError(f"'{self.public_name}' must be between {self.min_val} and {self.max_val}.")

# Store the validated value in the instance's dictionary
setattr(obj, self.private_name, value)

def __delete__(self, obj):
# Prevent deletion of the attribute
raise AttributeError(f"'{self.public_name}' cannot be deleted.")

class GameCharacter:
level = ValidatedInt(1, 100) # Our level attribute now uses the descriptor!
health = ValidatedInt(0, 1000) # Same for health!

def __init__(self, level, health):
self.level = level # This calls __set__ on the descriptor
self.health = health # This calls __set__ on the descriptor

print("--- Creating characters ---")
hero = GameCharacter(10, 500)
print(f"Hero Level: {hero.level}, Health: {hero.health}")

villain = GameCharacter(99, 900)
print(f"Villain Level: {villain.level}, Health: {villain.health}")

print("n--- Testing validation ---")
try:
hero.level = 101 # Too high, will raise ValueError
except ValueError as e:
print(f"Error setting hero level: {e}")

try:
hero.health = "low" # Not an int, will raise ValueError
except ValueError as e:
print(f"Error setting hero health: {e}")

try:
del hero.level # Attempt to delete, will raise AttributeError
except AttributeError as e:
print(f"Error deleting hero level: {e}")

print(f"nHero (after attempts): Level: {hero.level}, Health: {hero.health}")
```

Output example:
```
--- Creating characters ---
Hero Level: 10, Health: 500
Villain Level: 99, Health: 900

--- Testing validation ---
Error setting hero level: 'level' must be between 1 and 100.
Error setting hero health: 'health' must be an integer.
Error deleting hero level: 'level' cannot be deleted.

Hero (after attempts): Level: 10, Health: 500
```

💡 **Challenge:** Modify the `ValidatedInt` descriptor to also accept an optional `default_value` in its `__init__`. If the attribute is accessed but hasn't been set yet (i.e., `getattr(obj, self.private_name, None)` returns `None`), it should return this `default_value` instead of raising an `AttributeError`.

---

Here's a quick quiz to test your descriptor knowledge! 👇
🚀 Day 42 is here, and we're tackling one of Python's most debated topics: the Global Interpreter Lock (GIL)! 🔐 Ever wondered why your multi-threaded CPU-bound Python code doesn't run faster? The GIL is the answer, restricting CPython to execute only one thread at a time.

Today, we deconstruct its impact, explore its origins, and search for true parallelism! Get ready for an ultimate question! 🤔

```python
import time
import threading
import multiprocessing
import os

# A CPU-bound task that performs calculations
def cpu_bound_task(n):
count = 0
for i in range(n):
count += i * i # Simple calculation to keep CPU busy
return count

# Function to run multiple CPU-bound tasks using threads
def run_with_threads(n_tasks, iterations):
start_time = time.perf_counter()
threads = []
print(f" Starting {n_tasks} threads in PID: {os.getpid()}")
for _ in range(n_tasks):
thread = threading.Thread(target=cpu_bound_task, args=(iterations,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
end_time = time.perf_counter()
print(f" Threads finished in: {end_time - start_time:.4f} seconds")

# Function to run multiple CPU-bound tasks using processes
def run_with_processes(n_tasks, iterations):
start_time = time.perf_counter()
processes = []
print(f" Starting {n_tasks} processes (each with its own GIL)")
for _ in range(n_tasks):
process = multiprocessing.Process(target=cpu_bound_task, args=(iterations,))
processes.append(process)
process.start()
for process in processes:
process.join()
end_time = time.perf_counter()
print(f" Processes finished in: {end_time - start_time:.4f} seconds")

if __name__ == "__main__":
NUM_TASKS = 4 # Number of threads/processes to create
ITERATIONS = 10_000_000 # Complexity for each CPU-bound task

print("--- Testing with Threads (affected by GIL) ---")
run_with_threads(NUM_TASKS, ITERATIONS)

print("n--- Testing with Processes (bypasses GIL) ---")
run_with_processes(NUM_TASKS, ITERATIONS)

print("nObservation: For CPU-bound tasks in CPython, multi-processing typically shows better performance, as each process gets its own interpreter and GIL, allowing true parallel execution.")
```

Output example:
```
--- Testing with Threads (affected by GIL) ---
Starting 4 threads in PID: 12345
Threads finished in: 3.1234 seconds

--- Testing with Processes (bypasses GIL) ---
Starting 4 processes (each with its own GIL)
Processes finished in: 0.8765 seconds

Observation: For CPU-bound tasks in CPython, multi-processing typically shows better performance, as each process gets its own interpreter and GIL, allowing true parallel execution.
```

Small challenge:
Challenge: Modify the `cpu_bound_task` to simulate an I/O-bound operation (e.g., replace `count += i * i` with `time.sleep(0.01)` inside the loop, or better, `time.sleep(0.1)` outside the loop once). How do the execution times for threads vs. processes change? Why? 👇 Let us know your insights!
Which of the following methods, when implemented in a class, makes an instance of that class a 'descriptor' in Python?
Anonymous Quiz
0%
A) Only `__init__`
0%
B) At least one of `__get__`, `__set__`, or `__delete__`
0%
C) Only `__call__`
0%
D) At least one of `__enter__` or `__exit__`
🚀 **Day 44: The Zen of Zero-Copy with `memoryview` and Protocols!** 🚀

Ever wondered how to handle large datasets in Python without constantly copying memory? Today, we unlock a powerful secret: `memoryview` and Python's Buffer Protocol! 💡 This advanced topic allows you to create "views" into an existing object's memory, enabling high-performance, low-overhead data manipulation. Forget unnecessary copying; manipulate data directly, efficiently, and with incredible speed, especially when dealing with bytes, bytearrays, or integrating with C extensions. Let's make your Python code truly fly! 🚀

```python
import array

# 1. Start with a mutable data source (e.g., bytearray)
original_data = bytearray(b'Hello Python Learners!')
print(f"Original data: {original_data}")

# 2. Create a memoryview of the data
mv = memoryview(original_data)
print(f"Memoryview as string (initial): {mv.tobytes().decode()}")

# 3. Modify the data through the memoryview - no copy is made!
# The changes directly reflect in the original_data.
mv[6:12] = b'WORLD!' # Change 'Python' to 'WORLD!'
mv[0] = ord('J') # Change 'H' to 'J'

print(f"Memoryview as string (modified): {mv.tobytes().decode()}")
print(f"Original data after memoryview modification: {original_data}")

# 4. Memoryviews can be sliced, and these slices are also views!
sub_mv = mv[6:12]
print(f"Sliced memoryview (sub_mv): {sub_mv.tobytes().decode()}")

# 5. Modifying the sub-view also modifies the original data
sub_mv[0] = ord('W') # Change 'W' to 'W' (no visible change, but demonstrates ability)
print(f"Original data after sub_mv modification: {original_data}")

# 6. Attempting to create a memoryview of an immutable object (like bytes)
# and then trying to modify it will raise an error.
immutable_bytes = b'Immutable'
mv_immutable = memoryview(immutable_bytes)
try:
# This line would raise an error if uncommented
# mv_immutable[0] = ord('X')
pass
except TypeError as e:
print(f"nAttempting to modify immutable bytes via memoryview leads to: {e}")
```

Output example:
```
Original data: bytearray(b'Hello Python Learners!')
Memoryview as string (initial): Hello Python Learners!
Memoryview as string (modified): Jello WORLD! Learners!
Original data after memoryview modification: bytearray(b'Jello WORLD! Learners!')
Sliced memoryview (sub_mv): WORLD!
Original data after sub_mv modification: bytearray(b'Jello WORLD! Learners!')

Attempting to modify immutable bytes via memoryview leads to: memoryview: underlying object is not writable
```

💡 **Challenge:**
Imagine you have a `bytearray` containing a large stream of sensor data, and you need to process only the segments where a specific flag is set. How would you use `memoryview` to efficiently iterate through these segments, creating views for each, without creating multiple copies of data? Share your approach! 👇

---
Alright, Pythonistas! 🐍 On Day 45, we're diving deep into the very soul of CPython: its bytecode! ✨ Ever wondered what truly happens when your Python code runs? We're peeling back the layers to explore the low-level instructions CPython executes. Understanding bytecode unlocks a new dimension of performance optimization, advanced introspection, and even dynamic code generation. Get ready to witness some true Python magic! 🔥

```python
import dis
import types

# A simple function to inspect
def greet_person(name):
# Line 2: Defines a formatted string
message = f"Hello, {name}!"
# Line 3: Prints the message
print(message)
# Line 4: Returns the length of the message
return len(message)

print("--- Disassembling greet_person ---")
dis.dis(greet_person)

print("n--- Raw Bytecode and Constants ---")
# Accessing the code object, which contains all function details
code_obj = greet_person.__code__

# Raw bytecode bytes (opcodes and their arguments, packed)
print(f"Raw Bytecode (hex): {code_obj.co_code.hex()}")

# Constants used by the function (e.g., strings, numbers)
print(f"Constants: {code_obj.co_consts}")

# Bytecode is a sequence of opcodes, each potentially followed by an argument.
# The 'dis' module makes sense of these raw bytes for us!
```

**Output example:**
```
--- Disassembling greet_person ---
2 0 RESUME 0
3 2 LOAD_CONST 1 ('Hello, ')
4 LOAD_FAST 0 (name)
6 FORMAT_VALUE 128 (f-string)
8 LOAD_CONST 2 ('!')
10 BUILD_STRING 3
12 STORE_FAST 1 (message)

4 14 LOAD_GLOBAL 0 (print)
16 LOAD_FAST 1 (message)
18 PRECALL 1
22 CALL 1
32 POP_TOP

5 34 LOAD_GLOBAL 1 (len)
36 LOAD_FAST 1 (message)
38 PRECALL 1
42 CALL 1
52 RETURN_VALUE

--- Raw Bytecode and Constants ---
Raw Bytecode (hex): 000002640100650080006402008303005a0100640000650100be0100b401000100640100650100be0100b401005300
Constants: (None, 'Hello, ', '!', <class 'len'>)
```

**Small challenge:**
Your turn! 🚀 Disassemble the following function using `dis.dis()`:

```python
def calculate_sum(numbers):
total = 0
for num in numbers:
total += num
return total
```
Identify the opcodes responsible for:
1. Initializing `total = 0`.
2. Iterating through `numbers`.
3. Adding `num` to `total`.
4. Returning the final `total`.

Share your insights below! 👇
❤1
🚀 Day 46: The Miniature API! ✨

Ever wondered how `@property` works, or how powerful libraries like ORMs manage database fields? Meet Python's Descriptors! These fundamental objects allow you to customize attribute access (get, set, delete) at a low level, giving you incredible control over how your class attributes behave.

With the magic of `__set_name__`, your descriptor instance even knows the name of the attribute it's managing within the owner class. This makes your descriptors self-aware, enabling more generic error messages, logging, or complex setup without needing manual configuration. Dive in to craft elegant, robust, and reusable attribute behavior! 🐍

```python
class ValidatedString:
"""
A descriptor that ensures an attribute is a non-empty string and
can enforce min/max length, using __set_name__ for self-awareness.
"""
def __init__(self, min_len=1, max_len=float('inf')):
self.min_len = min_len
self.max_len = max_len

def __set_name__(self, owner, name):
# 'owner' is the class the descriptor is attached to (e.g., User)
# 'name' is the attribute name it's assigned to (e.g., 'username')
self.public_name = name
self.private_name = f'_{name}' # Store the actual value in a private attribute

def __get__(self, obj, objtype=None):
if obj is None:
return self # Accessing the descriptor itself via the class
return getattr(obj, self.private_name, '') # Return the private value

def __set__(self, obj, value):
if not isinstance(value, str):
raise TypeError(f"'{self.public_name}' must be a string.")
if not (self.min_len <= len(value) <= self.max_len):
if self.max_len == float('inf'):
raise ValueError(
f"'{self.public_name}' must be at least {self.min_len} characters long."
)
else:
raise ValueError(
f"'{self.public_name}' must be between {self.min_len} and "
f"{self.max_len} characters long."
)
setattr(obj, self.private_name, value) # Set the private value

class User:
username = ValidatedString(min_len=3, max_len=20)
email = ValidatedString(min_len=5) # No max_len constraint here

def __init__(self, username, email):
self.username = username
self.email = email

# --- Demonstration ---
try:
user1 = User("Alice", "alice@example.com")
print(f"User 1: {user1.username}, {user1.email}")

user1.username = "BobTheBuilder" # Valid update
print(f"User 1 (updated username): {user1.username}")

# Attempt to set an invalid username (too short)
user1.username = "Bo"
except ValueError as e:
print(f"Error caught: {e}")

try:
# Attempt to create a user with invalid email (too short)
user2 = User("Charlie", "c@ex")
except ValueError as e:
print(f"Error caught: {e}")

try:
# Attempt to create a user with non-string username
user3 = User(123, "test@example.com")
except TypeError as e:
print(f"Error caught: {e}")
```

Output example:
```
User 1: Alice, alice@example.com
User 1 (updated username): BobTheBuilder
Error caught: 'username' must be at least 3 characters long.
Error caught: 'email' must be at least 5 characters long.
Error caught: 'username' must be a string.
```

💡 Your Challenge: Can you create a `PositiveNumber` descriptor that ensures an attribute (like `age` or `quantity`) is always an integer greater than zero? Or perhaps a `ReadOnly` descriptor that allows setting a value only once during initialization? Go ahead, build your own miniature API! 🛠️

---
🚀 Day 48: Level up your Python! ✨ Ever wondered how frameworks enforce patterns or create domain-specific languages? Today, we're diving into the heart of Python's type system with **Metaclasses and `__init_subclass__`**! 🤯

We'll explore how to programmatically control class creation itself, ensuring your classes adhere to specific rules *before* they even exist. `__init_subclass__` offers a powerful hook for class-level validation and modification, a perfect stepping stone into the world of metaprogramming. Get ready to master the art of class factories!

```python
class PluginBase:
"""
Base class for plugins, enforcing that all subclasses define
a 'PLUGIN_NAME' attribute and an 'execute' method.
This logic runs when the subclass itself is defined, not when an instance is created.
"""
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs) # Always call super()

# Enforce PLUGIN_NAME attribute
if not hasattr(cls, 'PLUGIN_NAME') or not isinstance(cls.PLUGIN_NAME, str):
raise TypeError(f"Subclass '{cls.__name__}' must define a string 'PLUGIN_NAME'.")

# Enforce 'execute' method
if not callable(getattr(cls, 'execute', None)):
raise TypeError(f"Subclass '{cls.__name__}' must define an 'execute' method.")

print(f"✅ Plugin '{cls.PLUGIN_NAME}' validated and ready!")

# --- Valid Plugin Definition ---
class MyAwesomePlugin(PluginBase):
PLUGIN_NAME = "AwesomePlugin"

def execute(self, data):
print(f"Executing {self.PLUGIN_NAME} with data: {data}")
return data.upper()

# --- Instantiation of a valid plugin ---
print("n--- Creating MyAwesomePlugin instance ---")
plugin_instance = MyAwesomePlugin()
result = plugin_instance.execute("hello metaprogramming!")
print(f"Result: {result}")

# --- Invalid Plugin Definition (will raise an error) ---
print("n--- Attempting to define BadPlugin (will fail) ---")
try:
class BadPlugin(PluginBase):
# Missing PLUGIN_NAME and execute method
pass
except TypeError as e:
print(f"Error caught as expected: {e}")

# --- Another Invalid Plugin (missing execute) ---
print("n--- Attempting to define AnotherBadPlugin (will fail) ---")
try:
class AnotherBadPlugin(PluginBase):
PLUGIN_NAME = "MissingExecute"
# Missing execute method
pass
except TypeError as e:
print(f"Error caught as expected: {e}")
```

Output example:
```
✅ Plugin 'AwesomePlugin' validated and ready!

--- Creating MyAwesomePlugin instance ---
Executing AwesomePlugin with data: hello metaprogramming!
Result: HELLO METAPROGRAMMING!

--- Attempting to define BadPlugin (will fail) ---
Error caught as expected: Subclass 'BadPlugin' must define a string 'PLUGIN_NAME'.

--- Attempting to define AnotherBadPlugin (will fail) ---
Error caught as expected: Subclass 'AnotherBadPlugin' must define an 'execute' method.
```

🧠 **Small Challenge:** Modify `PluginBase` to automatically register all valid subclasses in a global dictionary (e.g., `_REGISTERED_PLUGINS = {}`) upon their definition via `__init_subclass__`. This is a very common pattern for building extensible architectures!

---
Which special method is automatically called when a class is created as a subclass of another class, allowing for class-level validation or registration *before* the subclass is fully defined?
Anonymous Quiz
0%
__new__
33%
__init__
67%
__init_subclass__
0%
__call__
🚀 **Python Learning Series - Day 49: Disassembling the Zen!** 🤯

Ever wondered what truly happens beneath the elegant surface of your Python code? Today, we're not just peeking; we're diving deep into the CPython interpreter's brain! 🧠 Welcome to the world of runtime bytecode manipulation and dynamic code generation. This isn't just about writing Python; it's about understanding how Python itself *runs* and how you can bend its internal mechanics to your will for advanced optimization, instrumentation, or even building truly adaptive systems. Get ready to deconstruct and reconstruct the very essence of your functions! ✨

```python
import dis
import types

def original_add(a, b):
"""A simple function to add two numbers."""
return a + b

def original_subtract(a, b):
"""A simple function to subtract two numbers."""
return a - b

print("--- Original add function bytecode ---")
dis.dis(original_add)

print("n--- Original subtract function bytecode ---")
dis.dis(original_subtract)

print(f"nBefore manipulation: original_add(5, 3) = {original_add(5, 3)}")

# Deep dive: Runtime bytecode manipulation!
# We can dynamically change a function's underlying code object.
# Here, we're making 'original_add' execute 'original_subtract's logic!
original_add.__code__ = original_subtract.__code__

print(f"nAfter manipulation: original_add(5, 3) = {original_add(5, 3)}")

# Let's verify by disassembling 'original_add' again
print("n--- original_add function bytecode after manipulation ---")
dis.dis(original_add)
```

**Output example:**
```
--- Original add function bytecode ---
9 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 RETURN_VALUE

--- Original subtract function bytecode ---
13 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_SUBTRACT
6 RETURN_VALUE

Before manipulation: original_add(5, 3) = 8

After manipulation: original_add(5, 3) = 2

--- original_add function bytecode after manipulation ---
13 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_SUBTRACT
6 RETURN_VALUE
```

### 🎯 **Small Challenge:**

Can you define a third function, `original_multiply(a, b)`, and then dynamically change `original_add`'s behavior *again* so that it performs multiplication when called? Show the output before and after your modification! Good luck, bytecode wizard! 🧙‍♂️

---
🚀 Day 50: Dive into the heart of Python's object model! Today, we're not just writing code, we're *designing* its very behavior. Welcome to **The Architect's Loom: Weaving Runtime Behavior with Advanced Metaclasses and Descriptors**. 🤯 Discover how to craft custom attribute access, enforce validation, and build dynamic systems that adapt at runtime. Descriptors and metaclasses are your powerful tools to unlock Python's deepest metaprogramming capabilities. Get ready to reshape how your objects behave! 👇

```python
class PositiveNumber:
"""
A descriptor that ensures an attribute is a positive number and logs access.
"""
def __set_name__(self, owner, name):
# Store the name of the attribute it manages (e.g., '_price')
self._private_name = '_' + name

def __get__(self, obj, objtype=None):
if obj is None: # Accessing the descriptor directly from the class
return self
print(f"🔍 Accessing attribute '{self._private_name}'...")
return obj.__dict__.get(self._private_name, None)

def __set__(self, obj, value):
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError(f"🛑 Attribute '{self._private_name}' must be a positive number.")
print(f"✏️ Setting attribute '{self._private_name}' to {value}...")
obj.__dict__[self._private_name] = value

class Product:
# Assigning a descriptor instance to a class attribute
price = PositiveNumber()
quantity = PositiveNumber()

def __init__(self, name, price, quantity):
self.name = name
self.price = price # This assignment uses the 'price' descriptor
self.quantity = quantity # This assignment uses the 'quantity' descriptor

# --- Usage Example ---
print("--- Initializing Product ---")
p = Product("SuperWidget", 1200, 5)
print(f"nProduct: {p.name}")
print(f"Price: {p.price}") # Accessing uses the descriptor's __get__
print(f"Quantity: {p.quantity}") # Accessing uses the descriptor's __get__

print("n--- Testing Invalid Assignments ---")
try:
p.price = -100 # This will raise a ValueError
except ValueError as e:
print(e)

try:
p.quantity = "five" # This will raise a ValueError
except ValueError as e:
print(e)

print("n--- Testing Valid Assignment ---")
p.price = 1500 # This assignment uses the descriptor's __set__
print(f"New price: {p.price}")
```

Output example:
```
--- Initializing Product ---
✏️ Setting attribute '_price' to 1200...
✏️ Setting attribute '_quantity' to 5...

Product: SuperWidget
🔍 Accessing attribute '_price'...
Price: 1200
🔍 Accessing attribute '_quantity'...
Quantity: 5

--- Testing Invalid Assignments ---
🛑 Attribute '_price' must be a positive number.
🛑 Attribute '_quantity' must be a positive number.

--- Testing Valid Assignment ---
✏️ Setting attribute '_price' to 1500...
🔍 Accessing attribute '_price'...
New price: 1500
```

💡 **Your Challenge:** How would you modify the `PositiveNumber` descriptor to allow a default value of `1` if no value has been explicitly set for the attribute yet, without breaking the positive number constraint? Think about when `__get__` is called and what information it receives! Share your ideas! 👇

---