Python et les decorateurs avec __init__

Question: comment faire pour générer un évenement de chargement après l’initialisation d’une classe, de manière générique ?

Sur cette simple question, prenons un example :

class A(object):
 def __init__(self):
 print 'init A'
 
class B(A):
 def __init__(self):
 print 'init B'
 super(B, self).__init__()
 print 'fin init B'
 
B()

Le programme affiche successivement :

init B
init A
fin init B

Le but est d’exécuter une fonction, à la fin de init B. Pour cela, on peut faire appel au décorateur python. Voilà de quoi j’ai rêvé cette nuit (mmh…) :

def decorate_onload(f):
 def my_init(*args, **kwargs):
		inst = args[0]
 if hasattr(inst, '__decorate_onload__'):
 return f(*args, **kwargs)
		inst.__setattr__('__decorate_onload__', True)
		f(*args, **kwargs)
 print 'launch on_load!'
 return my_init
 
class A(object):
	@decorate_onload
 def __init__(self):
 print 'init A'
 
class B(A):
	@decorate_onload
 def __init__(self):
 print 'init B'
 super(B, self).__init__()
 print 'fin init B'
 
class C(B):
	@decorate_onload
 def __init__(self):
 print 'init C'
 super(C, self).__init__()
 print 'fin init C'
 
C()

Et ce code magique affiche… :

init C
init B
init A
fin init B
fin init C
launch on_load!

Le print ‘on_load’, est bien appellé seulement quand l’intégralité du widget a terminé de charger ! Comment ca marche ? C’est tout simple :

def decorate_onload(f):
 def my_init(*args, **kwargs):
		inst = args[0]

Ici, on définit un nouveau décorateur, et on récupère l’instance de la classe en cours d’initialisation

if hasattr(inst, '__decorate_onload__'):
 return f(*args, **kwargs)

L’instance de la classe est unique. Si la fonction de chargement est déjà hooker par un init, on ne fait rien.

inst.__setattr__('__decorate_onload__', True)

Sinon, on indique que l’on gère le onload. Cela sera valable au tout premier appel du décorateur sur l’instance => il sera toujours executé par le init de plus haut niveau.

f(*args, **kwargs)
 print 'launch on_load!'
 return my_init

On exécute le init normalement, et on affiche ‘on_load’. C’est ici que l’on peut remplacer par un appel de fonction sur l’instance de l’object.

Simple, encore fallait-il le trouver.