What are the metaclasses?

Asked

Viewed 1,149 times

11

Some modern programming languages such as Ruby implement the so-called "Metaclasses".

  • What is this?
  • What good is?
  • How and when to use?

2 answers

12


A metaclass is the class of a class.

What is a class? At first, we could create objects directly, without intermediaries: just define an object X, give him a set of properties A, B, C and a set of operations P, Q, R. Languages that don’t have the concept of "class" (like Javascript) do it this way:

// Construção direta
var x = new Object();
x.foo = "bar";
x.baz = function() { ... };

// Literal para objetos
var x = {
    foo:"bar",
    baz:function() { ... }
};

// Função de criação
var x = Object.create(null, {
    foo:"bar",
    baz:function() { ... }
});

// Construtor
function X() {
    this.foo = "bar";
    this.baz = function() { ... };
}
var x = new X();

However, we often want to create several "similar" objects. One option is to use a constructor function. Another is to create a special object for describe a set of characteristics that every object defined in this way must possess. This object is called class.

class X {
    private String foo = "bar";
    public void baz() { ... }
}

Object x = new X();

The advantages of classes are many. In addition to being a form declarative to express the structure of the object, it also constitutes a guy relating all objects created by it in a single set, and establishing subtype relations between it and other classes.

If the class is also an object[1], how do you create a class? In the same way that other objects are created: either via a literal/keyword of the language itself (more common), or via a constructor function, or by creating an instance of a metaclass.

# Literal
class X(object):
    def __init__(self):
        self.foo = "bar"
    def baz():
        ...

# Instância de metaclasse
def init(self):
    self.foo = "bar"
def baz():
    ...
X = type("X",       # Nome da classe
         (object,), # Herda de...
         {          # Atributos e operações
             "__init__":init,
             "baz":baz,
         })

# Função construtora
def constuirX():
    X = ... # Usa um dos métodos acima - ou uma lógica mais complexa - pra definir a classe
    return X

The first and second examples are equivalent. In this example (Python), type is the class of the class X, i.e. your metaclass. Every class created using the keyword class has by default the metaclass type, unless otherwise defined.

>>> x = X()
>>> x.__class__
<class '__main__.X'>
>>> x.__class__.__class__
<type 'type'>

What good is?

In general, when we want to create a class we are not very demanding: we ask that the objects created by it have a set of attributes of the same type, operations in common, and only. For this reason, the "default behavior" when creating classes is just making these definitions and that’s it. The metaclass default (type in Python, Class in Ruby[2]), so it does just that.

However, there are cases where one wants class set have a lot in common. For example, when creating a framework with a specific purpose, requiring every class to define the same things again and again (e.g., Django, which implements object-relational mapping through a BD table class). Without metaclasses, it would be necessary for the programmer to define all common functionality in each created class. With them, one can let the metaclass take care of the actual creation of the classes, and let the programmer focus only on the details that interest him.

class Funcionario(Model):
    nome = CharField(max_length=30)
    empresa = ForeignKey(Empresa)

fulano = Funcionario(nome="Fulano", empresa_id=10) # A metaclasse definiu o construtor pra nós

isinstance(fulano.nome, CharField) # False
isinstance(fulano.nome, unicode) # True

In the above example (which uses metaclasses indirectly, by inheriting from Model), what is being defined are not the attributes of the objects created by the class Funcionario, and yes rules to map simple attributes (eg.: nome is a string, no CharField) to BD columns. The structure of instances of Funcionario in themselves are inferred by the metaclass from the information presented.

fulano.nome.__class__       # unicode (string)
fulano.empresa.__class__    # Empresa (outro Model)
fulano.epresa_id.__class__  # int (o ID da empresa no BD)
fulano.id.__class__         # int (o ID do funcionário no BD)

How and when to use?

Very rarely. Practically never. Unless you’re working on a generic framework or library - intended to be used by other programmers, who will create their own classes in it - there’s hardly a situation where metaclasses are needed. Personally, I have never used them, and I have difficulty even imagining situations where they are necessary. So I suggest you ask yourself the following questions:

  • I’m creating many classes?
  • These classes have many things in common?
    • And yet is not an inheritance case/mixin?
  • Is this common part redundant? (i.e. can be inferred from the unusual part)
  • The computer could do this redundant job for me?

