5. Metaclass

"Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why)." -- Tim Peters

5.1. Metaclass mechanism

5.1.1. What are Metaclasses?

  • Class is an instance of a Metaclass

  • Class defines how an instance of the class behaves

  • Metaclass defines how a class behaves

../_images/metaclass-instances.png

Figure 43. Class is an instance of a metaclass.

5.1.2. How Metaclasses works?

  • Instances are created by calling the class

  • Python creates a new class (when it executes the 'class' statement) by calling the metaclass

  • Combined with the normal __init__ and __new__ methods

  • Metaclasses allow you to do 'extra things' when creating a class

5.1.3. Example use of Metaclasses

  • Allow customization of class instantiation

  • Most commonly used as a class-factory

  • Registering the new class with some registry

  • Replace the class with something else entirely

5.2. Type and objects

5.2.1. Types

type(int)       # <class 'type'>
type(float)     # <class 'type'>
type(dict)      # <class 'type'>
type(list)      # <class 'type'>
type(tuple)     # <class 'type'>

type(type)      # <class 'type'>
../_images/metaclass-class-chain.png

Figure 44. Class is an instance of a metaclass.

5.2.2. Objects

Listing 434. Metaclass
class Iris:
    pass

flower = Iris()

isinstance(flower, Iris)    # True
isinstance(flower, object)  # True

Iris.__mro__
# (<class '__main__.Iris'>, <class 'object'>)
type(object)    # <class 'type'>
type(type)      # <class 'type'>

5.3. Example

class Iris:
    pass

def new(cls):
    obj = object.__new__(cls)
    obj.kingdom = 'Plantae'
    return obj

Iris.__new__ = new

setosa = Iris()
versicolor = Iris()

setosa.kingdom      # Plantae
versicolor.kingdom  # Plantae
Listing 435. Spoiler alert: This doesn't work!
def new(cls):
    obj = type.__new__(cls)
    obj.kingdom = 'Plantae'
    return obj

type.__new__ = new
# TypeError: can't set attributes of built-in/extension type 'type'
class Iris(type):
    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        obj.kingdom = 'Plantae'
        return obj

class Setosa(metaclass=Iris):
    pass

class Virginica(metaclass=Iris):
    pass

class Versicolor(metaclass=Iris):
    pass


Setosa.kingdom         # Plantae
Virginica.kingdom      # Plantae
Versicolor.kingdom     # Plantae

5.4. Factories

5.4.1. Object factory

Listing 436. Object factory
class Iris:
    def __init__(self):
        self.kingdom = 'Plantae'


setosa = Iris()
versicolor = Iris()
virginica = Iris()

setosa.kingdom          # Plantae
versicolor.kingdom      # Plantae
virginica.kingdom       # Plantae

5.4.2. Class Factory

Listing 437. Class Factory
class Iris(type):
    def __init__(cls, *args, **kwargs):
        cls.kingdom = 'Plantae'


 class Setosa(metaclass=Iris):
    pass

class Virginica(metaclass=Iris):
    pass

class Versicolor(metaclass=Iris):
    pass


Setosa.kingdom         # Plantae
Virginica.kingdom      # Plantae
Versicolor.kingdom     # Plantae

5.5. Metaclass replacements

  • Effectively accomplish the same thing

5.5.1. Inheritance

class Iris:
    kingdom = 'Plantae'

class Setosa(Iris):
    pass

Setosa.kingdom
# Plantae

5.5.2. Class Decorator

def add_kingdom(cls):
    class NewIris(cls):
        kingdom = 'Plantae'
    return NewIris

@add_kingdom
class Iris:
    pass

Iris.kingdom
# Plantae