Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Metaclass : Understanding the 'with_metaclass()'

I want to ask what the with_metaclass() call means in the definition of a class.

E.g.:

class Foo(with_metaclass(Cls1, Cls2)): 
  • Is it a special case where a class inherits from a metaclass?
  • Is the new class a metaclass, too?
like image 918
Zakos Avatar asked Aug 29 '13 14:08

Zakos


People also ask

What is __ metaclass __ in Python?

A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes.

How do you use metaclass in Python?

To create your own metaclass in Python you really just want to subclass type . A metaclass is most commonly used as a class-factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass.

What is the concept of a metaclass?

In object-oriented programming, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances. Not all object-oriented programming languages support metaclasses.

How do you set the metaclass of class A to B?

In order to set metaclass of a class, we use the __metaclass__ attribute. Metaclasses are used at the time the class is defined, so setting it explicitly after the class definition has no effect.


1 Answers

with_metaclass() is a utility class factory function provided by the six library to make it easier to develop code for both Python 2 and 3.

It uses a little sleight of hand (see below) with a temporary metaclass, to attach a metaclass to a regular class in a way that's cross-compatible with both Python 2 and Python 3.

Quoting from the documentation:

Create a new class with base class base and metaclass metaclass. This is designed to be used in class declarations like this:

from six import with_metaclass     class Meta(type):     pass  class Base(object):     pass  class MyClass(with_metaclass(Meta, Base)):     pass 

This is needed because the syntax to attach a metaclass changed between Python 2 and 3:

Python 2:

class MyClass(object):     __metaclass__ = Meta 

Python 3:

class MyClass(metaclass=Meta):     pass 

The with_metaclass() function makes use of the fact that metaclasses are a) inherited by subclasses, and b) a metaclass can be used to generate new classes and c) when you subclass from a base class with a metaclass, creating the actual subclass object is delegated to the metaclass. It effectively creates a new, temporary base class with a temporary metaclass metaclass that, when used to create the subclass swaps out the temporary base class and metaclass combo with the metaclass of your choice:

def with_metaclass(meta, *bases):     """Create a base class with a metaclass."""     # This requires a bit of explanation: the basic idea is to make a dummy     # metaclass for one level of class instantiation that replaces itself with     # the actual metaclass.     class metaclass(type):          def __new__(cls, name, this_bases, d):             return meta(name, bases, d)          @classmethod         def __prepare__(cls, name, this_bases):             return meta.__prepare__(name, bases)     return type.__new__(metaclass, 'temporary_class', (), {}) 

Breaking the above down:

  • type.__new__(metaclass, 'temporary_class', (), {}) uses the metaclass metaclass to create a new class object named temporary_class that is entirely empty otherwise. type.__new__(metaclass, ...) is used instead of metaclass(...) to avoid using the special metaclass.__new__() implementation that is needed for the slight of hand in a next step to work.
  • In Python 3 only, when temporary_class is used as a base class, Python first calls metaclass.__prepare__() (passing in the derived class name, (temporary_class,) as the this_bases argument. The intended metaclass meta is then used to call meta.__prepare__(), ignoring this_bases and passing in the bases argument.
  • next, after using the return value of metaclass.__prepare__() as the base namespace for the class attributes (or just using a plain dictionary when on Python 2), Python calls metaclass.__new__() to create the actual class. This is again passed (temporary_class,) as the this_bases tuple, but the code above ignores this and uses bases instead, calling on meta(name, bases, d) to create the new derived class.

As a result, using with_metaclass() gives you a new class object with no additional base classes:

>>> class FooMeta(type): pass ... >>> with_metaclass(FooMeta)  # returns a temporary_class object <class '__main__.temporary_class'> >>> type(with_metaclass(FooMeta))  # which has a custom metaclass <class '__main__.metaclass'> >>> class Foo(with_metaclass(FooMeta)): pass ... >>> Foo.__mro__  # no extra base classes (<class '__main__.Foo'>, <type 'object'>) >>> type(Foo) # correct metaclass <class '__main__.FooMeta'> 
like image 167
Martijn Pieters Avatar answered Sep 21 '22 17:09

Martijn Pieters