If the answer to these questions is "yes", it might be worth using metaclasses in your case. About how to use them:

  1. Imagine the "ideal class" (i.e. what you, as a programmer, would have to include in the class definition every time; leaving out all redundant);
  2. Establish the steps that would be necessary to turn this ideal class into the "real" class (i.e. the one the program needs to work - that defines the attributes and operations of the objects as they must actually have);
  3. Implement a metaclass that performs this transformation.
    1. Receive a class definition as a parameter;
    2. Interpret its structure (if the language supports metaclasses, this should be trivial);
    3. Add/remove/change your structure according to established logic.

Just so we’re not without an example, I’m going to propose an alternative to Rogers Corrêa’s answer: our "ideal class" should not have a constructor assigning each positional attribute to self (because it would be redundant - this information is already present in slots). But the real class needs it. Let’s then turn the ideal class into the real class while keeping everything else intact:

# Definindo uma metaclasse na forma de uma função
def minha_metaclass(nome, parents, atributos):
    # Acha os slots da classe
    slots = atributos['__slots__']

    # Cria um novo construtor que usa esses slots
    def init(self, *args):
        for i in range(len(slots)):
            setattr(self, slots[i], args[i])

    # Acrescenta esse construtor na definição da classe
    atributos['__init__'] = init

    # Cria a classe de fato (usando type)
    return type(nome, parents, atributos)

class Carro(object):
    __metaclass__ = minha_metaclass
    __slots__ = ['marca', 'modelo', 'ano', 'cor']

c = Carro('Toyota', 'Prius', 2005, 'green')
c.marca  # Toyota
c.modelo # Prius
c.ano    # 2005
c.cor    # green

Notes:

  1. Not every language considers classes as objects (i.e. they are not "first class members"), so it is not possible to create or modify them after compilation. However, the vast majority of modern languages have at the very least mechanisms to programmatically inspect the class structure and thus generously manipulate the objects created by them (reflection).

  2. In Ruby, every class is an instance of Class, while in Python the classes can be instances of any class that inherits from type. I have no experience with Ruby to comment on how metaclasses work in this language.

6

What is?

In object orientation, a metaclass is a class whose instances are also classes and not objects in the traditional sense. Just as classes define the behavior of certain objects, metaclasses define the behavior of certain classes and their instances.

Not every object-oriented language supports metaclasses. Among those that support, the extent of modifications that can be made in classes varies. Each language has its own metaobject protocol, a set of rules that define how objects, classes and metaclasses interact.

Python:

class Carro(object):
    __slots__ = ['marca', 'modelo', 'ano', 'cor']

    def __init__(self, marca, modelo, ano, cor):
        self.marca = marca
        self.modelo = modelo
        self.ano = ano
        self.cor = cor

    @property 
    def descricao(self):
        # Retorna uma descrição do carro.
        return "%s %s %s %s" % (self.cor, self.ano, self.marca, self.modelo)

The above example contains redundant code related to the four attributes brand, model, year and color. It is possible to delete part of the redundancy using a metaclass. In Python, a metaclass is defined as a subclass of type. Metaclass serves to prevent redundancies and increase performance of the algorithm execution.

class TipoIniciaAtributo(type):
    def __call__(self, *args, **kwargs):
        # Cria uma nova instância.

        # Primeiro, cria um objeto da forma padrão.
        obj = type.__call__(self, *args)

        # Adicionalmente, configura-se atributos do novo objeto.
        for nome in kwargs:
            setattr(obj, nome, kwargs[nome])

        # Retorna o novo objeto.
        return obj

This metaclass modifies the creation of the object. All other aspects of class and object behavior still follow what was defined by type. Now the Car class can be rewritten to use this metaclass above, which is done by assigning the new metaclass to metaclass.

class Carro(object):
    __metaclass__ = TipoIniciaAtributo
    __slots__ = ['marca', 'modelo', 'ano', 'cor']

    @property
    def descricao(self):
        # Retorna uma descrição do carro.
        return "%s %s %s %s" % (self.cor, self.ano, self.marca, self.modelo)

Car objects can then be instantiated through:

carros = [
    Carro(marca='Toyota', modelo='Prius', ano=2005, cor='green'),
    Carro(marca='Ford', modelo='Prefect', ano=1979, cor='blue'),
]

Source: http://en.wikipedia.org/wiki/Metaclass

Browser other questions tagged

You are not signed in. Login or sign up in order to post.