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
  1. No trackbacks yet.

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: