Bonjour,
Je vais essayer d'expliciter au mieux mon problème, mais c'est pas gagné
Je travaille sur un projet où je cherche à dissocier l'interface graphique du cœur. En fait, pour chaque classe qui constituerait un composant du programme, j'aimerais pouvoir greffer une interface de façon facultative et transparente.
Disons que je dispose d'une telle classe :
1 2 3 | class Menu: def __init__(self, *choices): self.choices = choices |
La classe est utilisable en l'état. Mais si on imagine que j'ai chargé un autre module mettant en place une interface graphique, la classe Menu
devra alors être spécialisée pour cette interface (placement des éléments, etc.) en une classe GUIMenu
. Ça c'est pour le côté facultatif.
Au niveau de la transparence, le module contenant Menu
pourrait contenir d'autres classes qui référencent Menu
. Je ne voudrais cependant pas accéder à la classe Menu
de base, mais à GUIMenu
, sans avoir à en changer le nom.
J'ai trouvé deux solutions qui s'offraient à moi, que je présente ci-dessous.
Décorateur
La première serait l'utilisation d'un décorateur autour de chaque classe pouvant être spécialisée par l'interface, par exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> class GUIMenu: ... foo = 1 ... >>> def gui(cls): ... return type(cls.__name__, (GUIMenu, cls), {}) ... >>> @gui ... class Menu: pass ... >>> Menu.foo 1 >>> Menu.mro() [<class '__main__.Menu'>, <class '__main__.GUIMenu'>, <class '__main__.Menu'>, <class 'object'>] |
Ça fonctionne correctement, la transparence est totale. Seul problème, ça nécessite de décorer toutes les classes actuelles et futures, je trouve ça un peu lourd.
Je n'ai rien trouvé permettant de décorer automatiquement toutes les classes d'un module. Les seules solutions que j'ai lues interviennent après le chargement du module, et non après la création de chaque classe, ce qui fait qu'une classe référencée dans le module pointera vers sa version non décorée.
Métaclasse
Cette fois-ci, je sors l'artillerie lourde. Plutôt que de décorer ma classe, je lui applique une métaclasse qui créera d'elle-même la classe spécialisée. Ainsi, ma classe originale ne sera jamais référéncée dans le module.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | >>> class GUIMenu: ... foo = 1 ... >>> class Meta(type): ... def __new__(cls, name, bases, dict): ... c = super().__new__(cls, name, bases, dict) ... if issubclass(c, GUIMenu): ... return c ... return type(name, (GUIMenu, c), {}) ... >>> class Menu(metaclass=Meta): pass ... >>> Menu.foo 1 >>> Menu.mro() [<class '__main__.Menu'>, <class '__main__.GUIMenu'>, <class '__main__.Menu'>, <class 'object'>] |
Cette solution me plaisait davantage, car toutes mes classes héritant déjà d'une classe commune, je peux aisément régler la métaclasse de tous mes composants.
Seulement, j'ai rencontré plus tard un aspect plus fourbe. Contrairement au décorateur, puisque la métaclasse intervient pendant la création, à l'intérieur même de celle-ci, __class__
ne référence pas la classe d'origine mais la nouvelle classe, ce qui m'empêche d'y utiliser super
convenablement.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> class GUIMenu: ... def __init__(self): ... super().__init__() ... >>> class Menu(metaclass=Meta): ... def __init__(self): ... super().__init__() ... >>> Menu() # BOUM Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in __init__ [...] File "<stdin>", line 3, in __init__ RecursionError: maximum recursion depth exceeded while calling a Python object |
Bref, c'est assez gênant de devoir utiliser chaque fois explicitement le nom de la classe parente, et ça peut surtout être cause de nombreux bugs que je n'ai aucune envie de rencontrer.
Donc ?
Maintenant que mon/mes soucis sont présentés, j'en viens à ma question, à savoir quelle méthode préférer ? Et surtout, s'il existait une solution pour réunir les avantages de l'une et de l'autre :
- Ne pas trop alourdir la syntaxe de définition des classes ;
- Ne pas exploser à cause de
super
.
J'aimerais bien un décorateur qui s'applique à toutes les classes d'un module, ou une métaclasse se comportant comme un décorateur, mais je doute que ça existe.
Vos avis ?
Merci d'avance.