Metaclasses

adapted from work by Joseph Sheedy

A class is just an object

A class is a first-class object:

Can be created at runtime

Passed as a parameter

Returned from a function

Assigned to a variable

Example

>>> def create_a_class(**kw):
...    return type('CoolClass', (object,), dict(**kw))
...
>>> cool_class = create_a_class(foo='nice', bar='sweet')
>>> cool_class
<class '__main__.CoolClass'>
>>> cool_object = cool_class()
>>> cool_object
<__main__.CoolClass object at 0x10224e208>
>>> cool_object.foo
'nice'
>>> cool_object.bar
'sweet'

Equivalent to:

class CoolClass(object):
   foo = 'nice'
   bar = 'sweet'

But it was created at runtime, returned from a function and assigned to a variable.

http://eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example

More on Classes

Objects get created from classes. So what is the class of a class?

The class of Class is a metaclass

The metaclass can be used to dynamically create a class

The metaclass, being a class, also has a metaclass

What is a metaclass?

  • A class is something that makes instances
  • A metaclass is something that makes classes
  • A metaclass is most commonly used as a class factory
  • Metaclasses allow you to do ‘extra things’ when creating a class, like registering the new class with some registry, adding methods dynamically, or even replace the class with something else entirely
  • Every object in Python has a metaclass
  • The default metaclass is type()

type()

With one argument, type() returns the type of the argument

With 3 arguments, type() returns a new class

type?
Type:       type
String Form: <type 'type'>
Namespace:  Python builtin
Docstring:
type(object) -> the object's type
type(name, bases, dict) -> a new type

name: string name of the class
bases: tuple of the parent classes
dict: dict containing attribute names and values

using type() to build a class

The class keyword is syntactic sugar, we can get by without it by using type

class MyClass(object):
    x = 1

or

MyClass = type('MyClass', (), {'x': 1})

(object is automatically a superclass)

Adding methods to a class built with type()

Just define a function with the correct signature and add it to the attr dictionary

def my_method(self):
    print("called my_method, x = %s" % self.x)

MyClass = type('MyClass',(), {'x': 1, 'my_method': my_method})
o = MyClass()
o.my_method()

MyClass = type(name, bases, dct)

  • name: name of newly created class
  • bases: tuple of class’s base classes
  • dct: class attribute mapping

What type is type?

In [30]: type(type)
Out[30]: type

metaclass

Setting a class’ metaclass:

class Foo(metaclass=MyMetaClass):
    pass

the class assigned to the metaclass keyword argument will be used to create the object class Foo.

If the metaclass kwarg is not defined, it will use type to create the class.

Whatever is assigned to metaclass should be a callable with the same signature as type()

Python2 NOTE:

In Python 2, instead of the keyword argument, a special class attribute: __metaclass__ is used:

class Foo(object):
  __metaclass__ = MyMetaClass

Why use metaclasses?

Useful when creating an API or framework

Whenever you need to manage object creation for one or more classes

For example, see Examples/metclasses/singleton.py

Or consider the Django ORM:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

person = Person(name='bob', age=35)
print person.name

When the Person class is created, it is dynamically modified to integrate with the database configured backend. Thus, different configurations will lead to different class definitions. This is abstracted from the user of the Model class.

Here is the Django Model metaclass:

https://github.com/django/django/blob/master/django/db/models/base.py#L77

__new__ vs __init__ in Metaclasses

__new__ is used when you want to control the creation of the class (object)

__init__ is used when you want to control the initiation of the class (object)

__new__ and __init__ are both called when the module containing the class is imported for the first time.

__call__ is used when you want to control how a class (object) is called (instantiation)

class CoolMeta(type):
    def __new__(meta, name, bases, dct):
        print('Creating class', name)
        return super(CoolMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
  print('Initializing class', name)
  super(CoolMeta, cls).__init__(name, bases, dct)
    def __call__(cls, *args, **kw):
        print('Meta has been called')
  return type(cls, *args, **kw)

class CoolClass(metaclass=CoolMeta):
    def __init__(self):
        print('And now my CoolClass exists')

print('Actually instantiating now')
foo = CoolClass()

Metaclass example

Consider wanting a metaclass which mangles all attribute names to provide uppercase and lower case attributes

class Foo(metaclass=NameMangler):
    x = 1

f = Foo()
print(f.X)
print(f.x)

NameMangler

class NameMangler(type):

    def __new__(cls, clsname, bases, _dict):
        uppercase_attr = {}
        for name, val in _dict.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
                uppercase_attr[name] = val
            else:
                uppercase_attr[name] = val

        return super().__new__(cls, clsname, bases, uppercase_attr)


class Foo(metaclass=NameMangler):
    x = 1

Exercise: Working with NameMangler

In the repository, find and run Examples/metaclasses/mangler.py

Modify the NameMangler metaclass such that setting an attribute f.x also sets f.xx

Now create a new metaclass, MangledSingleton, composed of the NameMangler and Singleton classes in the Examples/metaclasses directory.

Assign it to the metaclass keyword argument of a new class and verify that it works.

Your code should look like this:

class MyClass(metaclass=MangledSingleton) # define this
    x = 1

o1 = MyClass()
o2 = MyClass()
print(o1.X)
assert id(o1) == id(o2)

The Singleton

One common use of metaclasses is to create a singleton. There is an example of this called singleton.py in the Examples directory. However, metaclasses are not the only way to create a singleton. It really depends on what you are trying to do with your singleton.

http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Singleton.html

http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python