Metaprogramming
Master Python's introspection tools, inspect module, dynamic code generation, and metaclasses
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What Is Metaprogramming?
Metaprogramming means writing code that treats functions, classes, and modules as data and manipulates them.
Python makes this easy because functions, classes, and modules are all objects that can be inspected and modified.
Introspection Basics: dir(), type(), vars()
Python provides built-in functions for basic introspection.
The inspect Module
The inspect module provides powerful tools for analyzing Python objects deeply.
Inspecting Function Signatures
Extract detailed information about function parameters, defaults, and type hints.
Detecting Types of Objects
Inspect can identify exactly what kind of object you're dealing with.
Getting the Call Stack
Inspect where your code is being called from - essential for debugging and logging.
Dynamic Function Generation
Create functions at runtime based on configuration or input.
Intercepting Attribute Access
Use __getattr__, __setattr__ to intercept and control attribute access.
Function Metadata & Annotations
Functions carry metadata that frameworks use for validation and documentation.
Inspecting Closures
Examine captured variables in closures - how decorators store state.
Building a Plugin System
Use introspection to automatically discover and register plugins.
Metaclasses — Advanced Class Creation
Metaclasses intercept class creation itself, enabling automatic registration and validation.
Descriptors — Custom Attribute Behavior
Descriptors control how attributes are accessed, enabling typed fields and validation.
Auto-Generating Documentation
Use introspection to automatically generate documentation from code.
Building a Dynamic API Router
Use introspection to auto-discover and route API endpoints.
Summary
You've mastered Python's metaprogramming and introspection capabilities:
These techniques power major frameworks like Django, FastAPI, SQLAlchemy, pytest, and Pydantic. Metaprogramming enables you to build flexible, self-documenting systems that adapt to code structure automatically.
📋 Quick Reference — Metaprogramming
You can now write code that introspects and modifies other code at runtime — the power behind pytest, FastAPI, Django ORM, and Pydantic.
Up next: Decorator Libraries — build reusable decorator toolkits for production code.
Practice quiz
What does the built-in vars(obj) typically return?
- A list of method names
- The object's class
- The object's __dict__ of instance attributes
- Only the public attributes as strings
Answer: The object's __dict__ of instance attributes. vars(obj) returns the object's __dict__, the mapping of its instance attributes.
Which inspect function returns a function's parameters and return annotation?
- inspect.signature()
- inspect.getsource()
- inspect.getmembers()
- inspect.stack()
Answer: inspect.signature(). inspect.signature(fn) gives a Signature object exposing parameters and the return annotation.
What does setattr(obj, 'email', 'a@b.com') do?
- Reads the email attribute
- Deletes the email attribute
- Checks whether email exists
- Sets obj.email to 'a@b.com' using a name string
Answer: Sets obj.email to 'a@b.com' using a name string. setattr(obj, name, value) assigns an attribute whose name is given as a string.
Which special method intercepts access to attributes that are NOT found normally?
- __init__
- __getattr__
- __call__
- __new__
Answer: __getattr__. __getattr__ is invoked only when normal attribute lookup fails, ideal for lazy loading.
What is the key role of a metaclass like 'class Meta(type):'?
- It intercepts and customizes class creation itself
- It runs only when an instance method is called
- It replaces the need for __init__
- It deletes classes automatically
Answer: It intercepts and customizes class creation itself. A metaclass controls how classes are built, enabling auto-registration and validation at class-creation time.
Inside a descriptor, when is __set_name__(self, owner, name) called?
- Every time the attribute is read
- Only when the instance is deleted
- When the descriptor is assigned as a class attribute (at class creation)
- Never; it is optional and unused
Answer: When the descriptor is assigned as a class attribute (at class creation). __set_name__ fires as the owning class is created, letting the descriptor learn its attribute name.
Which inspect check is True for a function defined with 'def f(): yield 1'?
- inspect.isclass(f)
- inspect.isgeneratorfunction(f)
- inspect.iscoroutinefunction(f)
- inspect.ismethod(f)
Answer: inspect.isgeneratorfunction(f). A function containing yield is a generator function, so inspect.isgeneratorfunction returns True.
Where does a closure store the variables it captured from its enclosing scope?
- In func.__dict__
- In a global registry
- In func.__annotations__
- In func.__closure__ cells
Answer: In func.__closure__ cells. Captured free variables live in cell objects accessible via the function's __closure__ attribute.
What does a function's __annotations__ attribute contain?
- Its source code as a string
- A dict mapping parameter/return names to their type hints
- The list of decorators applied
- Its docstring
Answer: A dict mapping parameter/return names to their type hints. __annotations__ maps each annotated parameter (and 'return') to its declared type hint.
Why must TrackedObject use object.__setattr__ inside its own __setattr__?
- To make attributes read-only
- To skip type checking
- To avoid infinite recursion when storing the attribute
- To register the class in a metaclass
Answer: To avoid infinite recursion when storing the attribute. Calling self.attr = value inside __setattr__ would re-trigger __setattr__; object.__setattr__ stores it directly.