MetaClasses
Amit Kumar | aktech | dtu.amit@gmail.com
Introducing
First Introducing Myself
- SymPy Developer
- Mathematics and Computing Graduate
- Pythonista
- FOSS Enthusiast
Everything is an object.
So is a Class!
Class "object".
- you can assign it to a variable
- you can copy it
- you can add attributes to it
- you can pass it as a function parameter
Class "object".
An object is created by a class:
>>> Class Foo():
... pass
>>> foo_object = Foo()
Object:
Created by Class Foo:
Class "object".
Foo is also an object, since everything is an object in Python!
Now, which class creates Foo?
Class "object".
Lets see the class which creates Foo:
>>> type(Foo)
type
type? What does that mean?
Class "object".
Now what class creates "type"?
>>> type(type)
type
Inception!
The Other side of type
You can create classes with type on the fly!
The Other side of type
type( , , )
Name of the
class to be created
tuple of the parent class (for inheritance, can be empty)
dictionary containing attributes names and values
class_name
parent_classes
attributes
The Other side of type
>>> class MyClass(object): pass
>>> MyClass
__main__.MyClass
>>> MyClass = type('MyClass', (), {})
>>> MyClass
__main__.MyClass
Both are equivalent!
The Other side of type
>>> class Foo(object):
... bar = True
>>> Foo.bar
True
>>> Foo = type('Foo', (), {'bar':True})
>>> Foo.bar
True
=
type accepts a dictionary to define the attributes of the class.
The Other side of type
>>> class Car(Foo):
... def wheels(self):
... return 4
>>> car = Car()
>>> car.wheels()
4
>>> def wheels(self):
... return 4
>>> Car = type('Car', (), {'wheels': wheels})
>>> car = Car()
>>> car.wheels()
4
=
Define methods!
The Other side of type
This is what happens behind the scenes when you use the keyword class, and it does so by using a "metaclass".
Which Metaclass does that?
type
>>> x = 1
>>> type(x)
int
And this is what you thought type could do?
type
Thug Life!
What is a Meta Class?
The secret sauce which create classes.
So the Analogy is ...
object : class ::
class : Metaclass
Lets Create one!
A metaclass which would keep all the class attributes lowercase.
A metaclass which would keep all the class attributes lowercase.
class Car(object):
def WHEELS(self):
return 4
When you do this:
This Happens:
Car = type('Car', (),
{'WHEELS': WHEELS})
Where wheels is:
def WHEELS(self):
return 4
and we would like to change that!
A metaclass which would keep all the class attributes lowercase.
So we will write our custom metaclass to change that behaviour, on top of type metaclass
class LowerAttrMetaclass(type):
def __new__(lowerattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
# do some custom stuff here
# change future_class_attr to lowercase_attr
return type(future_class_name, future_class_parents,
lowercase_attr)
A metaclass which would keep all the class attributes lowercase.
class LowerAttrMetaclass(type):
def __new__(lowerattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
lowercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
lowercase_attr[name.lower()] = val
else:
lowercase_attr[name] = val
return type(future_class_name, future_class_parents,
lowercase_attr)
We have written our first metaclass!
How would we make our classes use our custom metaclass?
Instead of type!
The __metaclass__ attribute
This is how we make our classes use custom metaclasses:
class Foo():
__metaclass__ = CustomMetaClass
# Do stuff
class Foo(metaclass=CustomMetaClass):
# Do stuff
Python 2
Python 3
Writing MetaClasses
Override __new__ or __init__ ?
-
modify the class by modifying some class attribute: Override __new__
-
when we are looking just to carry out checks: Override __init__
Writing MetaClasses
Metaclasses in Action
enforce the requirement that all non-private methods in the project must have docstrings;
Example 1
Metaclasses in Action
Example 1
class DocMeta(type):
def __init__(self, name, bases, attrs):
for key, value in attrs.items():
# skip special/private methods
if key.startswith("__"): continue
# skip any non-callable
if not hasattr(value, "__call__"): continue
# check for a doc string.
if not getattr(value, '__doc__'):
raise TypeError("%s must have a docstring" % key)
type.__init__(self, name, bases, attrs)
Metaclasses in Action
Example 1
class Door(metaclass=DocMeta):
def __init__(self, number, status):
self.number = number
self.status = status
def open(self):
self.status = 'open'
def close(self):
self.status = 'closed'
>>> D = Door(1, 'open')
Traceback [...]
TypeError: close must have a
docstring
Metaclasses in Action
Example 2
Creating Abstract Base Classes
(classes that are only meant to be inherited from)
Not meant to be instantiated!
Metaclasses in Action
Example 2
from abc import (ABCMeta,
abstractmethod)
class Vehicle(object):
__metaclass__ = ABCMeta
@abstractmethod
def change_gear(self):
pass
@abstractmethod
def start_engine(self):
pass
class Car(Vehicle):
def __init__(self, make, model, color):
self.make = make
self.model = model
self.color = color
# abstract methods not implemented
>>> car = Vehicle("Toyota", "Avensis", "silver")
Traceback [..]
TypeError: Can't instantiate abstract class Car
with abstract methods change_gear, start_engine
Metaclasses in Action
Example 2
class Car(Vehicle):
def __init__(self, make, model, color):
self.make = make
self.model = model
self.color = color
def change_gear(self):
print("Changing gear")
def start_engine(self):
print("Changing engine")
>>> car = Car("Audi", "A4", "white")
>>> print(isinstance(car, Vehicle))
True
# Now it works!
References
Questions?
Thank You!
@iaktech dtu.amit.@gmail.com
This work is licensed under a Creative Commons
Introducing Metaclasses in Python
By Amit Kumar
Introducing Metaclasses in Python
A talk on Metaclasses in Python.
- 5,404