Metaclasses, denoted by Metaclasses, are a fascinating concept in object-oriented programming. They are essentially classes that create other classes. While all OOP languages have metaclasses (like Type in C# or Class in Java), the ability to define custom ones, as seen in Python and Groovy, is particularly interesting.
My previous post, so many years ago, delved into Groovy metaclasses. Unlike Groovy, custom metaclasses are less common in Python because features achievable through metaclasses in Groovy often have alternative implementations in Python (e.g., using __getattribute__, __getattr__). However, understanding metaclasses in Python is still valuable, particularly their role in callables and attribute lookup.
For an in-depth look at Python metaclasses, I recommend This. It enhanced my understanding of this complex topic. Let’s break down the key points:
Types:
- Classes and functions are themselves objects in Python.
- The base object
objectis an instance of thetypeclass. typeis uniquely an instance of itself.- All classes are instances of
type. - Consequently, metaclasses, being classes that create classes, are also instances of
typeand inherit from it. - To determine a class’s metaclass, use
type(A)whereArepresents your class.
Callables:
- When Python encounters
x(), it determines the class ofx. - It then calls the __call__ method of that class, passing
xas the first argument.
Instance Creation:
- Creating an instance like
a = A()invokes the __call__ method of the class’s metaclass. - For regular classes, this is
type.__call__. For classes with custom metaclasses, it’sCustomMetaclass.__call__. - The process involves
type.__call__(cls, *args, **kwargs), which callscls.__new__(cls)(defaulting toobject.__new__unless overridden) to create the instance, followed bycls.__init__(instance)to initialize it.
Class Creation:
- Defining a class like
class A:is similar toA = type("A", bases, attributes). - This invokes
type.__call__(type), leading to calls totype.__new__andtype.__init__to create the class object. (Note:type.__new__differs fromobject.__new__, see they are not).
Custom Metaclasses:
- Using
class A(metaclass=MetaB):creates an instance ofMetaB, callingMetaB.__new__andMetaB.__init__. - Creating an instance of
A(which usesMetaB) witha = A()invokesMetaB.__call__(which might be overridden or default totype.__call__).
Key Uses of Metaclasses:
- Managing class creation: Defining __new__ and/or __init__ in the metaclass controls the creation of classes (instances of the metaclass).
- Managing instance creation: Defining a custom __call__ in the metaclass controls the creation of instances of classes that utilize the metaclass (e.g., implementing singletons).
This article provides a good explanation of these concepts. Here’s a code snippet illustrating the signatures:
| |
Metaclasses and Attribute Lookup:
- Metaclasses don’t add complexity to attribute lookup.
- For
a.at1, Python searchesa’s __dict__, then its class (and base classes). The metaclass is not involved. - For
A.att1, Python searchesA’s __dict__, then its base classes, and finallytype.__dict__(sinceAis an instance oftype). The metaclass is referenced indirectly.
Metaclass Inheritance:
- Custom metaclasses are inherited from base classes.
- Multiple inheritance scenarios can be complex. superb article explains this well.
Metaclass Immutability:
- You can’t change a class’s metaclass after creation.
- However, there’s a workaround: create a new class inheriting from the original and assign the new metaclass.
Example:
| |
Use A2 to create new instances with the updated metaclass. Existing instances aren’t affected as their metaclass is determined upon instance creation.
This approach avoids modifying existing instances, which aligns with the intended behavior. my previous post demonstrates a similar technique.