Posts Tagged ‘ metaclass ’

Criador de Funções com Metaclasses

Esse é um projeto que fiz meses atrás para gerar funções em Python com operadores matemáticos usando metaclasses.
Com isso, você pode fazer:

g = f ** 2 + 1   #função que eleva ao quadrado e soma 1
g(10) == 101

O código abaixo é uma versão (muito) simplificada do módulo que coloquei no GitHub, mas serve como exemplo de uso de metaclasses em Python (neste caso, Python 3):

import operator

class MetaFuncBuilder(type):
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        attr = '__{0}{1}__'

        for op in (x for x in dir(operator) if not x.startswith('__')):
            oper = getattr(operator, op)
            op = op.rstrip('_') #special case for keywords: and_, or_

            def func(self, *n, oper=oper):
                return type(self)(lambda x: oper(self.func(x), *n),
                                  self.op + [(oper.__name__, n[0])
                                             if n else oper.__name__])
            def rfunc(self, n, *, oper=oper):
                return type(self)(lambda x: oper(n, self.func(x)),
                                  self.op + [(n, oper.__name__)])
           
            setattr(self, attr.format('', op), func)
            setattr(self, attr.format('r', op), rfunc)

class FuncBuilder(metaclass=MetaFuncBuilder):
    def __init__(self, func=None, op=None):
        self.op = op if op else []
        self.func = func if func else lambda x: x

    def __repr__(self):
        return '<var %s>' % self.op

    def __call__(self, *args):
        if not args:
            return self.func() #unary operators
        required, *args = args
        out = self.func(required)
        return out(*args) if args else out

f = FuncBuilder()

Em duas situações é comum o uso de metaclasses em Python: para que um certa classe tenha propriedades novas ou para automatizar algum mecanismo nas classes. Neste caso, usei para automatizar a criação de métodos para cada operador em Python, com ajuda do módulo operator.

Em Python, os operadores matemáticos binários existem nas formas __op__, __rop__ e __iop__, onde op é o nome, r significa que o objeto é o segundo operador e i significa in-place. Outros operadores existem em apenas uma forma.
Assim, o código pega os operadores e cria as três formas de cada um (os que existem em apenas uma forma ficam com 2 formas extras que não causam problema algum, já que não são nunca chamadas) e todos os operadores, ao invés de executar a operação designada, apenas criam uma novo objeto-função que pode ser chamado ou ter novas funções adicionadas.

O módulo no GitHub tem uma centena de outras funcionalidades para gerar essas funções, mas ele sofre de um problema que pode ser bem sério: Chamadas de função em Python são caras!

Basicamente, cada objeto do tipo FuncBuilder guarda a operação que tem que executar e o próximo da lista. Isso gera um monte de chamadas de função em cima dos operadores, o que pode deixar o código muito mais lento.

Minha próxima alteração pra esse código seria juntar todas as operações numa só chamada de função, o que não vai permitir extrair uma função de dentro de outra (como é possível agora por meio de closures), mas vai reduzir o custo para apenas uma chamada de função, como seria natural. Isso não vai ser tão simples por causa dessa estrutura recursiva da classe, mas quando tiver tempo, eu faço.

Anúncios