Skip to content

Latest commit

 

History

History
148 lines (102 loc) · 4.82 KB

metaclasses.md

File metadata and controls

148 lines (102 loc) · 4.82 KB

Metaclasses

Table of Contents

Basic

Metaclasses are a very basic but very advanced technique in Python. Even if you understand this technique, you will hardly ever use it in practice because it can be very useful but very destructive!

When it comes to metaclasses, it's like a chicken-and-egg or egg-and-chicken question. We all know how to create and instantiate a class, but who created the class?

def createClass(x):
    class Cls:
        def __init__(self, x):
            self.x = x

    return Cls(x)


cls = createClass(10)
print(type(cls))  # <class '__main__.createClass.<locals>.Cls'>
print(cls.x)      # 10

For example, above we can use function to create a class, which is created by the createClass method.

But what about the following example? Who allowed us to use class syntax to create the class?

# Who created Cls?
class Cls:
    def __init__(self, x):
        self.x = x

type

We all know that you can use type() to know the class of an object, so can't we use type() to find the class of the Cls class?

cls = Cls(10)
type(cls)       # <class '__main__.Cls'>
type(type(cls)) # <class 'type'>

Wow, it turns out that all classes are created from type! We can use type(cls_name, bases, attrs) to do exactly the same thing as class syntax, which is to create a class.

Creating a class with type() requires three input parameters:

  1. cls_name: name of class
  2. bases: inherited objects
  3. attrs: the properties (variables and methods) owned by this class
cls = type("Cls", (object, ), {"x": 10})
print(cls)    # <class '__main__.Cls'>
print(cls.x)  # 10

Metaclasses

Here's the kicker, our metaclasses are actually interfaces between type and class!

Let's create a metaclass inherited from type, if the metaclass does not inherit type, then it is just a normal class generated by type. The metaclass is created using the magic method __new__ and returned using type(). Don't forget to pass three main parameters of type, the first parameter mtcls is the metaclass itself.

class Meta(type):
    def __new__(mtcls, name, bases, attrs):
        print('name: ', name)
        print('bases: ', bases)
        print('attrs: ', attrs)
        return super().__new__(mtcls, name, bases, attrs)


class Cls(object, metaclass=Meta):
    x = 0

    def __init__(self, val):
        self.x = val

# name:  Cls
# bases:  (<class 'object'>,)
# attrs:  {'__module__': '__main__', '__qualname__': 'Cls', 'x': 0, '__init__': <function Cls.__init__ at 0x00000156333413A8>}

As you can see, the Cls implemented with the metaclass automatically print out all the contents of the __new__ in metaclass Meta, and even Cls hasn't been instantiated yet.

Usage

Metaclasses can do so many crazy things, so be very careful when using them!

For example, I can set the attrs of a metaclass to a blank dict, then the class which implemented the metaclass will be wrong no matter what I do.

class Meta(type):
    def __new__(mtcls, name, bases, attrs):
        return super().__new__(mtcls, name, bases, {})  # <------ blank attrs

class Cls(object, metaclass=Meta):
    x = 0

    def __init__(self, val):
        self.x = val


cls = Cls(10)  # TypeError: Cls() takes no arguments

...

cls = Cls()
print(cls.x)   # AttributeError: 'Cls' object has no attribute 'x'

But suppose we want someone who inherits our class to have the function must_to_do() when they write their own class. We can then check beforehand in the metaclass's __new__ method and allow the user to successfully create a class or raise exception, as the case may be.

class Meta(type):
    def __new__(mtcls, name, bases, attrs):
        if name != "Base" and "must_to_do" not in attrs:
            raise TypeError("Bad User Class: must_to_do() is needed")
        return super().__new__(mtcls, name, bases, attrs)


class Base(metaclass=Meta):
    def server_func(self):
        return self.must_to_do()


class UserClass(Base):
    pass 
# TypeError: Bad User Class: must_to_do() is needed

Related Articles

Article Link
What Does It Take To Be An Expert At Python? https://youtu.be/7lmCu8wz8ro?t=2311
Metaclasses & How Classes Really Work https://www.youtube.com/watch?v=NAQEj-c2CI8
淺談 Python Metaclass https://medium.com/@dboyliao/淺談-python-metaclass-dfacf24d6dd5