8.6. Metaprogramming Metaclass
Object is an instance of a class
Class is an instance of a Metaclass
Class defines how an object behaves
Metaclass defines how a class behaves
Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't. The people who actually need them know with certainty that they need them, and don't need an explanation about why.
—Tim Peters

Figure 8.2. Object is an instance of a Class. Class is an instance of a Metaclass. Metaclass is an instance of a type. Type is an instance of a type.
When a class definition is executed, the following steps occur:
MRO entries are resolved;
the appropriate metaclass is determined;
the class namespace is prepared;
the class body is executed;
the class object is created.
When using the default metaclass type, or any metaclass that ultimately
calls type.__new__
, the following additional customisation steps are
invoked after creating the class object:
type.__new__
collects all of the descriptors in the class namespace that define a__set_name__()
method;all of these
__set_name__
methods are called with the class being defined and the assigned name of that particular descriptor;the
__init_subclass__()
hook is called on the immediate parent of the new class in its method resolution order. [2]
8.6.1. Class Based Metaclass
Typically Metaclasses are defined as a class
Metaclass is a class which returns a class from it's constructor
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
return type(clsname, bases, attrs)
Then we can use it, instead of a regular type()
:
User = MyType('User', (), {})
8.6.2. Using Metaclasses
Metaclasses are used by setting the
metaclass
keyword argumentThis works for both function and class based metaclasses
For future, we will use the class based metaclasses and
metaclass=...
syntaxIt is exactly equivalent to other ones, but it is more common in practice
Class based metaclass:
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
return type(clsname, bases, attrs)
class User(metaclass=MyType):
pass
8.6.3. Rationale
Metaclasses allow you to do 'extra things' when creating a class
Allow customization of class instantiation
Most commonly used as a class-factory
Registering the new class with some registry
Replace the class with something else entirely
Inject logger instance
Injecting static fields
Ensure subclass implementation
Metaclasses run when Python defines class (even if no instance is created)
The potential uses for metaclasses are boundless. Some ideas that have been explored include enum, logging, interface checking, automatic delegation, automatic property creation, proxies, frameworks, and automatic resource locking/synchronization. [2]
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
print('before')
cls = super().__new__(metacls, clsname, bases, attrs)
print('after')
return cls
class User(metaclass=MyType):
pass
before
after
Metaclasses allow you to do 'extra things' when creating a class. You can do something before, or after class creation. You can modify a class, add new fields or methods, ensure name is correct, ensure it inherits from some other class, register it in some registry such as event listener, inject named logger instance, or even replace it with something else entirely.
8.6.4. Example - 1
Ensure class name starts with uppercase letter
I have chosen
NameError
exception, but you can choose any other
import string
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
lowercase_letters = tuple(string.ascii_lowercase)
if clsname.startswith(lowercase_letters):
raise NameError('Class name must start with uppercase letter')
return super().__new__(metacls, clsname, bases, attrs)
This code will work without any problems:
class User(metaclass=MyType):
pass
But if we change a name of a class to starts with lowercase u
,
then we will receive an exception:
class user(metaclass=MyType):
pass
Traceback (most recent call last):
NameError: Class name must start with uppercase letter
8.6.5. Example - 2
Ensure class inherits from some other class
I have chosen
TypeError
exception, but you can choose any other
class Account:
pass
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
if Account not in bases:
raise TypeError('All classes must inherit from Account')
return super().__new__(metacls, clsname, bases, attrs)
This code will work without any problems:
class User(Account, metaclass=MyType):
pass
If we forget to inherit from Account
class, then we will
receive an exception:
class User(metaclass=MyType):
pass
Traceback (most recent call last):
TypeError: All classes must inherit from Account
8.6.6. Example - 3
Ensure
__init__
method is always present in a classI have chosen
TypeError
exception, but you can choose any other
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
if '__init__' not in attrs:
raise TypeError('All classes must have __init__ method')
return super().__new__(metacls, clsname, bases, attrs)
This code will work without any problems:
class User(metaclass=MyType):
def __init__(self):
pass
If we forget to add __init__
method, then we will
receive an exception:
class User(metaclass=MyType):
pass
Traceback (most recent call last):
TypeError: All classes must have __init__ method
8.6.7. Hiding Metaclass
Metaclasses can be hidden by using inheritance
This is a common pattern in Python frameworks
class MyType(type):
def __new__(metacls, name, bases, attrs):
uppercase = tuple(string.ascii_uppercase)
if not name.startswith(uppercase):
raise ValueError(f'Class name must start with uppercase letter')
cls = super().__new__(metacls, name, bases, attrs)
return cls
class Account(metaclass=MyType):
pass
Usage:
class User(Account):
pass
class user(Account):
pass
Traceback (most recent call last):
ValueError: Class name must start with uppercase letter
8.6.8. Metaclass Arguments
Metaclasses can accept additional arguments
Arguments can be passed to metaclass using
metaclass=...
syntaxThis behavior allows for more customization
from pprint import pprint
import logging
from datetime import datetime, timezone
from uuid import uuid4
class MyType(type):
def __new__(metacls, clsname, bases, attrs, debug=False):
cls = super().__new__(metacls, clsname, bases, attrs)
if debug:
cls._since = datetime.now(timezone.utc)
cls._uuid = uuid4()
cls._log = logging.getLogger(cls.__name__)
return cls
Creating an object without additional arguments, will create a normal class with typical attributes:
class User(metaclass=MyType):
pass
pprint(vars(User))
mappingproxy({'__dict__': <attribute '__dict__' of 'User' objects>,
'__doc__': None,
'__firstlineno__': 1,
'__module__': '__main__',
'__static_attributes__': (),
'__weakref__': <attribute '__weakref__' of 'User' objects>})
Setting debug
to True
, will create a class with additional
attributes, that is _since
, _uuid
, and _log
, which can
enable better debugging:
class User(metaclass=MyType, debug=True):
pass
pprint(vars(User))
mappingproxy({'__dict__': <attribute '__dict__' of 'User' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'User' objects>,
'_log': <Logger User (INFO)>,
'_since': datetime.datetime(2024, 8, 21, 10, 11, 16, 244761, tzinfo=datetime.timezone.utc),
'_uuid': UUID('8eb20432-38dc-4a3b-b798-079285fabde0')})
8.6.9. Metaclass Protocol
__prepare__(metacls, name, bases, **kwargs) -> dict
- on class namespace initialization__new__(metacls, classname, bases, attrs) -> cls
- before class creation__init__(self, name, bases, attrs) -> None
- after class creation__call__(self, *args, **kwargs)
- when the class is called (instantiated)
class MyType(type):
@classmethod
def __prepare__(metacls, name, bases) -> 'mappingproxy': ...
def __new__(metacls, classname, bases, attrs, *args, **kwargs) -> type: ...
def __init__(self, *args, **kwargs) -> None: ...
def __call__(self, *args, **kwargs) -> object: ...
Once the appropriate metaclass has been identified, then the class
namespace is prepared. If the metaclass has a __prepare__
attribute,
it is called as namespace = metaclass.__prepare__(name, bases, **kwds)
(where the additional keyword arguments, if any, come from the class
definition). The __prepare__
method should be implemented as a
classmethod()
. The namespace returned by __prepare__
is passed in
to __new__
, but when the final class object is created the namespace
is copied into a new dict
. If the metaclass has no __prepare__
attribute, then the class namespace is initialised as an empty ordered
mapping. [1]
class MyType(type):
@classmethod
def __prepare__(metacls, name, bases):
print('prepare: before')
result = super().__prepare__(metacls, name, bases)
print('prepare: after')
return result
def __new__(metacls, clsname, bases, attrs):
print('new: before')
result = super().__new__(metacls, clsname, bases, attrs)
print('new: after')
return result
def __init__(self, *args, **kwargs):
print('init: before')
super().__init__(*args, **kwargs)
print('init: after')
def __call__(self, *args, **kwargs):
print('call: before')
result = super().__call__(*args, **kwargs)
print('call: after')
return result
class User(metaclass=MyType):
pass
prepare: before
prepare: after
new: before
new: after
init: before
init: after
mark = User()
call: before
call: after
8.6.10. Case Study
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
if Account not in bases:
raise TypeError('All classes must inherit from Account')
return super().__new__(metacls, clsname, bases, attrs)
class Account:
pass
class User(Account, metaclass=MyType):
pass
class Admin(metaclass=MyType):
pass
# Traceback (most recent call last):
# TypeError: All classes must inherit from Account
from string import ascii_uppercase
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
uppercase = tuple(ascii_uppercase)
if not clsname.startswith(uppercase):
raise NameError('All class names must starts with uppercase letter')
return super().__new__(metacls, clsname, bases, attrs)
class User(metaclass=MyType):
pass
class admin(metaclass=MyType):
pass
# Traceback (most recent call last):
# NameError: All class names must starts with uppercase letter
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
if ('AGE_MIN' not in attrs) or ('AGE_MAX' not in attrs):
raise TypeError('All classes must have AGE_MIN and AGE_MAX defined')
return super().__new__(metacls, clsname, bases, attrs)
class User(metaclass=MyType):
AGE_MIN = 18
AGE_MAX = 65
class Admin(metaclass=MyType):
pass
# Traceback (most recent call last):
# TypeError: All classes must have AGE_MIN and AGE_MAX defined
import logging
from datetime import timezone, datetime
from uuid import uuid4
class MyType(type):
def __new__(metacls, clsname, bases, attrs, trace=False):
cls = super().__new__(metacls, clsname, bases, attrs)
if trace:
cls._uuid4 = uuid4()
cls._since = datetime.now(timezone.utc)
cls._log = logging.getLogger(clsname)
return cls
class User(metaclass=MyType):
pass
class Admin(metaclass=MyType, trace=True):
pass
pprint(vars(User))
# mappingproxy({'__dict__': <attribute '__dict__' of 'User' objects>,
# '__doc__': None,
# '__module__': '__main__',
# '__weakref__': <attribute '__weakref__' of 'User' objects>})
pprint(vars(Admin))
# mappingproxy({'__dict__': <attribute '__dict__' of 'Admin' objects>,
# '__doc__': None,
# '__module__': '__main__',
# '__weakref__': <attribute '__weakref__' of 'Admin' objects>,
# '_log': <Logger Admin (INFO)>,
# '_since': datetime.datetime(2024, 8, 7, 8, 15, 58, 999871, tzinfo=datetime.timezone.utc),
# '_uuid4': UUID('9b7bedd0-51d8-44bc-93bb-85fc298299d1')})
from datetime import datetime, timezone
from uuid import uuid4
class Register(type):
listeners = {}
@classmethod
def on_create(cls, *names):
def wrapper(func):
for name in names:
if name not in cls.listeners:
cls.listeners[name] = []
cls.listeners[name] += [func]
return wrapper
def __new__(cls, name, bases, attrs):
for listener in cls.listeners.get(name, []):
name, bases, attrs = listener(name, bases, attrs)
return super().__new__(cls, name, bases, attrs)
@Register.on_create('User', 'Admin')
def debug(name, bases, attrs):
print(f'DEBUG: {name=}')
print(f'DEBUG: {bases=}')
print(f'DEBUG: {attrs=}')
return name, bases, attrs
@Register.on_create('User', 'Admin')
def debug(name, bases, attrs):
print(f'INFO: {name=}')
return name, bases, attrs
@Register.on_create('User')
def trace(name, bases, attrs):
attrs['_uuid4'] = uuid4()
attrs['_since'] = datetime.now(timezone.utc)
return name, bases, attrs
class Account(metaclass=Register):
pass
class User(Account):
pass
# DEBUG: name='User'
# DEBUG: bases=(<class '__main__.Account'>,)
# DEBUG: attrs={'__module__': '__main__', '__qualname__': 'User'}
# INFO: name='User'
class Admin(Account):
pass
# DEBUG: name='Admin'
# DEBUG: bases=(<class '__main__.Account'>,)
# DEBUG: attrs={'__module__': '__main__', '__qualname__': 'Admin'}
# INFO: name='Admin'
pprint(vars(User))
# mappingproxy({'__doc__': None,
# '__module__': '__main__',
# '_since': datetime.datetime(2024, 8, 21, 10, 1, 58, 442815, tzinfo=datetime.timezone.utc),
# '_uuid4': UUID('dd17cdb6-6aec-4afb-9434-54f4297715af')})
pprint(vars(Admin))
# mappingproxy({'__module__': '__main__', '__doc__': None})
# listeners = {
# 'User': [debug, trace],
# 'Admin': [debug],
# }
8.6.11. Use Case - 1
Injecting a named logger instance
import logging
class Logger(type):
def __init__(cls, *args, **kwargs):
cls._logger = logging.getLogger(cls.__name__)
class User(metaclass=Logger):
pass
class Admin(metaclass=Logger):
pass
print(User._logger)
<Logger User (WARNING)>
print(Admin._logger)
<Logger Admin (WARNING)>
8.6.12. Instance vs. Class vs. Metaclass
MRO - Method Resolution Order
MRO is a list of classes that Python will use to search for methods
MRO is defined by C3 Linearization algorithm
MRO is defined by
__mro__
attribute
Let's consider a normal (without a metaclass) class:
class User:
pass
Object is an instance of a class, and an object:
mark = User()
isinstance(mark, User)
True
isinstance(mark, object)
True
This is because User
class is an instance of a type
class:
User.__mro__
(<class '__main__.User'>, <class 'object'>)
Now, consider a case with using metaclasses:
class MyType(type):
pass
class User(metaclass=MyType):
pass
Instances for User
class are still instances of a User
class:
mark = User()
isinstance(mark, User)
True
isinstance(mark, object)
True
But, they are not an instances of a metaclass:
isinstance(mark, MyType)
False
isinstance(User, MyType)
True
And a class still inherits from object:
User.__mro__
(<class '__main__.User'>, <class 'object'>)
The following diagram displays the relationship between an instance, class and a metaclass:

Figure 8.3. Object is an instance of a Class. Class is an instance of a Metaclass. Metaclass is an instance of a type. Type is an instance of a type.
8.6.13. Class Bases
Bases for an instance stays the same
Bases of a class changes when using a metaclass
class User:
pass
User.__class__.__bases__
(<class 'object'>,)
mark = User()
mark.__class__.__bases__
(<class 'object'>,)
Bases for an instance stays the same, but note, how bases of a class changes when using a metaclass:
class MyType(type):
def __new__(metacls, classname, bases, attrs):
return type(classname, bases, attrs)
class User(metaclass=MyType):
pass
User.__class__.__bases__
(<class 'object'>,)
mark = User()
mark.__class__.__bases__
(<class 'object'>,)
8.6.14. Metaclass replacements
Metaclasses allows for more customization, but at a cost of complexity
There are several ways, how you can achieve the same effect
SetUp:
import logging
Inheritance and __init__()
method. This requires calling
super()
in a subclass if __init__()
is overridden:
class Logger:
def __init__(self):
self._logger = logging.getLogger(self.__class__.__name__)
class User(Logger):
pass
mark = User()
print(mark._logger)
<Logger User (WARNING)>
Inheritance and __new__()
method. Overriding __new__
method
is less common, but it is possible. In such case, super()
is
needed:
class Logger:
def __new__(cls, *args, **kwargs):
obj = super().__new__(cls)
obj._logger = logging.getLogger(obj.__class__.__name__)
return obj
class User(Logger):
pass
mark = User()
print(mark._logger)
<Logger User (WARNING)>
Class Decorator. This is a more common way to achieve the same effect.
The problem with decorators is that they substitute the class with
a new class Wrapper
, which can lead to problems with inheritance:
def logger(cls):
class Wrapper(cls):
_logger = logging.getLogger(cls.__name__)
return Wrapper
@logger
class User:
pass
mark = User()
print(mark._logger)
<Logger User (WARNING)>
8.6.15. Case Study
import logging
from datetime import datetime, timezone
from uuid import uuid4
Let's create a metaclass EventListener
which enables
to register listeners for class creation.
class OnCreate(type):
listeners = {}
def __new__(metacls, clsname, bases, attrs):
for listener in metacls.listeners.get(clsname, []):
clsname, bases, attrs = listener(clsname, bases, attrs)
return super().__new__(metacls, clsname, bases, attrs)
@classmethod
def of(metacls, *clsnames):
def wrapper(func):
for clsname in clsnames:
if clsname not in metacls.listeners:
metacls.listeners[clsname] = []
metacls.listeners[clsname] += [func]
return wrapper
@OnCreate.of('User')
def trace(clsname, bases, attrs):
attrs['_since'] = datetime.now(timezone.utc)
attrs['_uuid'] = uuid4()
attrs['_log'] = logging.getLogger(clsname)
return clsname, bases, attrs
@OnCreate.of('User', 'Admin')
def info(clsname, bases, attrs):
print(f'Info: {clsname=}')
return clsname, bases, attrs
@OnCreate.of('User', 'Admin')
def debug(clsname, bases, attrs):
print(f'Debug: {clsname=}')
print(f'Debug: {bases=}')
print(f'Debug: {attrs=}')
return clsname, bases, attrs
We can inspect what listeners are already registered to class creations:
result = OnCreate.listeners
pprint(result, width=30)
{'Admin': [<function info at 0x14c9db740>,
<function debug at 0x14c9db7e0>],
'User': [<function trace at 0x14c9db6a0>,
<function info at 0x14c9db740>,
<function debug at 0x14c9db7e0>]}
Let's create a Base
class to hide the metaclass complexity:
class Base(metaclass=OnCreate):
pass
Finally, let's create classes:
class User(Base):
pass
Info: clsname='User'
Debug: clsname='User'
Debug: bases=(<class '__main__.Base'>,)
Debug: attrs={'__module__': '__main__', '__qualname__': 'User', '_since': datetime.datetime(2024, 7, 9, 19, 30, 3, 181585, tzinfo=datetime.timezone.utc), '_uuid': UUID('a20292f4-23c4-4a76-934b-1f2c3fd4967c'), '_log': <Logger User (INFO)>}
class Admin(Base):
pass
Info: clsname='Admin'
Debug: clsname='Admin'
Debug: bases=(<class '__main__.Base'>,)
Debug: attrs={'__module__': '__main__', '__qualname__': 'Admin', '__firstlineno__': 1, '__static_attributes__': ()}
Now we can inspect created classes:
pprint(vars(User))
mappingproxy({'__doc__': None,
'__module__': '__main__',
'_log': <Logger User (INFO)>,
'_since': datetime.datetime(2024, 7, 9, 19, 30, 3, 181585, tzinfo=datetime.timezone.utc),
'_uuid': UUID('a20292f4-23c4-4a76-934b-1f2c3fd4967c')})
pprint(vars(Admin))
mappingproxy({'__doc__': None,
'__firstlineno__': 1,
'__module__': '__main__',
'__static_attributes__': ()})
8.6.16. Use Case - 1
Abstract Base Classes are great example of using metaclasses
ABCMeta
will ensure, that you cannot create an instance of an abstract classABCMeta
at creation ensures that the implementing class covers all abstract methods
SetUp:
from abc import ABCMeta, abstractmethod
ABCMeta
will ensure, that you cannot create an instance of an abstract class:
class Account(metaclass=ABCMeta):
@abstractmethod
def login(self):
...
mark = Account()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class Account without an implementation for abstract method 'login'
ABCMeta
at creation ensures that the implementing class covers all abstract methods:
If not, then an exception is raised:
class Account(metaclass=ABCMeta):
@abstractmethod
def login(self):
...
class User(Account):
pass
mark = User()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class User without an implementation for abstract method 'login'
If those two conditions are met, the instance will be created:
class Account(metaclass=ABCMeta):
@abstractmethod
def login(self):
...
class User(Account):
def login(self):
print('User login')
mark = User()
mark.login()
User login
8.6.17. Use Case - 2
Metaclasses can force a class to inherit from a base class
They can automatically inject a base class to the bases
class Account:
pass
class MyType(type):
def __new__(metacls, clsname, bases, attrs):
if Account not in bases:
bases += (Account,)
cls = type(clsname, bases, attrs)
return cls
Define a class:
class User(metaclass=MyType):
pass
User.mro()
[<class '__main__.User'>, <class '__main__.Account'>, <class 'object'>]
8.6.18. Use Case - 3
Event Listener
class EventListener(type):
listeners = {}
@classmethod
def register(metacls, *clsnames):
def wrapper(func):
for clsname in clsnames:
if not clsname in metacls.listeners:
metacls.listeners[clsname] = []
metacls.listeners[clsname] += [func]
return func
return wrapper
def __new__(metacls, clsname, bases, attrs):
for listener in metacls.listeners.get(clsname, []):
clsname, bases, attrs = listener(clsname, bases, attrs)
return super().__new__(metacls, clsname, bases, attrs)
@EventListener.register('User')
def debug(clsname, bases, attrs):
print(f'Debug: {clsname=}')
print(f'Debug: {bases=}')
print(f'Debug: {attrs=}')
return clsname, bases, attrs
@EventListener.register('User', 'Admin')
def info(clsname, bases, attrs):
print(f'Info: {clsname=}')
return clsname, bases, attrs
EventListener.listeners
{'User': [<function debug at 0x1051c0d60>, <function info at 0x1051c0e00>],
'Admin': [<function info at 0x1051c0e00>]}
class User(metaclass=EventListener):
pass
Debug: clsname='User'
Debug: bases=()
Debug: attrs={'__module__': '__main__', '__qualname__': 'User', '__firstlineno__': 1, '__static_attributes__': ()}
Info: clsname='User'
class Admin(metaclass=EventListener):
pass
Info: clsname='Admin'
8.6.19. Use Case - 5
from datetime import datetime, timezone
import logging
from uuid import uuid4
class EventListener(type):
listeners = {}
@classmethod
def register(metacls, *clsnames):
def decorator(func):
for clsname in clsnames:
if clsname not in metacls.listeners:
metacls.listeners[clsname] = []
metacls.listeners[clsname] += [func]
return decorator
def __new__(metacls, clsname, bases, attrs):
listeners = metacls.listeners.get(clsname, [])
cls = type(clsname, bases, attrs)
for listener in listeners:
cls = listener.__call__(cls)
return cls
Create listener functions and register them for class creation:
@EventListener.register('User', 'Admin')
def add_logger(cls):
cls._log = logging.getLogger(cls.__name__)
return cls
@EventListener.register('User')
def add_trace(cls):
cls._uuid = str(uuid4())
cls._since = datetime.now(tz=timezone.utc)
return cls
Now, define classes with EventListener
metaclass.
class User(metaclass=EventListener):
pass
class Admin(metaclass=EventListener):
pass
pprint(vars(User))
mappingproxy({'__dict__': <attribute '__dict__' of 'User' objects>,
'__doc__': None,
'__firstlineno__': 1,
'__module__': '__main__',
'__static_attributes__': (),
'__weakref__': <attribute '__weakref__' of 'User' objects>,
'_log': <Logger User (WARNING)>,
'_since': datetime.datetime(2024, 12, 1, 11, 32, 45, 680739, tzinfo=datetime.timezone.utc),
'_uuid': 'aa237774-d5a7-448f-bb04-034c06419332'})
pprint(vars(Admin))
mappingproxy({'__dict__': <attribute '__dict__' of 'Admin' objects>,
'__doc__': None,
'__firstlineno__': 1,
'__module__': '__main__',
'__static_attributes__': (),
'__weakref__': <attribute '__weakref__' of 'Admin' objects>,
'_log': <Logger Admin (WARNING)>})
8.6.20. Use Case - 6
Singleton
class Singleton(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 MyClass(metaclass=Singleton):
pass
a = MyClass()
b = MyClass()
a is b
True
id(a)
4375248416
id(b)
4375248416
8.6.21. Use Case - 7
Final
class Final(type):
def __new__(metacls, classname, base, attrs):
for cls in base:
if isinstance(cls, Final):
raise TypeError(f'{cls.__name__} is final and cannot inherit from it')
return type.__new__(metacls, classname, base, attrs)
class MyClass(metaclass=Final):
pass
class SomeOtherClass(MyClass):
pass
Traceback (most recent call last):
TypeError: MyClass is final and cannot inherit from it
8.6.22. Use Case - 8
Django
Access static fields of a class, before creating instance:
... from django.db import models
...
... # class Model(metaclass=...)
... # ...
...
...
... class User(models.Model):
... firstname = models.CharField(max_length=255)
... lastname = models.CharField(max_length=255)