06_metaclasses.py

Download
python 415 lines 10.7 KB
  1"""
  2Python Metaclasses
  3
  4Demonstrates:
  5- type() - the metaclass of all classes
  6- Custom metaclasses
  7- __init_subclass__ hook
  8- Class creation process
  9- Practical examples (registry, singleton, validation)
 10- ABCMeta
 11"""
 12
 13from typing import Any, Dict
 14from abc import ABCMeta, abstractmethod
 15
 16
 17def section(title: str) -> None:
 18    """Print a section header."""
 19    print("\n" + "=" * 60)
 20    print(f"  {title}")
 21    print("=" * 60)
 22
 23
 24# =============================================================================
 25# Understanding type()
 26# =============================================================================
 27
 28section("Understanding type()")
 29
 30# type() has two uses:
 31# 1. Return the type of an object
 32# 2. Create a new class dynamically
 33
 34print("type() as type checker:")
 35print(f"  type(42) = {type(42)}")
 36print(f"  type('hello') = {type('hello')}")
 37print(f"  type([]) = {type([])}")
 38
 39
 40# Create class with type()
 41print("\nCreating class with type():")
 42
 43
 44def init_method(self, value):
 45    self.value = value
 46
 47
 48def display_method(self):
 49    return f"MyClass(value={self.value})"
 50
 51
 52# type(name, bases, dict)
 53MyClass = type('MyClass', (object,), {
 54    '__init__': init_method,
 55    'display': display_method,
 56    'class_var': 42
 57})
 58
 59obj = MyClass(100)
 60print(f"  Created: {obj.display()}")
 61print(f"  Class var: {MyClass.class_var}")
 62print(f"  type(MyClass) = {type(MyClass)}")
 63
 64
 65# =============================================================================
 66# Basic Metaclass
 67# =============================================================================
 68
 69section("Basic Metaclass")
 70
 71
 72class SimpleMeta(type):
 73    """Simple metaclass that prints when class is created."""
 74
 75    def __new__(mcs, name, bases, namespace, **kwargs):
 76        print(f"  Creating class '{name}' with SimpleMeta")
 77        print(f"    Bases: {bases}")
 78        print(f"    Namespace keys: {list(namespace.keys())}")
 79        cls = super().__new__(mcs, name, bases, namespace)
 80        return cls
 81
 82
 83class MyClassWithMeta(metaclass=SimpleMeta):
 84    """Class using SimpleMeta."""
 85
 86    class_attr = "I'm a class attribute"
 87
 88    def instance_method(self):
 89        return "I'm an instance method"
 90
 91
 92print("\nInstantiating MyClassWithMeta:")
 93instance = MyClassWithMeta()
 94print(f"  Instance created: {instance}")
 95
 96
 97# =============================================================================
 98# Metaclass with __init__
 99# =============================================================================
