MetaClasses 

Amit Kumar |       aktech |     dtu.amit@gmail.com

Introducing

First Introducing Myself

  • SymPy Developer
  • Mathematics and Computing Graduate
  • Pythonista
  • FOSS Enthusiast

Everything is an object.

So is a Class!

Class "object".

 

  • 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

Class "object".

 

An object is created by a class:

>>> Class Foo():
...     pass
>>> foo_object = Foo()

Object:

Created by Class Foo:

Class "object".

Foo is also an object, since everything is an object in Python!

Now, which class creates Foo?

Class "object".

Lets see the class which creates Foo:

>>> type(Foo)
type

type? What does that mean?

Class "object".

Now what class creates "type"?

>>> type(type)
type

Inception!

The Other side of type

You can create classes with type on the fly!

The Other side of type

type(                       ,                              ,                     )

Name of the

class to be created

tuple of the parent class (for inheritance, can be empty)

dictionary containing attributes names and values

class_name

parent_classes

attributes

The Other side of type

>>> class MyClass(object): pass

>>> MyClass
__main__.MyClass
>>> MyClass = type('MyClass', (), {})

>>> MyClass
__main__.MyClass

Both are equivalent!

The Other side of type

>>> class Foo(object):
...       bar = True
>>> Foo.bar
True
>>> Foo = type('Foo', (), {'bar':True})
>>> Foo.bar
True

=

type accepts a dictionary to define the attributes of the class.

The Other side of type

>>> class Car(Foo):
...     def wheels(self):
...         return 4
>>> car =  Car()
>>> car.wheels()
4
>>> def wheels(self):
...     return 4
>>> Car = type('Car', (), {'wheels': wheels})
>>> car =  Car()
>>> car.wheels()
4

=

Define methods!

The Other side of type

This is what happens behind the scenes when you use the keyword class, and it does so by using a "metaclass".

Which Metaclass does that?

type

>>> x = 1
>>> type(x)
int

And this is what you thought type could do?

type

Thug Life!

What is a Meta Class?

The secret sauce which create classes.

So the Analogy is ...

object : class ::

class : Metaclass

Lets Create one!

A metaclass which would keep all the class attributes lowercase.

A metaclass which would keep all the class attributes lowercase.

class Car(object):
    def WHEELS(self):
        return 4

When you do this:

This Happens:

Car = type('Car', (),
           {'WHEELS': WHEELS})

Where wheels is:

def WHEELS(self):
    return 4

and we would like to change that!

A metaclass which would keep all the class attributes lowercase.

So we will write our custom metaclass to change that behaviour, on top of type metaclass

class LowerAttrMetaclass(type):
    def __new__(lowerattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):
        # do some custom stuff here
        # change future_class_attr to lowercase_attr
        return type(future_class_name, future_class_parents,
                    lowercase_attr)

A metaclass which would keep all the class attributes lowercase.

class LowerAttrMetaclass(type):
    def __new__(lowerattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        lowercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                lowercase_attr[name.lower()] = val
            else:
                lowercase_attr[name] = val
        return type(future_class_name, future_class_parents,
                    lowercase_attr)

We have written our first metaclass!

How would we make our classes use our custom metaclass?

Instead of type!

The __metaclass__ attribute

This is how we make our classes use custom metaclasses:

class Foo():
    __metaclass__ = CustomMetaClass
    # Do stuff
class Foo(metaclass=CustomMetaClass):
    # Do stuff

Python 2

Python 3

Writing MetaClasses

Override __new__ or __init__ ?

  • modify the class by modifying some class attribute: Override __new__

  • when we are looking just to carry out checks: Override __init__

Writing MetaClasses

Metaclasses in Action

enforce the requirement that all non-private methods in the project must have docstrings;

Example 1

Metaclasses in Action

Example 1

class DocMeta(type):
    def __init__(self, name, bases, attrs):
        for key, value in attrs.items():
            # skip special/private methods
            if key.startswith("__"): continue
            # skip any non-callable
            if not hasattr(value, "__call__"): continue
            # check for a doc string.
            if not getattr(value, '__doc__'):  
                raise TypeError("%s must have a docstring" % key)
        type.__init__(self, name, bases, attrs)

Metaclasses in Action

Example 1

class Door(metaclass=DocMeta):
    def __init__(self, number, status):
        self.number = number
        self.status = status
    def open(self):
        self.status = 'open'
    def close(self):
        self.status = 'closed'
>>> D = Door(1, 'open')
Traceback [...]
TypeError: close must have a
docstring

Metaclasses in Action

Example 2

Creating Abstract Base Classes

(classes that are only meant to be inherited from)

Not meant to be instantiated!

Metaclasses in Action

Example 2

from abc import (ABCMeta,
          abstractmethod)

class Vehicle(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def change_gear(self):
        pass

    @abstractmethod
    def start_engine(self):
        pass
class Car(Vehicle):
    def __init__(self, make, model, color):
        self.make = make
        self.model = model
        self.color = color


# abstract methods not implemented
>>> car = Vehicle("Toyota", "Avensis", "silver")
Traceback [..]
TypeError: Can't instantiate abstract class Car
with abstract methods change_gear, start_engine

Metaclasses in Action

Example 2

class Car(Vehicle):

    def __init__(self, make, model, color):
        self.make = make
        self.model = model
        self.color = color

    def change_gear(self):
        print("Changing gear")

    def start_engine(self):
        print("Changing engine")
>>> car = Car("Audi", "A4", "white")
>>> print(isinstance(car, Vehicle))
True

# Now it works!

References

Questions?

Thank You!

    @iaktech        dtu.amit.@gmail.com

This work is licensed under a Creative Commons

Introducing Metaclasses in Python

By Amit Kumar

Introducing Metaclasses in Python

A talk on Metaclasses in Python.

  • 5,404