Metaclasses: Form and Usage
If you want to know what metaclasses are, go look on the web. Quoting from somewhere,
Most Python programmers rarely, if ever, have to think about metaclasses
Except me. Except me. I have to think about them all the time. And I’m not a Python programmer. What I want to know is the options, the form for coding, and when when they run. Heve you ever wanted/needed to override a metaclass? I have.
Sample Code
So,
#!/usr/bin/env python3
import collections
# Note that if a class inherits a metaclass, any new metaclass must
# inherit the initial metaclass, or it is an error.
class Metaclass(type):
# This method and name,
# https://www.python.org/dev/peps/pep-3115/
# Main purpose of this, base of class as ordered dict,
# Classmethod because called before the class exists
@classmethod
def __prepare__(self, name, bases):
print("{}: __prepare__ in metaclass called".format(self.__name__))
print(" Parameters(cls, name: {}, bases: {})\n".format(self, name, bases))
return collections.OrderedDict()
def __new__(mcs, name, bases, attrs):
print("{}: __new__ in metaclass called".format(mcs.__name__))
print(" Parameters(mcs: {}, name: {}, bases: {}, attrs: {})\n".format(mcs, name, bases, attrs))
new_class = super().__new__(mcs, name, bases, attrs)
# may want to do work here, with the new class, like add or
# gather data from attributes
return new_class
# Does init get called?
def __init__(self, *args, **kwargs):
print("{}: __init___ called".format(self.__class__.__name__))
# This placement of metaclass is a later decision from
# https://www.python.org/dev/peps/pep-3115/
class Innocent(metaclass=Metaclass):
def __new__(cls, *args, **kwargs):
print('{}: __new__ called and returns class instance, finally from Python class "type"'.format(cls.__name__))
print(" Parameters(cls: {}, args: {}, kwargs: {})\n".format(cls, args, kwargs))
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print("{}: __init___ called".format(self.__class__.__name__))
# To avoid using metaclasses for this purpose, a hook,
# https://www.python.org/dev/peps/pep-0487/
def __init_subclass__(cls, **kwargs):
print("{}: __init_subclass__ called".format(cls.__name__))
print(" Parameters(cls: {}, kwargs: {})\n".format(cls, kwargs))
super().__init_subclass__()
# To avoid using metaclasses for this purpose, a hook,
# https://www.python.org/dev/peps/pep-0487/
def __set_name__(self, owner, name):
print("{}: __set_name__ called".format(self.__class__.__name__))
self.key = name
# Declaring a subclass, to illustrate various meta calls
class InnocenceLearned(Innocent):
pass
print("\nInnocent---instanciate")
print("----------------------")
Innocent()
print("\nInnocenceLearned---instanciate")
print("------------------------------")
InnocenceLearned()
Output,
Metaclass: __prepare__ in metaclass called
Parameters(cls, name: <class '__main__.Metaclass'>, bases: Innocent)
Metaclass: __new__ in metaclass called
Parameters(mcs: <class '__main__.Metaclass'>, name: Innocent, bases: (), attrs: OrderedDict([('__module__', '__main__'), ('__qualname__', 'Innocent'), ('__new__', <function Innocent.__new__ at 0x7f1745bf0e18>), ('__init__', <function Innocent.__init__ at 0x7f1745bf0ea0>), ('__init_subclass__', <function Innocent.__init_subclass__ at 0x7f1745bf0f28>), ('__set_name__', <function Innocent.__set_name__ at 0x7f1745c69048>), ('__classcell__', <cell at 0x7f1745c3afd8: empty>)]))
Metaclass: __init___ called
Metaclass: __prepare__ in metaclass called
Parameters(cls, name: <class '__main__.Metaclass'>, bases: InnocenceLearned)
Metaclass: __new__ in metaclass called
Parameters(mcs: <class '__main__.Metaclass'>, name: InnocenceLearned, bases: (<class '__main__.Innocent'>,), attrs: OrderedDict([('__module__', '__main__'), ('__qualname__', 'InnocenceLearned')]))
InnocenceLearned: __init_subclass__ called
Parameters(cls: <class '__main__.InnocenceLearned'>, kwargs: {})
Metaclass: __init___ called
Innocent---instanciate
----------------------
Innocent: __new__ called and returns class instance, finally from Python class "type"
Parameters(cls: <class '__main__.Innocent'>, args: (), kwargs: {})
Innocent: __init___ called
InnocenceLearned---instanciate
------------------------------
InnocenceLearned: __new__ called and returns class instance, finally from Python class "type"
Parameters(cls: <class '__main__.InnocenceLearned'>, args: (), kwargs: {})
InnocenceLearned: __init___ called
Notes
Order is,
Metaclass: __prepare__
Metaclass: __new__
Class: __init_subclass__
Class: __new__
Class: __init__
And usage,
Metaclass __prepare__ is stated as having several uses. However, it’s class reference is only itself, the metaclass, so it can only tinker with the base dictionary and bases. See the PEP.
Metaclass __new__ is a bit more useful, as it is called with all the methods and properties to be attached, as well as the bases.
Class __init_subclass__, used as it is here, for sure has uses, but runs after Metaclass __new__, so you will not be getting in beforehand with this one.
Class __new__ runs after MetaClass __init__ code, so is hampered if you are already dealing with metaclasses.
Overall, you face what I call the ‘I’m most important’ problem. Ask any part of a democratic and collaborative venture, and they will tell you they are the most important part. For sure, if you are already dealing with code where people thought it would be cool to use metaclasses, your chances of anticipating that code using an override are limited. Best you can do is try modify afterwards.
Anyway, it will do for me, and if it helps you, good.
References
Metaclasses in Python 3000, new‐style declaration and __prepare__,
Simpler customisation of class creation, __init_subclass__ and __set_name__,