Skip to content

Commit a52421d

Browse files
committed
docs: add detailed README for Section 24 - Metaprogramming
🔮 Learn how to manipulate classes and objects at the type level using Python's metaprogramming features: - 🧱 Understand the `__new__` method and its role in object creation - 📦 Use `type()` to dynamically create classes at runtime - 🧬 Define custom metaclasses to control class behavior - 🧩 Inject functionality across multiple classes using metaclass logic - 📐 Leverage `@dataclass` to auto-generate boilerplate methods (`__init__`, `__repr__`, etc.) - 🧪 Real-world example – Auto-registering plugins and audit logging via metaclass - 💡 Hidden notes: - `__new__` is useful for immutable types like str, int, tuple - Prefer `@dataclass` over manual `__init__` and `__repr__` - Metaclasses are applied at class definition time, not instance creation - Avoid overusing metaprogramming unless it simplifies complex patterns
1 parent 7bbecbc commit a52421d

File tree

1 file changed

+316
-0
lines changed

1 file changed

+316
-0
lines changed

Section_24_Meta_programming/README.md

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
# 🧪 Section 24: Metaprogramming
2+
## Understanding `__new__`, `type`, and Metaclasses in Python
3+
4+
🔮 **Learn how to write code that writes or manipulates other code at runtime** using metaprogramming techniques like:
5+
- `__new__` method
6+
- `type()` function
7+
- Custom metaclasses
8+
- Metaclass-based feature injection
9+
- `dataclass` for auto-generated methods
10+
11+
This section gives you a deep understanding of **how classes are created**, how to manipulate them dynamically, and when to use these advanced features effectively.
12+
13+
14+
15+
## 🧠 What You'll Learn
16+
17+
| Concept | Description |
18+
|--------|-------------|
19+
| `__new__()` | Control object creation before `__init__` |
20+
| `type()` | Built-in class factory — the default metaclass |
21+
| **Metaclasses** | Classes that create other classes |
22+
| **Metaclass Example** | Injecting functionality into multiple classes |
23+
| `@dataclass` | Automatically generate special methods like `__init__` and `__repr__` |
24+
| 💡 Hidden notes on best practices, pitfalls, and real-world uses |
25+
26+
27+
28+
## 🛠️ The `__new__` Method – Controlling Object Creation
29+
30+
The `__new__()` method is called **before `__init__()`** and is responsible for creating the instance.
31+
32+
🔹 **Example – Customizing Instance Creation**
33+
```python
34+
class Person:
35+
def __new__(self, name):
36+
print("Creating new instance")
37+
return super().__new__(self)
38+
39+
def __init__(self, name):
40+
self.name = name
41+
print("Initializing instance")
42+
43+
p = Person("Alice")
44+
```
45+
46+
🔸 **Output:**
47+
```
48+
Creating new instance
49+
Initializing instance
50+
```
51+
52+
🔹 **Use Case – Singleton Pattern**
53+
```python
54+
class Singleton:
55+
_instance = None
56+
57+
def __new__(cls):
58+
if cls._instance is None:
59+
cls._instance = super().__new__(cls)
60+
return cls._instance
61+
62+
s1 = Singleton()
63+
s2 = Singleton()
64+
print(s1 is s2) # True
65+
```
66+
67+
🔸 This ensures only one instance ever exists.
68+
69+
70+
71+
## 🧱 Using `type()` to Dynamically Create Classes
72+
73+
Python allows dynamic class creation using the built-in `type()` function.
74+
75+
🔹 **Basic Syntax:**
76+
```python
77+
type(name, bases, attrs)
78+
```
79+
80+
- `name`: Name of the class
81+
- `bases`: Tuple of base classes
82+
- `attrs`: Dictionary of attributes and methods
83+
84+
🔹 **Example – Dynamic Class Creation**
85+
```python
86+
def say_hello(self):
87+
print(f"Hello, I'm {self.name}")
88+
89+
Person = type('Person', (), {
90+
'name': None,
91+
'__init__': lambda self, name: setattr(self, 'name', name),
92+
'greet': say_hello
93+
})
94+
95+
p = Person("Alice")
96+
p.greet() # Hello, I'm Alice
97+
```
98+
99+
🔸 This is useful in frameworks, plugins, or DSL (domain-specific language) generation.
100+
101+
102+
103+
## 🔮 What is a Metaclass?
104+
105+
A **metaclass** is a class whose instances are **classes**. It defines how a class behaves — think of it as a **class for classes**.
106+
107+
🔹 **Default Metaclass:** `type`
108+
```python
109+
class MyClass:
110+
pass
111+
112+
print(type(MyClass)) # <class 'type'>
113+
```
114+
115+
🔸 All classes are instances of `type`.
116+
117+
118+
119+
## 🧬 Define Your Own Metaclass
120+
121+
Create a custom metaclass by inheriting from `type`.
122+
123+
🔹 **Example – Add a timestamp on class creation**
124+
```python
125+
import time
126+
127+
class TimestampMeta(type):
128+
def __new__(cls, name, bases, attrs):
129+
print(f"[{time.time()}] Creating class {name}")
130+
return super().__new__(cls, name, bases, attrs)
131+
132+
class MyNewClass(metaclass=TimestampMeta):
133+
pass
134+
```
135+
136+
🔸 Every time a class with this metaclass is defined, it logs a timestamp.
137+
138+
139+
140+
## 🧩 Metaclass Example – Auto-Inject Methods
141+
142+
Inject common functionality across many classes using a metaclass.
143+
144+
🔹 **Example – Add `log()` to all classes**
145+
```python
146+
class LoggerMeta(type):
147+
def __new__(cls, name, bases, attrs):
148+
attrs['log'] = lambda self, msg: print(f"[{self.__class__.__name__}] {msg}")
149+
return super().__new__(cls, name, bases, attrs)
150+
151+
class Animal(metaclass=LoggerMeta):
152+
def __init__(self, name):
153+
self.name = name
154+
155+
class Dog(Animal):
156+
pass
157+
158+
dog = Dog("Buddy")
159+
dog.log("Woof!") # [Dog] Woof!
160+
```
161+
162+
🔸 This pattern helps inject logging, validation, or serialization logic automatically.
163+
164+
165+
166+
## 📦 Use Case – Enforce Interface Requirements
167+
168+
Use metaclasses to ensure certain methods are implemented.
169+
170+
🔹 **Example – Require `save()` method**
171+
```python
172+
from abc import ABCMeta, abstractmethod
173+
174+
class ModelMeta(ABCMeta):
175+
def __new__(cls, name, bases, namespace):
176+
if 'save' not in namespace:
177+
raise TypeError("Must implement save()")
178+
return super().__new__(cls, name, bases, namespace)
179+
180+
class DatabaseModel(metaclass=ModelMeta):
181+
@abstractmethod
182+
def save(self):
183+
pass
184+
185+
class User(DatabaseModel):
186+
def save(self):
187+
print("Saving user to DB...")
188+
189+
u = User()
190+
u.save()
191+
```
192+
193+
🔸 If `save()` is missing, Python raises an error at class definition time.
194+
195+
196+
197+
## 🧰 Real-World Example – Auto-register Subclasses
198+
199+
Use a metaclass to automatically register subclasses — great for plugin systems.
200+
201+
🔹 **Example – Plugin Registry**
202+
```python
203+
class PluginMeta(type):
204+
registry = {}
205+
206+
def __new__(cls, name, bases, attrs):
207+
new_class = super().__new__(cls, name, bases, attrs)
208+
name = getattr(new_class, 'name', None)
209+
if name:
210+
cls.registry[name] = new_class
211+
return new_class
212+
213+
class Plugin(metaclass=PluginMeta):
214+
name = None
215+
216+
class EmailPlugin(Plugin):
217+
name = "email"
218+
219+
class SmsPlugin(Plugin):
220+
name = "sms"
221+
```
222+
223+
🔹 **Usage – Get class by name:**
224+
```python
225+
plugin_name = "email"
226+
plugin_class = PluginMeta.registry[plugin_name]
227+
plugin = plugin_class()
228+
```
229+
230+
231+
232+
## 📐 `@dataclass` – Reduce Boilerplate Code
233+
234+
Introduced in Python 3.7, `@dataclass` automatically generates `__init__`, `__repr__`, and more.
235+
236+
🔹 **Example – Simple data model**
237+
```python
238+
from dataclasses import dataclass
239+
240+
@dataclass
241+
class Product:
242+
name: str
243+
price: float
244+
in_stock: bool = True
245+
246+
product = Product("Laptop", 999.0)
247+
print(product) # Product(name='Laptop', price=999.0, in_stock=True)
248+
```
249+
250+
🔸 Other options include:
251+
- `@dataclass(order=True)` – enable comparisons
252+
- `@dataclass(frozen=True)` – make immutable objects
253+
- `@dataclass(kw_only=True)` – force keyword-only arguments
254+
255+
256+
257+
## 🧪 Advanced Example – Audit Log Metaclass
258+
259+
Build a metaclass that adds audit capabilities to any class.
260+
261+
```python
262+
import time
263+
264+
class AuditableMeta(type):
265+
def __new__(cls, name, bases, attrs):
266+
original_init = attrs.get('__init__', lambda self: None)
267+
268+
def wrapped_init(self, *args, **kwargs):
269+
print(f"[Audit] Initializing {name} at {time.time()}")
270+
original_init(self, *args, **kwargs)
271+
272+
attrs['__init__'] = wrapped_init
273+
return super().__new__(cls, name, bases, attrs)
274+
275+
class Order(metaclass=AuditableMeta):
276+
def __init__(self, order_id):
277+
self.order_id = order_id
278+
279+
order = Order(123)
280+
# Output: [Audit] Initializing Order at 1681005123.123
281+
```
282+
283+
284+
285+
## 💡 Hidden Tips & Notes
286+
287+
- 🧩 `__new__` is used less often than `__init__`, but it's essential for immutables like `str`, `int`, and `tuple`.
288+
- 🧱 `type()` is the default metaclass — don’t override unless necessary.
289+
- 🧾 Metaclasses affect class creation, not instance creation.
290+
- 📦 Use metaclasses for cross-cutting concerns like logging, validation, or registration.
291+
- 🚫 Don't overuse metaclasses — prefer composition and decorators where possible.
292+
- 🧵 Metaclasses can be combined with `classmethod` and `abstractmethod` for powerful design patterns.
293+
- 🧠 `@dataclass` reduces boilerplate for data models — use it instead of manually writing `__init__` and `__repr__`.
294+
295+
296+
297+
## 📌 Summary
298+
299+
| Feature | Purpose |
300+
|--------|---------|
301+
| `__new__()` | Control instance creation before initialization |
302+
| `type()` | Dynamically create classes at runtime |
303+
| **Metaclass** | Define behavior for class creation |
304+
| **Custom Metaclass** | Inject methods, enforce requirements, auto-register subclasses |
305+
| `@dataclass` | Auto-generate `__init__`, `__repr__`, etc. |
306+
| **Best Practice** | Prefer composition and avoid complex metaprogramming unless necessary |
307+
308+
309+
310+
🎉 Congratulations! You now understand how to **write and use metaprogramming features** in Python, including:
311+
- How to control object creation with `__new__`
312+
- How to dynamically create classes with `type()`
313+
- How to define and use custom metaclasses
314+
- When and how to use `@dataclass` to reduce boilerplate
315+
316+
Next up: 🧾 **Section 25: Exceptions in OOP** – learn how to handle exceptions inside classes and build your own exception hierarchy.

0 commit comments

Comments
 (0)