quinta-feira, outubro 30, 2008

Decorators com argumentos

Nos últimos tempos estive escrevendo uma API que utiliza decorators para registrar eventos em uma aplicação PyGTK/Glade. Nessa API basicamente eu tenho eventos registrados no glade e associados a um handler na aplicação (glade.sinal_connect('event', handler)) e eventos conectados diretamente na aplicação associados a um handler e um widget (widget.connect('event', handler)).

Então precisei de um decorator que em alguns momentos não receberia nenhum parâmetro, associando a função decorada a um sinal registrado no glade com o mesmo nome da função.

# Associa a função 'destroy' ao evento 'destroy' já registrado no glade

@handler
def destroy(self, *args):
print args

# Mesmo funcionamento

@handler()
def destroy(self, *args):
print args


Ou ainda, este mesmo decorator poderia receber argumentos detalhando qual o nome do evento e widget para quais a função decorada deve ser associada.

# Associa a função 'x' ao evento 'destroy' já registrado no glade

@handler('destroy')
def x(self, *args):
print args

# Associa a função y ao evento 'on_bt_ok_clicked' no widget bt_ok

@handler('on_bt_ok_clicked', bt_ok)
def y(self, *args):
print args


Quando nenhum argumento é informado (@decorator()) utilizo uma função aninhada para ser retornada como decorator e assim efetivamente decorar a função. O mesmo funcionamento se aplica quando os argumentos não são funções ou métodos (@decorator('x'), @decorator('x', a=1)), utilizando o módulo inspect . Esse comportamento só muda quando o decorator não recebe parâmetros, ou seja, quando este não é invocado, retornando a função ou método recebido sem nenhuma modificação.

def decorator(*args, **kargs):
import inspect
# @decorator() or @decorator('x') or @decorator('x', d='a')
if not args or not inspect.isroutine(args[0]):
def deco(func):
def wrapper(*f_args, **f_kargs):
return func(*f_args, **f_kargs)
return wrapper
return deco
elif inspect.isroutine(args[0]): # @decorator
return args[0]


Exemplos de uso:

@decorator
def d(x, y, d=1):
print x, y, d
#d = decorator(d)

@decorator()
def e(x, y, d=1):
print x, y, d
#e = decorator()(e)

@decorator('x')
def f(x, y, d=1):
print x, y, d
#f = decorator('x')(f)

@decorator('x', d=5)
def g(x, y, d=1):
print x, y, d
#f = decorator('x', d=5)(g)


Abaixo o mesmo decorator utilizando classe, assim o comportamento do decorator pode ser facilmente expandido para funcionar com métodos seguindo esse texto Decorators and Descriptors, fazendo o decorator ser ainda mais flexível podendo ser utilizado com funções ou métodos ao mesmo tempo:

class decorator(object):
def __init__(self, *args, **kargs):
import inspect
# @decorator() or @decorator('x') or @decorator('x', d='a')
if not args or not inspect.isroutine(args[0]):
def deco(func):
def wrapper(*f_args, **f_kargs):
return func(*f_args, **f_kargs)
return wrapper
self.func = deco
elif inspect.isroutine(args[0]): # @decorator
self.func = args[0]

def __call__(self, *args, **kargs):
return self.func(*args, **kargs)

def __get__(self, obj, type=None):
if obj is None:
return self
new_func = self.func.__get__(obj, type)
return self.__class__(new_func)


Exemplos de uso:

class A:
@decorator
def b(self, x, y):
print x, y

a = A()
a.b(1, 2)

@decorator
def b(x, y):
print x, y

b(2, 3)

2 comentários:

Anônimo disse...

Essa coisa toda não está ficando muito "javanesca" não? Meu encanto pelo Python é proveniente, justamente, da limpeza do código (zen) Python e da minha frustração com aquela parafernalha verborrágica do Java.

Eu ainda curto Java e tenho sistemas escritos em Java, mas tudo o que posso (novo) estou fazendo em Python+GTK.

Dia desses estava ficando meio chateado com a sintaxe do binding PyGTK para acessar os widgets. Resolvi o problema escrevendo uma classe base que lê os widgets do arquivo glade e todo nome de componente que não é padrão (i.e. que não seja "button1" ou similar) ele associa à um atributo que é criado dinâmicamente na classe. Assim não tenho mais que me preocupar com o tal self.ui.get_widget('palavrao_enorme').

Talvez seja possível fazer com que os handlers sejam criados dinâmicamente seguindo o mesmo princípio. Ou não?

Anônimo disse...

Re> Daniel...

Decorators em Python estão muito além de um simples "pattern" como em Java.

Decorators+funções de introspeção são muito poderosas. Os melhores frameworks escritos em Python utilizam muito desse tipo de implementação, tente fazer algo parecido em Java e você vai saber que essa implementação não pode ser chamada de "javanesca".

Claro que o código é meio complicado, mas afinal são pequenos "hacks" para melhorar ainda mais o poder da linguagem.

A sua sugestão de implementação é legal, e pode ser facilmente implementada com "__getattr__". Mas na minha lib eu queria uma API de uso menos intrusivo/automático.

Sobre criar os handlers dinamicamente, desconheço alguma maneira de pegar todos os sinais da "libglade", mas deve haver um maneira.