11
Some modern programming languages such as Ruby implement the so-called "Metaclasses".
- What is this?
- What good is?
- How and when to use?
11
Some modern programming languages such as Ruby implement the so-called "Metaclasses".
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'>
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)
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:
If the answer to these questions is "yes", it might be worth using metaclasses in your case. About how to use them:
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:
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).
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'),
]
Browser other questions tagged oop metaclass
You are not signed in. Login or sign up in order to post.