Table of Contents
Classes
Classes as objects
Before understanding metaclasses, you need to master classes in Python. And Python has a very peculiar idea of what classes are, borrowed from the Smalltalk language.
In most languages, classes are just pieces of code that describe how to produce an object. That's kinda true in Python too:
class ObjectCreator(object):
.
.. pass …
my_object = ObjectCreator()print(my_object)
<
main.ObjectCreator object at 0x8974f2c>
But classes are more than that in Python. Classes are objects too.
Yes, objects.
As soon as you use the keyword class, Python executes it and creates an OBJECT. The instruction
class ObjectCreator(object):
.
.. pass …
creates in memory an object with the name “ObjectCreator”.
This object (the class) is itself capable of creating objects (the instances), and this is why it's a class.
But still, it's an object, and therefore:
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
e.g.:
print(ObjectCreator) # you can print a class because it's an object
<
class 'main.ObjectCreator'>
def echo(o):
.
.. print(o) …
>>> echo(ObjectCreator) # you can pass a class as a parameter
<
class 'main.ObjectCreator'>
print(hasattr(ObjectCreator, 'new_attribute'))
F
alse
ObjectCreator.new_attribute = 'foo' # you can add attributes to a classprint(hasattr(ObjectCreator, 'new_attribute'))
T
rue
print(ObjectCreator.new_attribute)
f
oo
ObjectCreatorMirror = ObjectCreator # you can assign a class to a variableprint(ObjectCreatorMirror.new_attribute)
f
oo
print(ObjectCreatorMirror())
<
main.ObjectCreator object at 0x8997b4c>
Creating classes dynamically
Since classes are objects, you can create them on the fly, like any object.
First, you can create a class in a function using class:
def choose_class(name):
.
.. if name == 'foo': …
class Foo(object):
…
pass
…
return Foo # return the class, not an instance
…
else:
…
class Bar(object):
…
pass
…
return Bar
…
>>> MyClass = choose_class('foo')
print(MyClass) # the function returns a class, not an instance<
class 'main.Foo'>
print(MyClass()) # you can create an object from this class
<
main.Foo object at 0x89c6d4c>
But it's not so dynamic, since you still have to write the whole class yourself.
Since classes are objects, they must be generated by something.
When you use the class keyword, Python creates this object automatically. But as with most things in Python, it gives you a way to do it manually.
Remember the function type? The good old function that lets you know what type an object is:
print(type(1))
<
type 'int'>
print(type("1"))
<
type 'str'>
print(type(ObjectCreator))
<
type 'type'>
print(type(ObjectCreator()))
<
class 'main.ObjectCreator'>
Well, type has a completely different ability, it can also create classes on the fly. type can take the description of a class as parameters, and return a class.
(I know, it's silly that the same function can have two completely different uses according to the parameters you pass to it. It's an issue due to backwards compatibility in Python)
type works this way:
type(name of the class,
tuple of the parent class (for inheritance, can be empty), dictionary containing attributes names and values)
e.g.:
class MyShinyClass(object):
.
.. pass
can be created manually this way:
MyShinyClass = type('MyShinyClass', (), {}) # returns a class objectprint(MyShinyClass)
<
class 'main.MyShinyClass'>
print(MyShinyClass()) # create an instance with the class
<
main.MyShinyClass object at 0x8997cec>
You'll notice that we use “MyShinyClass” as the name of the class and as the variable to hold the class reference. They can be different, but there is no reason to complicate things.
type accepts a dictionary to define the attributes of the class. So:
class Foo(object):
.
.. bar = True
Can be translated to:
Foo = type('Foo', (), {'bar':True})
And used as a normal class:
print(Foo)
<
class 'main.Foo'>
print(Foo.bar)
T
rue
f = Foo()print(f)
<
main.Foo object at 0x8a9b84c>
print(f.bar)
T
rue
And of course, you can inherit from it, so:
class FooChild(Foo):
.
.. pass
would be:
FooChild = type('FooChild', (Foo,), {})print(FooChild)
<
class 'main.FooChild'>
print(FooChild.bar) # bar is inherited from Foo
T
rue
Eventually you'll want to add methods to your class. Just define a function with the proper signature and assign it as an attribute.
def echo_bar(self):
.
.. print(self.bar) …
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
hasattr(Foo, 'echo_bar')F
alse
hasattr(FooChild, 'echo_bar')
T
rue
my_foo = FooChild()my_foo.echo_bar()
T
rue
You see where we are going: in Python, classes are objects, and you can create a class on the fly, dynamically.
This is what Python does when you use the keyword class, and it does so by using a metaclass. What are metaclasses (finally)
Metaclasses are the 'stuff' that creates classes.
You define classes in order to create objects, right?
But we learned that Python classes are objects.
Well, metaclasses are what create these objects. They are the classes' classes, you can picture them this way:
MyClass = MetaClass() MyObject = MyClass()
You've seen that type lets you do something like this:
MyClass = type('MyClass', (), {})
It's because the function type is in fact a metaclass. type is the metaclass Python uses to create all classes behind the scenes.
Now you wonder why the heck is it written in lowercase, and not Type?
Well, I guess it's a matter of consistency with str, the class that creates strings objects, and int the class that creates integer objects. type is just the class that creates class objects.
You see that by checking the class attribute.
Everything, and I mean everything, is an object in Python. That includes ints, strings, functions and classes. All of them are objects. And all of them have been created from a class:
age = 35age.__class__
<
type 'int'>
name = 'bob'name.__class__
<
type 'str'>
def foo(): passfoo.__class__
<
type 'function'>
class Bar(object): passb = Bar()b.__class__
<
class 'main.Bar'>
Now, what is the class of any class ?
age.__class__.__class__
<
type 'type'>
name.__class__.__class__
<
type 'type'>
foo.__class__.__class__
<
type 'type'>
b.__class__.__class__
<
type 'type'>
So, a metaclass is just the stuff that creates class objects.
You can call it a 'class factory' if you wish.
type is the built-in metaclass Python uses, but of course, you can create your own metaclass. The metaclass attribute
You can add a metaclass attribute when you write a class:
class Foo(object):
__metaclass__ = something... [...]
If you do so, Python will use the metaclass to create the class Foo.
Careful, it's tricky.
You write class Foo(object) first, but the class object Foo is not created in memory yet.
Python will look for metaclass in the class definition. If it finds it, it will use it to create the object class Foo. If it doesn't, it will use type to create the class.
Read that several times.
When you do:
class Foo(Bar):
pass
Python does the following:
Is there a metaclass attribute in Foo?
If yes, create in memory a class object (I said a class object, stay with me here), with the name Foo by using what is in metaclass.
If Python can't find metaclass, it will look for a metaclass in Bar (the parent class), and try to do the same.
If Python can't find metaclass in any parent, it will look for a metaclass at the MODULE level, and try to do the same.
Then if it can't find any metaclass at all, it will use type to create the class object.
Now the big question is, what can you put in metaclass ?
The answer is: something that can create a class.
And what can create a class? type, or anything that subclasses or uses it. Custom metaclasses
The main purpose of a metaclass is to change the class automatically, when it's created.
You usually do this for APIs, where you want to create classes matching the current context.
Imagine a stupid example, where you decide that all classes in your module should have their attributes written in uppercase. There are several ways to do this, but one way is to set metaclass at the module level.
This way, all classes of this module will be created using this metaclass, and we just have to tell the metaclass to turn all attributes to uppercase.
Luckily, metaclass can actually be any callable, it doesn't need to be a formal class (I know, something with 'class' in its name doesn't need to be a class, go figure… but it's helpful).
So we will start with a simple example, by using a function.