Metaprogramação
Certa vez conversando com o Fernando, um colega meu da faculdade, ele me disse que deveria haver um paradigma de programação mais avançado do que orientação a objetos. Então comentei com ele sobre orientação a eventos e metaprogramação – esqueci do paradigma funcional, ó que vergonha!
Resolvi então falar sobre metaprogramação.
Vamos compreender o que significa este meta: o que é metalinguística? É o estudo liguístico da própria linguística! Por exemplo, um dicionário é um livro metalinguístico – a linguagem usada para falar da linguagem. =)
Entendendo o uso do prefixo (na verdade um radical), vamos agora entender o que é metaprogramação.
Há dois conceitos distintos mas relacionados.
O primeiro conceito é o de metacódigo: é o código que gera como resultado outro código. Neste caso, metaprogramação é a criação de metacódigo.
Vou dar um exemplo em Shell script:
#!/bin/bash
# criaget.sh
ARQ="get.sh"
echo -n "Informe o mirror: "
read MIRROR
echo -n "Informe o caminho para o arquivo: "
read CAMINHO
if [[ "${MIRROR:0:4}" != "http" && "${MIRROR:0:3}" != "ftp" ]]
then
MIRROR="http://$MIRROR"
fi
if [[ "${MIRROR%/}" = "${$MIRROR}" ]]
then
$MIRROR="${MIRROR}/"
fi
cat > $ARQ <<-EOF
#!/bin/bash
wget -c ${MIRROR}${CAMINHO}.md5
wget -c ${MIRROR}${CAMINHO}
cat *.md5 | md5sum --check
EOF
chmod +x $ARQ
exit
Este programa irá pedir um mirror e um arquivo neste mirror, e irá gerar um código (
get.sh
) que, quando executado, tentará baixar o arquivo informado, fazendo uma verificação de MD5
.Não é um conceito difícil, mas não é desse tipo de metaprogramação que quero falar. =P
Outro conceito de metaprogramação é quando elementos que geralmente são definidos na codificação, como funções e classes, são definidos em tempo de execução por outro elemento semelhante.
Ou seja: funções que retornam funções e classes cujas instâncias são classes.
Vamos a um exemplo em Python:
def inteiro(f):
def func(*args, **kw):
resp = f(*args, **kw)
return int(resp + .5)
return func
Olha só: esta função recebe uma função como argumento e retorna outra função. Por exemplo:
>>> def pre_fib(n):
n += 1
return (
pow(
(1. + pow(5., .5)) / 2.,
n
) - pow(
(1. - pow(5., .5)) / 2.,
n
)
) / pow(5., .5)
>>> fib = inteiro(pre_fib)
>>> [fib(x) for xrange(10)]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Esta é a fórmula fechada de Fibonacci, no entanto, como trabalha com números de ponto flutuante, os valores trazem erros de aproximação que precisam ser solucionados por conversão para inteiros.
A função
inteiro()
pega a função pre_fib()
como parâmetro e retorna uma função que faz a mesma coisa que a original, mas traduzindo a resposta para inteiro. Portanto inteiro()
é uma metafunção.Python traz ainda um açúcar sintático chamado «decorador», que reduz a quantidade de código ao programar com metafunções. Por exemplo, a função acima poderia ser reescrita usando
inteiro()
como decorador:@inteiro
def fib(n):
n += 1
return (
pow(
(1. + pow(5., .5)) / 2.,
n
) - pow(
(1. - pow(5., .5)) / 2.,
n
)
) / pow(5., .5)
Observação: classes podem ser usadas também como decoradores, desde que suas instâncias sejam «executáveis» (callable) – futuramente escreverei um artigo sobre memoization, então vou falar um pouco mais sobre isso.
Vamos agora falar de «metaclasses».
Metaclasse é uma classe que retorna como instância outra classe – é um recurso para criação de classes em tempo de execução (e/ou para salvar horas de codificação). =)
Em Python, metaclasses devem ser filhas (herdeiras) de
type
e o construtor deve receber como argumentos cls
(referência à própria instância), name
, bases
e dict
, assim como a função/classe type()
, que cria tipos/classes dinamicamente.Vamos ao exemplo proposto pelo próprio BDFL:
class autoprop(type):
def __init__(cls, name, bases, dict):
super(autoprop, cls).__init__(name, bases, dict)
props = {}
for name in dict.keys():
if len(name) > 5:
if name.startswith("_get_") \
or name.startswith("_set_") \
or name.startswith("_del_"):
props[name[5:]] = 1
for name in props.keys():
fget = getattr(cls, "_get_" + name, None)
fset = getattr(cls, "_set_" + name, None)
fdel = getattr(cls, "_del_" + name, None)
setattr(cls, name, property(fget, fset, fdel))
Esta metaclasse cria propriedades (atributos que chamam métodos) automaticamente em suas instâncias. Um exemplo:
class X:
__metaclass__ = autoprop
def __init__(self, x=None):
self.__x = 0
if x is not None:
self._set_x(x)
def _get_x(self):
return self.__x
def _set_x(self, v):
v = int(v + .5)
if v >= 0:
self.__x = v
def _del_x(self):
raise TypeError, "Nao eh possivel remover x"
Esta classe é uma instância de
autoprop
(não filha). Veja o que acontece com suas próprias instâncias:>>> a = X()
>>> a.x
0
>>> a.x = 2.1
>>> a.x
2
>>> a.x = -4
>>> a.x
2
>>> del a.x
> del a.x
Traceback (most recent call last):
File "<pyshell#37>", line 1, in -toplevel-
del a.x
File "<pyshell#30>", line 14, in _del_x
raise TypeError, "Nao eh possivel remover x"
TypeError: Nao eh possivel remover x
>>> a.x
2
A propriedade
x
interceptou todas as chamadas a a.x
e chamou os métodos convenientes (_get_x()
, _set_x()
e _del_x()
). Para isso, a propriedade x
precisou ser definida antes pela função property()
. Onde esta função foi executada? Ela não existem na definição da classe X
...Exatamente: ela foi executada no construtor de
autoprop
, atribuindo assim a propriedade x
– criada dinamicamente – a sua instância X
.Se alguém ficou em dúvida, comente este artigo e responderei!
PS: Walter, que tal escrever um artigo sobre metatabelas em Lua?