Metaclass Creation
00:00
In the previous lesson, I showed you how to dynamically create a class using the type()
function and how to make an object callable with .__call__()
. In this lesson, I’ll finally be getting to the subject matter: metaclasses.
00:14
You saw how the .__call__()
method can turn an object into a callable. At a more general level, you can state that in Python, parenthesis invokes objects, and, well, you may have heard somewhere that everything’s an object, right?
00:29
Consider print
without the parenthesis. It’s a reference. But call it with the parenthesis, and you’re invoking it. See, that parenthesis thing works for everything.
00:43
So classes are objects, and when you use parenthesis on the class, you are invoking it. It’s callable. All classes inherent from the base type
metaclass, and that metaclass has a .__call__()
.
00:58
That’s why the .__call__()
tangent in the previous lesson. It’s all fitting together. So what happens when you invoke a class? The type
metaclass creates the object and calls two dunder methods on it: .__new__()
and .__init__()
.
01:13
There are default implementations of these methods, so if you don’t define them, the parent’s methods get called. .__new__()
is responsible for creating the object, which I’ll show you in a minute.
01:25
And .__init__()
you’re probably familiar with. This is why you’ll occasionally come across someone making the argument that the .__init__()
method is not a constructor, so you shouldn’t call it that.
01:35 In most languages, the constructor creates and initializes the object. In Python, these things are separated into two parts. Don’t let that stop you from calling it a constructor.
01:46 Everyone knows what you mean. And if someone challenges you, you can go to town on them about types and callables because you just learned all that. All right, let’s go play with some of these special methods.
01:58
I’m going to use the same technique as before, creating a function then changing it into a method. I’ll start with the new()
function.
02:12
The primary purpose of new()
is to create the object. You can do that by using the .__new__()
method on the obj
object. Yep, type of type
isn’t the only thing. You’ve also got the obj
object.
02:24 You pass in a reference to the class to be constructed, And Python instantiates an instance for you.
02:35
Once you’ve got the instance, you can add attributes to it. And the one thing you must remember is your .__new__()
must return the constructed instance. With the function defined, let’s use type()
to create a class.
02:58
In its attribute dictionary, I’m turning the new()
function into the .__new__()
method. Now I’m going to create some ceviche
. Mm, raw seafood.
03:13
And because .__new__()
was called when the instance was created, the .description
was added as an attribute. Of course, this is an overly complicated way of doing this.
03:26
You can simply override .__new__()
in your class syntax definition, just like you override .__init__()
. But then you wouldn’t be learning what I’m going to show you next.
03:38
You just saw how .__new__()
is called when an object instance is created. But what if you want something done when the class itself is created?
03:46
The type
metaclass does have a .__new__()
method, but you’re not allowed to override it. There’d be no way to instantiate, and ultimately the buck has to stop somewhere.
03:57
So instead, Python provides the metaclass
argument to the inheritance syntax. You create a metaclass like a regular class, except it inherits from type
and typically overrides the .__new__()
on the class.
04:13 The resulting metaclass can then be passed into the inheritance part of the class definition. This is done inside the parenthesis where you indicate inheritance from another class.
04:22
You provide the metaclass
keyword argument and pass it the metaclass to be used as a hook into creation. Let’s go turn all that word soup into a concrete example.
04:36 The metaclass I’m going to show you contains a simple counter that gets incremented when it is used. This is kind of like having a counter that tracks how many instances of an object there are. But instead, this counts how many classes inherit using this metaclass.
04:56
The first step for a metaclass is to define a class that inherits from type
. I almost always use the word meta in the name of the class to help provide some clarity.
05:06 The other technique I’ve seen, and you’ll see later, is to use the word type in the name instead. In order to be able to count things, I need a counter, so here I’ve created an attribute on the metaclass.
05:26
Pardon the lack of spacing here. The REPL doesn’t like blank lines. I’m overriding the .__new__()
method of the metaclass. Note the arguments here.
05:35
Do they look familiar? They’re the same three arguments that the type()
function uses to create a class
05:50
inside the .__new__()
. I’m using super()
to invoke the class creation of the parent, which will be the new type
metaclass.
05:58 This is what actually creates the class instance. This will be the instance of the class using the metaclass, not the metaclass itself.
06:10
And here is my side effect. Each time a new class is created using this metaclass counter, the metaclass .count
attribute is incremented. I initialized it to 0
, so the first creation will have a count of 1
.
06:28
Finally, like an object’s .__new__()
, the class’s .__new__()
needs to return the created class. Let’s use the metaclass by including it in the inheritance syntax.
06:44
This defines the Pie
class—mmmm, pie—and uses the counter metaclass by passing in a reference with the metaclass
argument. Note that it’s a reference to metaclass
. It isn’t invoked.
06:58 There are no parentheses here. I prefer simple pie. This one’s more of a crust. There’s nothing inside of it. Make some apple … and some raspberry … and let’s see the count.
07:18
Did you expect that to work? Remember the .count
attribute is on the metaclass, not on the class. It isn’t a class attribute. I can get at it, though, using the .__class__
attribute. The count is 1
.
07:35
Having apple
and raspberry
isn’t the relevant part. This code doesn’t care how many instances of Pie
there are, only how many classes use the metaclass counter. So far, there’s only one Pie
, so the count is 1
. What’s better than pie?
07:56
Cake, of course. Cake is definitely better. I’ll fight you. Like Pie
, here I’m using the metaclass
keyword to use the counter metaclass hook.
08:07
And like Pie
, I’m going to keep it simple. Some cake, some more cake.
08:20
And there’s the counter. Four object instances, but only two classes, so the count is 2
. I’m not sure why you’d want to count how many classes are based on a particular metaclass, but the important part is the side effect. You can now hook when a programmer defines a class and do things with it. What kinds of things? Well, I’m glad you asked. In the next lesson, I’ll outline some common uses of metaclasses in the real world.
Become a Member to join the conversation.