I have a rough idea of what meta-classes are. They are the classes of which class objects are based on (because classes are objects in Python). But could someone explain (with code) how one goes about creating one.
There are (at this point) two key methods in a metaclass:
__prepare__
, and__new__
__prepare__
lets you supply a custom mapping (such as an OrderedDict
) to be used as the namespace while the class is being created. You must return an instance of whatever namespace you choose. If you don't implement __prepare__
a normal dict
is used.
__new__
is responsible for the actual creation/modification of the final class.
A bare-bones, do-nothing-extra metaclass would look like:
class Meta(type):
def __prepare__(metaclass, cls, bases):
return dict()
def __new__(metacls, cls, bases, clsdict):
return super().__new__(metacls, cls, bases, clsdict)
A simple example:
Say you want some simple validation code to run on your attributes -- like it must always be an int
or a str
. Without a metaclass, your class would look something like:
class Person:
weight = ValidateType('weight', int)
age = ValidateType('age', int)
name = ValidateType('name', str)
As you can see, you have to repeat the name of the attribute twice. This makes typos possible along with irritating bugs.
A simple metaclass can address that problem:
class Person(metaclass=Validator):
weight = ValidateType(int)
age = ValidateType(int)
name = ValidateType(str)
This is what the metaclass would look like (not using __prepare__
since it is not needed):
class Validator(type):
def __new__(metacls, cls, bases, clsdict):
# search clsdict looking for ValidateType descriptors
for name, attr in clsdict.items():
if isinstance(attr, ValidateType):
attr.name = name
attr.attr = '_' + name
# create final class and return it
return super().__new__(metacls, cls, bases, clsdict)
A sample run of:
p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'
produces:
9
Traceback (most recent call last):
File "simple_meta.py", line 36, in <module>
p.weight = '9'
File "simple_meta.py", line 24, in __set__
(self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')
Notes
This example is simple enough it could have also been accomplished with a class decorator, but presumably an actual metaclass would be doing much more.
In Python 2.x, the __prepare__
method doesn't exist, and the class speficies its metaclass by including a class variable __metaclass__ = ...
, like this:
class Person(object):
__metaclass__ = ValidateType
The 'ValidateType' class for reference:
class ValidateType:
def __init__(self, type):
self.name = None # will be set by metaclass
self.attr = None # will be set by metaclass
self.type = type
def __get__(self, inst, cls):
if inst is None:
return self
else:
return inst.__dict__[self.attr]
def __set__(self, inst, value):
if not isinstance(value, self.type):
raise TypeError('%s must be of type(s) %s (got %r)' %
(self.name, self.type, value))
else:
inst.__dict__[self.attr] = value
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With