100
101section("Metaclass __init__")
102
103
104class InitMeta(type):
105    """Metaclass with __init__ to modify class after creation."""
106
107    def __init__(cls, name, bases, namespace):
108        super().__init__(name, bases, namespace)
109        # Add attribute to class after creation
110        cls.metaclass_added = f"Added by InitMeta to {name}"
111        print(f"  InitMeta.__init__ called for {name}")
112
113
114class MyInitClass(metaclass=InitMeta):
115    """Class using InitMeta."""
116    pass
117
118
119print(f"MyInitClass.metaclass_added = {MyInitClass.metaclass_added}")
120
121
122# =============================================================================
123# Registry Pattern
124# =============================================================================
125
126section("Registry Pattern with Metaclass")
127
128
129class RegistryMeta(type):
130    """Metaclass that maintains a registry of all subclasses."""
131
132    registry: Dict[str, type] = {}
133
134    def __new__(mcs, name, bases, namespace):
135        cls = super().__new__(mcs, name, bases, namespace)
136
137        # Don't register the base class itself
138        if bases:
139            mcs.registry[name] = cls
140            print(f"  Registered: {name}")
141
142        return cls
143
144    @classmethod
145    def get_registry(mcs):
146        return mcs.registry.copy()
147
148
149class Plugin(metaclass=RegistryMeta):
150    """Base plugin class."""
151    pass
152
153
154class PluginA(Plugin):
155    """First plugin."""
156    def run(self):
157        return "PluginA running"
158
159
160class PluginB(Plugin):
161    """Second plugin."""
162    def run(self):
163        return "PluginB running"
164
165
166class PluginC(Plugin):
167    """Third plugin."""
168    def run(self):
169        return "PluginC running"
170
171
172print("\nRegistry contents:")
173for name, cls in RegistryMeta.get_registry().items():
174    print(f"  {name}: {cls}")
175
176print("\nInstantiating plugins from registry:")
177for name, cls in RegistryMeta.get_registry().items():
178    plugin = cls()
179    print(f"  {name}: {plugin.run()}")
180
181
182# =============================================================================
183# Singleton Pattern
184# =============================================================================
185
186section("Singleton Pattern with Metaclass")
187
188
189class SingletonMeta(type):
190    """Metaclass that implements singleton pattern."""
191
192    _instances: Dict[type, Any] = {}
193
194    def __call__(cls, *args, **kwargs):
195        if cls not in cls._instances:
196            print(f"  Creating new instance of {cls.__name__}")
197            instance = super().__call__(*args, **kwargs)
198            cls._instances[cls] = instance
199        else:
200            print(f"  Returning existing instance of {cls.__name__}")
201
202        return cls._instances[cls]
203
204
205class Database(metaclass=SingletonMeta):
206    """Singleton database connection."""
207
208    def __init__(self, connection_string: str):
209        self.connection_string = connection_string
210        print(f"  Database initialized with: {connection_string}")
211
212
213db1 = Database("postgresql://localhost/db1")
214db2 = Database("postgresql://localhost/db2")  # Same instance!
215
216print(f"\ndb1 is db2: {db1 is db2}")
217print(f"db1.connection_string: {db1.connection_string}")
218
219
220# =============================================================================
221# Attribute Validation
222# =============================================================================
223
224section("Attribute Validation with Metaclass")
225
226
227class ValidatedMeta(type):
228    """Metaclass that validates class attributes."""
229
230    def __new__(mcs, name, bases, namespace):
231        # Check that required_fields are present
232        if 'required_fields' in namespace:
233            required = namespace['required_fields']
234            for field in required:
235                if field not in namespace:
236                    raise TypeError(
237                        f"Class {name} missing required field: {field}"
238                    )
239
240        return super().__new__(mcs, name, bases, namespace)
241
242
243class ValidModel(metaclass=ValidatedMeta):
244    """Base model with validation."""
245    required_fields = ['name', 'version']
246
247    name = "ValidModel"
248    version = "1.0"
249
250
251print("ValidModel created successfully")
252print(f"  name: {ValidModel.name}")
253print(f"  version: {ValidModel.version}")
254
255
256# Try creating invalid model
257try:
258    class InvalidModel(metaclass=ValidatedMeta):
259        required_fields = ['name', 'version']
260        name = "InvalidModel"
261        # Missing 'version' field
262except TypeError as e:
263    print(f"\nInvalidModel creation failed: {e}")
264
265
266# =============================================================================
267# __init_subclass__ Hook (Alternative to Metaclass)
268# =============================================================================
269
270section("__init_subclass__ Hook")
271
272
273class PluginBase:
274    """Base class using __init_subclass__ instead of metaclass."""
275
276    plugins = {}
277
278    def __init_subclass__(cls, plugin_name: str = None, **kwargs):
279        super().__init_subclass__(**kwargs)
280        if plugin_name:
281            cls.plugins[plugin_name] = cls
282            print(f"  Registered plugin: {plugin_name} -> {cls.__name__}")
283
284
285class ImagePlugin(PluginBase, plugin_name="image"):
286    """Image processing plugin."""
287    def process(self):
288        return "Processing image"
289
290
291class VideoPlugin(PluginBase, plugin_name="video"):
292    """Video processing plugin."""
293    def process(self):
294        return "Processing video"
295
296
297print("\nPlugins registered via __init_subclass__:")
298for name, cls in PluginBase.plugins.items():
299    print(f"  {name}: {cls.__name__}")
300
301
302# =============================================================================
303# ABCMeta - Abstract Base Classes
304# =============================================================================
305
306section("ABCMeta - Abstract Base Classes")
307
308
309class Shape(metaclass=ABCMeta):
310    """Abstract shape class."""
311
312    @abstractmethod
313    def area(self) -> float:
314        """Calculate area."""
315        pass
316
317    @abstractmethod
318    def perimeter(self) -> float:
319        """Calculate perimeter."""
320        pass
321
322
323class Rectangle(Shape):
324    """Concrete rectangle implementation."""
325
326    def __init__(self, width: float, height: float):
327        self.width = width
328        self.height = height
329
330    def area(self) -> float:
331        return self.width * self.height
332
333    def perimeter(self) -> float:
334        return 2 * (self.width + self.height)
335
336
337rect = Rectangle(5, 3)
338print(f"Rectangle(5, 3):")
339print(f"  Area: {rect.area()}")
340print(f"  Perimeter: {rect.perimeter()}")
341
342
343# Try to instantiate abstract class
344try:
345    shape = Shape()
346except TypeError as e:
347    print(f"\nCannot instantiate abstract class:")
348    print(f"  {e}")
349
350
351# =============================================================================
352# Metaclass Inheritance
353# =============================================================================
354
355section("Metaclass Inheritance")
356
357
358class MetaA(type):
359    """First metaclass."""
360    def __new__(mcs, name, bases, namespace):
361        print(f"  MetaA processing {name}")
362        namespace['from_meta_a'] = True
363        return super().__new__(mcs, name, bases, namespace)
364
365
366class MetaB(MetaA):
367    """Metaclass inheriting from MetaA."""
368    def __new__(mcs, name, bases, namespace):
369        print(f"  MetaB processing {name}")
370        namespace['from_meta_b'] = True
371        return super().__new__(mcs, name, bases, namespace)
372
373
374class MyDerivedClass(metaclass=MetaB):
375    """Class using derived metaclass."""
376    pass
377
378
379print(f"\nMyDerivedClass.from_meta_a: {MyDerivedClass.from_meta_a}")
380print(f"MyDerivedClass.from_meta_b: {MyDerivedClass.from_meta_b}")
381
382
383# =============================================================================
384# Summary
385# =============================================================================
386
387section("Summary")
388
389print("""
390Metaclass patterns covered:
3911. type() - the metaclass of all classes
3922. Custom metaclasses - control class creation
3933. Registry pattern - auto-register subclasses
3944. Singleton pattern - single instance enforcement
3955. Validation - ensure class contracts
3966. __init_subclass__ - simpler alternative to metaclasses
3977. ABCMeta - abstract base classes
3988. Metaclass inheritance - compose metaclass behavior
399
400When to use metaclasses:
401- Framework/library development
402- Enforcing API contracts
403- Auto-registration patterns
404- Domain-specific languages
405- ORM implementations
406
407Alternatives to consider:
408- Class decorators (simpler)
409- __init_subclass__ (for registration)
410- Descriptors (for attribute control)
411
412"Metaclasses are deeper magic than 99% of users should ever
413worry about." - Tim Peters
414""")