Python Metaprogramming, Metaclasses & AST Rewriting — The Complete Master Notebook
Master advanced Python metaprogramming: the type-object ouroboros, complete metaclass lifecycle (__prepare__, __new__, __init__, __call__), writing a declarative ORM from scratch, and AST code transformation.
Python Metaprogramming, Metaclasses & AST Rewriting
1. The Python Metaclass Hierarchy: The Ouroboros#
In Python, everything is an object, and every object has a type. The type of a class is a metaclass. By default, all classes are instances of the built-in metaclass type.
mermaidgraph TD TypeObj["type (Metaclass)"] -->|instance of| TypeObj TypeObj -->|creates class| ClassObj["Custom Class (e.g., User)"] ClassObj -->|creates instance| InstObj["Instance (e.g., user_1)"]
🐍 PythonInteractive WebAssembly# The fundamental circular identity of CPython's type system:
print(isinstance(object, type)) # True (object is an instance of type)
print(isinstance(type, object)) # True (type is an instance of object)
print(type(type) is type) # True (type is its own metaclass!)
print(type(object) is type) # True (object's metaclass is type)
2. Dynamic Runtime Class Construction with type()#
The built-in type() function has two signatures:
type(instance)Returns the class of the instance.type(name: str, bases: tuple, namespace: dict)Dynamically constructs a brand new class in memory!
🐍 PythonInteractive WebAssembly# Dynamically create class 'DataSchema' inheriting from object
def validate_schema(self) -> bool:
return len(self.fields) > 0
DynamicDataSchema = type(
"DataSchema", # Class Name (__name__)
(object,), # Base Classes (__bases__)
{ # Class Dictionary (__dict__)
"version": "1.4.0",
"fields": ["id", "timestamp", "payload"],
"is_valid": validate_schema
}
)
schema_instance = DynamicDataSchema()
print(schema_instance.version) # "1.4.0"
print(schema_instance.is_valid()) # True
3. The Complete Metaclass Lifecycle#
A custom metaclass intercepts class creation at four distinct hook phases:
mermaidsequenceDiagram autonumber participant Parser as Class Definition Block participant Meta as Metaclass participant NS as __prepare__ Namespace participant Cls as Resulting Class Object Parser->>Meta: 1. Call __prepare__(mcs, name, bases) Meta-->>Parser: Returns custom namespace mapping (e.g. OrderedDict) Parser->>NS: 2. Execute class body & populate attributes Parser->>Meta: 3. Call __new__(mcs, name, bases, namespace) Meta->>Meta: Mutate/validate class attributes & allocate Class object Meta-->>Cls: Returns freshly allocated Class Parser->>Meta: 4. Call __init__(cls, name, bases, namespace)
🐍 PythonInteractive WebAssemblyclass LifecycleAuditMeta(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
print(f"1. __prepare__: Creating custom attribute namespace for class '{name}'")
return dict()
def __new__(mcs, name, bases, namespace, **kwargs):
print(f"2. __new__: Allocating class object '{name}' with {len(namespace)} attributes")
cls = super().__new__(mcs, name, bases, namespace)
return cls
def __init__(cls, name, bases, namespace, **kwargs):
print(f"3. __init__: Initializing class object '{name}'")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
print(f"4. __call__: Instantiating an instance of class '{cls.__name__}'")
instance = super().__call__(*args, **kwargs)
return instance
class MonitoredService(metaclass=LifecycleAuditMeta):
service_port = 8080
# Output at class definition time (modules import time):
# 1. __prepare__: Creating custom attribute namespace for class 'MonitoredService'
# 2. __new__: Allocating class object 'MonitoredService' with 4 attributes
# 3. __init__: Initializing class object 'MonitoredService'
# Output at instance instantiation time:
srv = MonitoredService()
# 4. __call__: Instantiating an instance of class 'MonitoredService'
4. Writing a Declarative Mini-ORM from Scratch#
This is how enterprise frameworks like Django ORM and SQLAlchemy declare schemas using descriptors and metaclasses:
🐍 PythonInteractive WebAssemblyfrom typing import Any, Dict
class Field:
"""Descriptor representing a typed database column."""
def __init__(self, data_type: type, primary_key: bool = False):
self.data_type = data_type
self.primary_key = primary_key
self.name = ""
def __set_name__(self, owner, name):
self.name = name
self.private_name = f"_{name}"
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.private_name, None)
def __set__(self, instance, value):
if value is not None and not isinstance(value, self.data_type):
raise TypeError(f"Field '{self.name}' must be of type {self.data_type.__name__}, got {type(value).__name__}")
setattr(instance, self.private_name, value)
class ModelMeta(type):
def __new__(mcs, name, bases, namespace):
fields: Dict[str, Field] = {}
# Collect and extract all Field descriptors declared in class
for attr_name, attr_val in list(namespace.items()):
if isinstance(attr_val, Field):
fields[attr_name] = attr_val
namespace["_fields"] = fields
namespace["_table_name"] = namespace.get("table_name", name.lower() + "s")
return super().__new__(mcs, name, bases, namespace)
class Model(metaclass=ModelMeta):
def __init__(self, **kwargs):
for field_name, field_obj in self._fields.items():
val = kwargs.get(field_name, None)
setattr(self, field_name, val)
def to_sql_insert(self) -> str:
columns = ", ".join(self._fields.keys())
values = ", ".join(repr(getattr(self, f)) for f in self._fields.keys())
return f"INSERT INTO {self._table_name} ({columns}) VALUES ({values});"
# Declarative Model Definition
class UserRecord(Model):
table_name = "users"
id = Field(int, primary_key=True)
username = Field(str)
email = Field(str)
user = UserRecord(id=101, username="alice", email="alice@corp.io")
print(user.to_sql_insert())
# INSERT INTO users (id, username, email) VALUES (101, 'alice', 'alice@corp.io');
5. Modern Alternative: __init_subclass__#
In modern Python (3.6+), __init_subclass__ eliminates the need for complex metaclasses for 90% of use cases (such as plugin registration, subclass validation, and configuration):
🐍 PythonInteractive WebAssemblyclass SerializablePlugin:
_plugin_registry = {}
def __init_subclass__(cls, plugin_id: str, auto_register: bool = True, **kwargs):
super().__init_subclass__(**kwargs)
# Enforce required abstract methods
if not hasattr(cls, "serialize") or not callable(getattr(cls, "serialize")):
raise TypeError(f"Plugin '{cls.__name__}' must implement a serialize() method.")
if auto_register:
cls.plugin_id = plugin_id
SerializablePlugin._plugin_registry[plugin_id] = cls
print(f"[PLUGIN SYSTEM] Registered: '{plugin_id}' -> {cls.__name__}")
class JSONSerializer(SerializablePlugin, plugin_id="json"):
def serialize(self, data: dict) -> str:
import json
return json.dumps(data)
print(SerializablePlugin._plugin_registry)
# {'json': <class '__main__.JSONSerializer'>}
6. Abstract Syntax Tree (AST) Introspection & Code Rewriting#
Python source code is parsed into an Abstract Syntax Tree (AST) before compilation into bytecode. The standard ast module allows you to inspect, analyze, and rewrite code before execution.
🐍 PythonInteractive WebAssemblyimport ast
import inspect
# 1. Parsing Python source into an AST
source_code = """
def calculate_tax(income, rate=0.2):
return income * rate
"""
tree = ast.parse(source_code)
print(ast.dump(tree, indent=2))
# 2. AST Visitor: Static Security Linter (Detects dangerous eval() calls)
class SecurityLinterVisitor(ast.NodeVisitor):
def visit_Call(self, node: ast.Call):
if isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
print(f" SECURITY ALERT: Dangerous call to '{node.func.id}()' detected at line {node.lineno}!")
self.generic_visit(node)
unsafe_code = """
def user_input_handler(query):
result = eval(query) # Unsafe!
return result
"""
unsafe_tree = ast.parse(unsafe_code)
linter = SecurityLinterVisitor()
linter.visit(unsafe_tree)
# SECURITY ALERT: Dangerous call to 'eval()' detected at line 3!
7. Metaprogramming Architecture Decision Matrix#
| Problem | Recommended Technique | Complexity |
|---|---|---|
| Validate/modify subclass attributes | __init_subclass__ | Low |
| Custom attribute access / type checking | Descriptors (__get__, __set__) | Medium |
| Wrap methods / add logging / caching | Function / Class Decorators | Low |
| Construct classes from dynamic JSON schemas | type("ClassName", (bases,), dict) | Medium |
| Intercept class creation / custom namespaces | Metaclass (class Meta(type):) | High |
| Static code analysis / bytecode manipulation | ast module / dis module | Very High |
Metaprogramming, Metaclasses & AST Checkpoint
Finished studying this notebook?
Mark this guide as completed to update your course progress roadmap.