Metaclasses Introduction¶
A metaclass is a class whose instances are classes. Just as a class defines how instances behave, a metaclass defines how classes behave. Every class in Python is created by a metaclass — if you don't specify one, Python uses type as the default. You are always using a metaclass, even when you don't say so explicitly.
Mental Model
A metaclass is a class factory + behavior controller. It builds the class
object (via __new__), initializes it (via __init__), and controls how that
class creates its own instances (via __call__).
text
Metaclass → builds classes
Class → builds instances
ABC → constrains class design
A metaclass is not responsible for constructing instances — that is the class's job. The metaclass constructs the class itself.
Understanding the Concept¶
Everything is an Object¶
In Python, classes are objects too:
```python class MyClass: pass
MyClass is an object¶
print(type(MyClass)) #
Just like instances are objects¶
obj = MyClass()
print(type(obj)) #
The type() Function¶
type has two uses:
```python
1. Get the type of an object¶
print(type(42)) #
2. Create a new class dynamically¶
MyClass = type('MyClass', (), {'x': 10}) print(MyClass.x) # 10 ```
type is the Default Metaclass¶
```python class MyClass: pass
These are equivalent:¶
1. Using class statement¶
class MyClass: x = 10
2. Using type() directly¶
MyClass = type('MyClass', (), {'x': 10}) ```
The Class Creation Process¶
When Python sees a class definition:
python
class MyClass(BaseClass):
x = 10
def method(self):
pass
It executes roughly:
```python
1. Collect class body into a namespace dict¶
namespace = {'x': 10, 'method': method_function}
2. Determine metaclass (default: type)¶
metaclass = type
3. Call metaclass to create class¶
MyClass = metaclass('MyClass', (BaseClass,), namespace) ```
Creating a Metaclass¶
Basic Metaclass¶
```python class MyMeta(type): def new(mcs, name, bases, namespace): print(f"Creating class: {name}") # Create the class using type.new cls = super().new(mcs, name, bases, namespace) return cls
class MyClass(metaclass=MyMeta): pass
Output: Creating class: MyClass¶
```
new vs init in Metaclass¶
The key difference: __new__ can replace the class object entirely (by returning
a different object), while __init__ receives the already-created class and can only
modify it in place. Use __new__ when you need to intercept or transform the class
before it exists; use __init__ for post-creation setup.
```python class MyMeta(type): def new(mcs, name, bases, namespace): """Called to CREATE the class object. Can modify namespace or return a completely different class.""" print(f"new: Creating {name}") return super().new(mcs, name, bases, namespace)
def __init__(cls, name, bases, namespace):
"""Called to INITIALIZE the class object.
The class already exists — can only modify it, not replace it."""
print(f"__init__: Initializing {name}")
super().__init__(name, bases, namespace)
class MyClass(metaclass=MyMeta): pass
new: Creating MyClass¶
init: Initializing MyClass¶
```
call in Metaclass¶
Controls instance creation:
```python class SingletonMeta(type): _instances = {}
def __call__(cls, *args, **kwargs):
"""Called when MyClass() is invoked."""
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta): pass
a = Singleton() b = Singleton() print(a is b) # True ```
Singleton caveat: first initialization wins
After the first instance is created, all subsequent calls return that same instance — any new arguments are silently ignored. This can hide bugs:
python
db1 = Database("postgres") # Creates instance with postgres
db2 = Database("mysql") # Returns db1 — "mysql" is ignored!
print(db2.url) # "postgres"
Document this behavior clearly, or raise an error on re-initialization.
Practical Examples¶
Automatic Registration¶
```python class PluginMeta(type): plugins = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Don't register the base class itself
if bases: # Has parent classes
mcs.plugins[name] = cls
return cls
class Plugin(metaclass=PluginMeta): """Base class for plugins.""" pass
class AudioPlugin(Plugin): pass
class VideoPlugin(Plugin): pass
print(PluginMeta.plugins)
{'AudioPlugin': , 'VideoPlugin': }¶
```
Attribute Validation¶
```python class ValidatedMeta(type): def new(mcs, name, bases, namespace): # Check that required attributes exist if bases: # Not the base class if 'required_field' not in namespace: raise TypeError(f"{name} must define 'required_field'") return super().new(mcs, name, bases, namespace)
class Base(metaclass=ValidatedMeta): pass
class Valid(Base): required_field = "I exist"
class Invalid(Base): # TypeError: Invalid must define 'required_field'¶
pass¶
```
Method Wrapping¶
```python import functools
class LoggedMeta(type): def new(mcs, name, bases, namespace): # Wrap all methods with logging for key, value in namespace.items(): if callable(value) and not key.startswith('_'): namespace[key] = mcs.log_call(value) return super().new(mcs, name, bases, namespace)
@staticmethod
def log_call(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
class MyClass(metaclass=LoggedMeta): def method1(self): return "result1"
def method2(self, x):
return x * 2
obj = MyClass() obj.method1() # Calling method1 obj.method2(5) # Calling method2 ```
Interface Enforcement¶
```python class InterfaceMeta(type): required_methods = []
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# Skip check for base class
if bases and mcs.required_methods:
for method in mcs.required_methods:
if method not in namespace:
raise TypeError(
f"{name} must implement {method}()"
)
return cls
class ServiceMeta(InterfaceMeta): required_methods = ['start', 'stop', 'status']
class Service(metaclass=ServiceMeta): """Base service class.""" def start(self): pass def stop(self): pass def status(self): pass
class DatabaseService(Service): def start(self): print("DB starting")
def stop(self):
print("DB stopping")
def status(self):
return "running"
```
Metaclass vs Alternatives¶
When NOT to Use Metaclasses¶
Most use cases have simpler alternatives:
| Need | Alternative |
|---|---|
| Modify class creation | __init_subclass__ |
| Add methods | Class decorators |
| Validate attributes | Descriptors |
| Singleton pattern | Module-level instance |
| Registration | Class decorators |
init_subclass (Python 3.6+)¶
Simpler alternative for many cases:
```python class Plugin: plugins = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin.plugins[cls.__name__] = cls
class AudioPlugin(Plugin): pass
class VideoPlugin(Plugin): pass
print(Plugin.plugins)
{'AudioPlugin': , 'VideoPlugin': }¶
```
Class Decorators¶
```python def register(cls): registry[cls.name] = cls return cls
def add_method(cls): cls.new_method = lambda self: "added" return cls
@register @add_method class MyClass: pass ```
Class Creation Timeline¶
The full pipeline, showing where each hook fires and how __init_subclass__ fits in:
```text class MyClass(Base, metaclass=MyMeta): ...
Step 1: MyMeta.prepare('MyClass', (Base,)) → namespace dict Step 2: Execute class body in that namespace Step 3: MyMeta.new(mcs, 'MyClass', ...) → class object created Step 4: MyMeta.init(cls, 'MyClass', ...) → class object initialized Step 5: Base.init_subclass(MyClass) → parent's subclass hook ```
__init_subclass__ runs after the metaclass has finished creating the class.
This is why it cannot modify the namespace or replace the class — it only sees the
finished product.
Advanced: prepare¶
Control the namespace dict used during class creation:
```python from collections import OrderedDict
class OrderedMeta(type): @classmethod def prepare(mcs, name, bases): # Return custom namespace (must be dict-like) return OrderedDict()
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, dict(namespace))
cls._field_order = list(namespace.keys())
return cls
class Record(metaclass=OrderedMeta): first = 1 second = 2 third = 3
print(Record._field_order)
['module', 'qualname', 'first', 'second', 'third']¶
```
When to Use Metaclasses¶
Use metaclasses when you need to:
- Modify class creation before the class exists
- Automatically register all subclasses
- Enforce class-level invariants
- Create DSLs (Domain Specific Languages)
- Build frameworks (ORMs, serializers)
Famous examples:
- Django models (
ModelBase) - SQLAlchemy ORM (
DeclarativeMeta) - ABC module (
ABCMeta) - Enum (
EnumMeta)
Metaclass vs ABC (Common Confusion)¶
Metaclasses and abstract base classes (ABCs) both "control class behavior" but at different levels:
| Aspect | Metaclass | ABC |
|---|---|---|
| Level | Class creation | Class usage (instantiation) |
| When it runs | Class definition time | Instance creation time |
| Purpose | Modify/build the class object | Enforce an interface contract |
| Complexity | Very high | Moderate |
The relationship: ABC is implemented using a metaclass. Writing class Shape(ABC) is equivalent to class Shape(metaclass=ABCMeta). The metaclass is the underlying mechanism; ABC is a practical tool built on top of it.
Summary¶
```python
Class hierarchy¶
object # Base of all objects ↑ type # Metaclass - creates classes (type is its own metaclass) ↑ MyMeta # Custom metaclass ↓ MyClass # Regular class (instance of MyMeta) ↓ obj # Instance of MyClass ```
| Hook | Called When | Use For |
|---|---|---|
__new__ |
Creating class | Modify namespace, validate |
__init__ |
Initializing class | Post-creation setup |
__call__ |
Creating instance | Singleton, pooling |
__prepare__ |
Before body execution | Custom namespace |
Mental Compression
A metaclass is to a class what a class is to an instance:
| Relationship | Factory | Product |
|---|---|---|
| Class → Instance | MyClass() calls type.__call__ |
instance object |
| Metaclass → Class | class MyClass: calls MyMeta.__new__ + MyMeta.__init__ |
class object |
The three metaclass hooks map directly to this:
__new__— builds the class (like__new__builds an instance)__init__— configures the class after creation__call__— controls what happens when the class creates instances
Key Takeaways:
- Classes are instances of metaclasses
typeis the default metaclass for all classes- Metaclasses control class creation and behavior
- Use
__init_subclass__or decorators when possible - Reserve metaclasses for framework-level code
- "If you wonder whether you need metaclasses, you don't" — Tim Peters
The Unifying Insight
Python is built on just two fundamental operations:
- Attribute access:
obj.attr→ resolved via__getattribute__, descriptors, and MRO - Call:
obj(...)→ resolved via__call__
Everything else is built on these two:
obj.method()→ attribute lookup + callMyClass()→type.__call__(MyClass)→MyClass.__new__+MyClass.__init__class MyClass:→metaclass(name, bases, namespace)→ another callsuper().method()→ modified attribute lookup + call
Once you see this, the entire object model — classes, metaclasses, descriptors, MRO, super() — reduces to attribute access and calls, composed in different ways.
Runnable Example: class_factory_type.py¶
```python """ TUTORIAL: Class Factory Using type() - Creating Classes Dynamically ===================================================================
In this tutorial, you'll learn how to create classes dynamically using the type() builtin. Normally, type(obj) returns the type of an object, but type() has another mode: creating new classes!
Normal usage: type(obj) → returns the class
Dynamic creation: type(name, bases, dict) → creates a NEW class
The three arguments to type(): 1. name (str): The name of the new class 2. bases (tuple): Parent classes (can be empty for object) 3. dict (dict): Class attributes and methods
Why create classes dynamically? - Build flexible frameworks where classes are generated at runtime - Reduce boilerplate when creating many similar classes - Generate specialized classes based on data or configuration - Create classes from strings (parsing, serialization)
In this example: - record_factory() creates classes dynamically using type() - These classes are tuples with named fields and custom methods - They demonstrate the full power of runtime class creation
Key insight: Classes are objects too! You can create them, modify them, and pass them around just like any other Python object. """
from typing import Union, Any from collections.abc import Iterable, Iterator
============ Example 1: Understanding type() for Class Creation ============¶
if name == "main": print("=" * 70) print("EXAMPLE 1: Creating classes with type()") print("=" * 70)
# The type() builtin has two modes:
print(f"\nMode 1: type(obj) returns the class of obj")
print(f" type(42) = {type(42)}")
print(f" type('hello') = {type('hello')}")
print(f" type([1, 2, 3]) = {type([1, 2, 3])}")
print(f"\nMode 2: type(name, bases, dict) creates a NEW class")
print(f" MyClass = type('MyClass', (), {{}})")
print(f" Creates: a class named MyClass with no parents and no attributes")
# Create a simple class dynamically
MyClass = type('MyClass', (), {})
print(f"\nMyClass = {MyClass}")
print(f" Type: {type(MyClass)}")
print(f" Instance: {MyClass()}")
# ============ Example 2: Adding Attributes and Methods ============
print("\n" + "=" * 70)
print("EXAMPLE 2: Adding methods to dynamically created classes")
print("=" * 70)
def say_hello(self):
"""A method to add to the class."""
return f'Hello from {self.__class__.__name__}'
def __init__(self, name):
"""An initializer for the class."""
self.name = name
def __repr__(self):
"""String representation."""
return f'{self.__class__.__name__}(name={self.name!r})'
# Create a class with methods
Greeter = type('Greeter',
(),
{'__init__': __init__,
'say_hello': say_hello,
'__repr__': __repr__,
'class_var': 'I am a class variable'})
print(f"\nGreeter = type('Greeter', (), {{'__init__': ..., 'say_hello': ..., ...}})")
print(f"\nCreated class: {Greeter}")
print(f"Class variable: {Greeter.class_var}")
greeter = Greeter('Alice')
print(f"\ngreeter = Greeter('Alice')")
print(f" greeter.name = '{greeter.name}'")
print(f" repr(greeter) = {repr(greeter)}")
print(f" greeter.say_hello() = '{greeter.say_hello()}'")
# ============ Example 3: The record_factory() Function ============
print("\n" + "=" * 70)
print("EXAMPLE 3: Practical example - record_factory()")
print("=" * 70)
FieldNames = Union[str, Iterable[str]]
def record_factory(cls_name: str, field_names: FieldNames) -> type[tuple]:
"""Create a class for holding data, like a namedtuple.
This factory function dynamically creates classes that:
1. Store data efficiently using __slots__
2. Support unpacking (for x, y in records)
3. Have a nice __repr__ for debugging
4. Accept both positional and keyword arguments
Args:
cls_name: The name for the new class.
field_names: Field names as a string ('name age') or iterable.
Returns:
A new class that can be instantiated with the given fields.
"""
slots = parse_identifiers(field_names)
def __init__(self, *args, **kwargs) -> None:
"""Initialize instance, accepting positional and keyword args."""
attrs = dict(zip(self.__slots__, args))
attrs.update(kwargs)
for name, value in attrs.items():
setattr(self, name, value)
def __iter__(self) -> Iterator[Any]:
"""Allow unpacking of the instance (for x, y in instances)."""
for name in self.__slots__:
yield getattr(self, name)
def __repr__(self):
"""Return a nice representation like: Dog(name='Rex', weight=30)."""
values = ', '.join(f'{name}={value!r}'
for name, value in zip(self.__slots__, self))
cls_name_local = self.__class__.__name__
return f'{cls_name_local}({values})'
# Prepare the class attributes dictionary
cls_attrs = dict(
__slots__=slots,
__init__=__init__,
__iter__=__iter__,
__repr__=__repr__,
)
# Create and return the class using type()
return type(cls_name, (object,), cls_attrs)
def parse_identifiers(names: FieldNames) -> tuple[str, ...]:
"""Parse field names from string or iterable.
Args:
names: Either a string like 'name age weight' or a list/tuple.
Returns:
Tuple of validated identifier strings.
Raises:
ValueError: If any name is not a valid Python identifier.
"""
if isinstance(names, str):
names = names.replace(',', ' ').split()
if not all(s.isidentifier() for s in names):
raise ValueError('names must all be valid identifiers')
return tuple(names)
print(f"\nrecord_factory() creates data classes using type()")
print(f"\nSignature: record_factory(cls_name, field_names) -> class")
print(f"\nExample: Dog = record_factory('Dog', 'name weight owner')")
# ============ Example 4: Using the Created Classes ============
print("\n" + "=" * 70)
print("EXAMPLE 4: Creating and using classes from record_factory()")
print("=" * 70)
# Create a Dog class
Dog = record_factory('Dog', 'name weight owner')
print(f"\nDog = record_factory('Dog', 'name weight owner')")
print(f" Created: {Dog}")
print(f" Fields (__slots__): {Dog.__slots__}")
# Create instances
rex = Dog('Rex', 30, 'Bob')
buddy = Dog('Buddy', 25, 'Alice')
print(f"\nrex = Dog('Rex', 30, 'Bob')")
print(f" repr(rex) = {repr(rex)}")
print(f"\nbuddy = Dog('Buddy', 25, 'Alice')")
print(f" repr(buddy) = {repr(buddy)}")
print(f"\nAccessing attributes:")
print(f" rex.name = '{rex.name}'")
print(f" rex.weight = {rex.weight}")
print(f" rex.owner = '{rex.owner}'")
# ============ Example 5: Unpacking and Iteration ============
print("\n" + "=" * 70)
print("EXAMPLE 5: Unpacking and iteration support")
print("=" * 70)
print(f"\nUnpacking (like tuple unpacking):")
name, weight, owner = rex
print(f" name, weight, owner = rex")
print(f" name = '{name}', weight = {weight}, owner = '{owner}'")
print(f"\nIndexing (like tuple indexing):")
print(f" rex[0] = '{rex[0]}' (name)")
print(f" rex[1] = {rex[1]} (weight)")
print(f" rex[2] = '{rex[2]}' (owner)")
print(f"\nFormatting with unpacking:")
message = "{2}'s dog {0} weighs {1}kg"
print(f" message = \"{message}\"")
print(f" message.format(*rex) = '{message.format(*rex)}'")
print(f"\nIteration:")
print(f" for value in rex:")
for i, value in enumerate(rex):
print(f" [{i}] = {value!r}")
# ============ Example 6: Modifying Instance Attributes ============
print("\n" + "=" * 70)
print("EXAMPLE 6: Instances are mutable (can be modified)")
print("=" * 70)
print(f"\nOriginal: rex = {repr(rex)}")
rex.weight = 32
print(f" rex.weight = 32")
print(f" Modified: rex = {repr(rex)}")
print(f"\nThis is different from namedtuples (immutable)")
print(f" - record_factory classes are mutable")
print(f" - namedtuples are immutable")
print(f" - Both are efficient with __slots__")
# ============ Example 7: Creating with String or List ============
print("\n" + "=" * 70)
print("EXAMPLE 7: Flexible field name input")
print("=" * 70)
# Create with string
Person = record_factory('Person', 'name age city')
print(f"\nPerson = record_factory('Person', 'name age city')")
print(f" Fields: {Person.__slots__}")
alice = Person('Alice', 30, 'NYC')
print(f" alice = Person('Alice', 30, 'NYC')")
print(f" repr(alice) = {repr(alice)}")
# Create with list
Product = record_factory('Product', ['id', 'name', 'price'])
print(f"\nProduct = record_factory('Product', ['id', 'name', 'price'])")
print(f" Fields: {Product.__slots__}")
item = Product(1, 'Laptop', 999.99)
print(f" item = Product(1, 'Laptop', 999.99)")
print(f" repr(item) = {repr(item)}")
# Create with comma-separated string
Coordinate = record_factory('Coordinate', 'x,y,z')
print(f"\nCoordinate = record_factory('Coordinate', 'x,y,z')")
print(f" Fields: {Coordinate.__slots__}")
point = Coordinate(10, 20, 30)
print(f" point = Coordinate(10, 20, 30)")
print(f" repr(point) = {repr(point)}")
# ============ Example 8: Keyword Arguments ============
print("\n" + "=" * 70)
print("EXAMPLE 8: Creating instances with keyword arguments")
print("=" * 70)
# Using positional arguments
dog1 = Dog('Max', 28, 'Charlie')
print(f"\ndog1 = Dog('Max', 28, 'Charlie')")
print(f" {repr(dog1)}")
# Using keyword arguments
dog2 = Dog(name='Lucy', weight=24, owner='Diana')
print(f"\ndog2 = Dog(name='Lucy', weight=24, owner='Diana')")
print(f" {repr(dog2)}")
# Using mixed positional and keyword
dog3 = Dog('Buddy', owner='Eve', weight=26)
print(f"\ndog3 = Dog('Buddy', owner='Eve', weight=26)")
print(f" {repr(dog3)}")
# ============ Example 9: Why Use type() for Class Creation ============
print("\n" + "=" * 70)
print("EXAMPLE 9: Why dynamically create classes with type()")
print("=" * 70)
print(f"\nBenefits of type()-based class creation:")
print(f" 1. Reduce boilerplate - don't repeat similar classes")
print(f" 2. Generate from data - create classes from strings, configs")
print(f" 3. Framework design - base frameworks on runtime class generation")
print(f" 4. Meta-programming - inspect and modify classes at runtime")
print(f"\nUse cases:")
print(f" - ORM systems (database mapping): create model classes dynamically")
print(f" - Data validation: generate validators from schemas")
print(f" - API clients: generate resource classes from API specs")
print(f" - Configuration: create classes based on config files")
print(f"\nAlternatives to type():")
print(f" - dataclass: for data-holding classes with less boilerplate")
print(f" - namedtuple: for immutable tuple-like classes")
print(f" - type(): for maximum flexibility and runtime creation")
# ============ Example 10: Class Hierarchy ============
print("\n" + "=" * 70)
print("EXAMPLE 10: Classes created with type() in the hierarchy")
print("=" * 70)
print(f"\nDog.__mro__ (method resolution order):")
print(f" {Dog.__mro__}")
print(f"\nDog class details:")
print(f" Dog.__name__ = '{Dog.__name__}'")
print(f" Dog.__bases__ = {Dog.__bases__}")
print(f" Dog.__dict__ keys = {list(Dog.__dict__.keys())}")
print(f"\nInstance details:")
print(f" rex.__class__.__name__ = '{rex.__class__.__name__}'")
print(f" isinstance(rex, Dog) = {isinstance(rex, Dog)}")
print(f" type(rex) = {type(rex)}")
print(f" type(type(rex)) = {type(type(rex))}")
print(f"\nThe full chain:")
print(f" rex is an instance of Dog")
print(f" Dog is an instance of type (it's a class)")
print(f" type is an instance of type (it's a metaclass)")
# ============ Example 11: Summary - Dynamic Class Creation ============
print("\n" + "=" * 70)
print("EXAMPLE 11: Summary - When and How to Use type()")
print("=" * 70)
print(f"\nThe type() function has three jobs:")
print(f" 1. type(obj) - returns the type of an object")
print(f" 2. type(name, bases, dict) - creates a new class")
print(f" 3. type as a metaclass - controls class creation")
print(f"\nCreating classes with type():")
print(f" Syntax: NewClass = type(name, bases, attributes_dict)")
print(f" name: String name for the class")
print(f" bases: Tuple of parent classes (or () for object)")
print(f" attributes_dict: Dict of methods and class variables")
print(f"\nKey benefits:")
print(f" - Powerful for framework and library design")
print(f" - Can generate many similar classes from data")
print(f" - Allows runtime inspection and modification")
print(f"\nImportant notes:")
print(f" - Classes created with type() are normal classes")
print(f" - They work with inheritance, isinstance(), etc.")
print(f" - __slots__ makes them memory-efficient")
print(f" - Custom methods work just like normal classes")
print(f"\n" + "=" * 70)
```
Exercises¶
Exercise 1.
Use type() as a function to dynamically create a class Dog with a bark method and a species class attribute. Create an instance and call bark(). Show that the dynamically created class behaves identically to one defined with the class keyword.
Solution to Exercise 1
def bark(self):
return f"{self.name} says Woof!"
Dog = type('Dog', (), {
'species': 'Canis familiaris',
'bark': bark,
'__init__': lambda self, name: setattr(self, 'name', name),
})
d = Dog("Buddy")
print(d.bark()) # Buddy says Woof!
print(Dog.species) # Canis familiaris
print(type(d)) # <class 'Dog'>
Exercise 2.
Write a metaclass SingletonMeta where __call__ ensures only one instance of any class using this metaclass can exist. If an instance already exists, return it. Apply the metaclass to a Database class and show that multiple calls to Database() return the same object.
Solution to Exercise 2
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self, url="default"):
self.url = url
db1 = Database("postgres://localhost")
db2 = Database("mysql://localhost")
print(db1 is db2) # True — same instance
print(db1.url) # postgres://localhost (first call wins)
Exercise 3.
Create a metaclass AutoReprMeta that automatically adds a __repr__ method to any class that uses it. The auto-generated __repr__ should list all instance attributes. Apply it to a Point class with x and y attributes and show the auto-generated repr.
Solution to Exercise 3
class AutoReprMeta(type):
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if '__repr__' not in namespace:
def auto_repr(self):
attrs = ', '.join(f'{k}={v!r}' for k, v in vars(self).items())
return f"{name}({attrs})"
cls.__repr__ = auto_repr
return cls
class Point(metaclass=AutoReprMeta):
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(repr(p)) # Point(x=3, y=4)