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 :

  1. class A(object):
  2. def __init__(self):
  3. print 'init A'
  4.  
  5. class B(A):
  6. def __init__(self):
  7. print 'init B'
  8. super(B, self).__init__()
  9. print 'fin init B'
  10.  
  11. 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…) :

  1. def decorate_onload(f):
  2. def my_init(*args, **kwargs):
  3. inst = args[0]
  4. if hasattr(inst, '__decorate_onload__'):
  5. return f(*args, **kwargs)
  6. inst.__setattr__('__decorate_onload__', True)
  7. f(*args, **kwargs)
  8. print 'launch on_load!'
  9. return my_init
  10.  
  11. class A(object):
  12. @decorate_onload
  13. def __init__(self):
  14. print 'init A'
  15.  
  16. class B(A):
  17. @decorate_onload
  18. def __init__(self):
  19. print 'init B'
  20. super(B, self).__init__()
  21. print 'fin init B'
  22.  
  23. class C(B):
  24. @decorate_onload
  25. def __init__(self):
  26. print 'init C'
  27. super(C, self).__init__()
  28. print 'fin init C'
  29.  
  30. 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 :

  1. def decorate_onload(f):
  2. def my_init(*args, **kwargs):
  3. 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

  1. if hasattr(inst, '__decorate_onload__'):
  2. 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.

  1. 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.

  1. f(*args, **kwargs)
  2. print 'launch on_load!'
  3. 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.