Real-World Metaclasses
00:00 In the previous lesson, I showed you how to write a basic metaclass. In this lesson, I’ll be discussing some of the common real-world uses.
00:09 Before I get to some examples, first a warning. Metaclasses are pretty useful in some very specific cases, but the vast majority of code does not need this technique.
00:20 Don’t go looking for a reason to use them. Most developers aren’t familiar with this, and it can make your code seem spooky and magical. In fact, I’ll bet you’ve been using code that’s built with metaclasses and didn’t even realize it, and that’s the point.
00:33 This is a behind-the-scenes kind of mechanism.
00:40 Okay, I promised some examples. The first one is the factory pattern. Have you ever written a factory method that does some work to an object after instantiating it? Metaclasses can be used as an alternative to that.
00:52 Similarly for enforcing singletons. My favorite use is within ORMs, like Django or SQLAlchemy. The attributes in a Django ORM model define the columns of a table.
01:04 The underlying metaclass magic is inspecting the attributes you put in the class and determining what to do with the database. Meta classes are also useful if you’re trying to use alternate programming techniques, like implementing certain behaviors that aren’t native to Python.
01:19 Aspect-oriented programming is the idea of being able to make changes to a piece of code’s behavior without touching the code. For example, adding entry and exit point logging to a function without touching the function.
01:32 Decorators are in this space.
01:35 Interface-oriented programming is coding against a defined interface. This is kind of like duck-typing and is used in plugin mechanisms. Consider a text editor that knows when you’re writing a function.
01:47 It might expose hooks for doing things with that function. A plugin would only have to implement the interface and be called when the editor sees such text.
01:57 This overlaps a bit with an observer/observable pattern and often involves some sort of registration step. And of course, registration can be a side effect of either class or object instantiation.
02:10 And as for a prototype-based, Python’s style of classes and their hierarchy isn’t the only way. JavaScript and other languages use a mechanism based on object prototypes.
02:22 This means that inheritance is done between objects rather than between classes and objects. Inheritance becomes based on object reuse. Using metaclasses, you could build things that behave this way. One other use of metaclasses is domain-specific languages.
02:39 These are mini-languages based on another programming language to save you the effort of having to write a parser. Let’s talk about some of these in some more detail.
02:51 The factory pattern is usually used when you need a side effect to occur on instance creation. For example, registering the object with a a central tracking data structure.
03:01 You can use a function as a factory, and this is often done with a class method on the class that you invoke instead of creating the object directly.
03:11 The factory creates the object and performs the side effect, returning the object. The problem is there’s no way to force the programmer to use the factory.
03:20
They could accidentally directly instantiate your object and skip your side effect step. With a metaclass, you instead have the metaclass cause the side effect. Of course, this means the programmer has to remember to use the metaclass in the class declaration as well. Depending on what you’re trying to do in your factory, you may be able to get by with simply overriding .__new__()
and not needing a metaclass altogether.
03:49 Singletons are where you want a single instance of an object. These are often gateways to real-world resources like references to a database connection. One use of a factory method is to enforce the single-use policy.
04:03
If the object hasn’t been created yet, the factory creates it and keeps a reference. Subsequent calls to the factory return the object. Alternatively, you can implement it as a metaclass and then throw an exception at multiple creation or track and return the object. Like with the factory, careful use of .__new__()
in the object can avoid the need for a metaclass.
04:28 If you want to make sure that your class can only ever be inherited from once, then you’d have to use a metaclass, but I’m not sure why you’d ever want to do that. I
04:40
mentioned the ORM concept earlier. So here’s a short example. This is a Django model class that I borrowed from the DRF course. The .title
and .restricted
attributes here map to varchar and Boolean columns in a database, and the whole class maps to a table.
04:57 Instances of this object correspond to rows in the table, so the metaclasses is needed to do the mapping. All the database magic has to be done at class creation, not at instance creation.
05:09 The factory and singleton examples can be accomplished without a metaclass. This ORM framework stuff requires it.
05:19
Domain-specific languages are sublanguages built in other languages, type of the type
, never mind. They’re a quick way of building a language without having to implement a whole parser. A Python-based DSL would also be Python, so the Python parser does the hard work.
05:38 Consider a DSL for constructing HTML documents. You could create classes for each tag and then have rules around what things could be inside of other things, and use metaclass hooks to enforce these kinds of rules.
05:51 And by doing these things through class declarations, you could add data to some sort of central document. The classes in your file would correspond to the HTML tags and would never get turned into actual instance objects.
06:04 The parser would then build the corresponding document as a side effect of instantiating the classes.
06:14
Well, that was a lot. One more lesson before the end. Let’s go look at how the Python standard library uses metaclasses to implement Enum
.
Christopher Trudeau RP Team on Nov. 11, 2023
Metaclasses are kind of factories by definition: their entire purpose is to create other classes.
Its been a while since I wrote this course, so I can’t quite remember what examples I used, so hopefully the following isn’t repetition.
Inside of Django, you have Model classes that represent tables in the database. Django comes with a tool called the Django Admin, which is a web based interface for doing operations on those same Model classes. For each Model you build, you build an AdminModel that controls how the data is shown on the screen. For example, one of the fields in the AdminModel specifies which columns are displayed in the content listing.
A lot of AdminModel code is boiler plate. You can write methods that link to related tables and then include them as columns in the interface. This is where metaclasses can come in: you could write a tool (I have done so) that is an AdminModel class creator. It takes a few parameters to specify what columns there are and how they define links, and the end result is a new subclass of AdminModel with the appropriate settings.
The version of the tool I built didn’t use a metaclass, but dynamically added methods to a helper class, but the idea is the same. When you get this deep into Python’s dynamic programming, there are often several different ways of approaching a problem. Metaclasses are just the granddaddy of all the hammers :)
Become a Member to join the conversation.
Demetre Dzmanashvili on Nov. 11, 2023
Hello, Thanks for the amazing course of Metaclasses in python. It is really advanced topic that most of the people does not need to know. But I am very curious about those stuff.
In this course video you mentioned that you can use metaclasses for Factory Design Pattern, I have used dunder new method for that case that I came up with and was very proud about myself, but I cant think the way to use Metaclass to do that. I think it is because Metaclasses are new for me and cant think of the way yet. Can you show me one of the simple example of using metaclasses for factory pattern.
Thanks a lot.