2 votes

Enregistrement automatique d'une classe lorsqu'elle est définie (mais sans l'importer nulle part)

Je voudrais enregistrer une classe (pas une instance) lors de sa création... mais sans l'importer.

En gros, je veux faire ce qui est décrit ici :

Comment enregistrer automatiquement une classe lorsqu'elle est définie ?

... mais sans avoir à importer la classe enregistrée nulle part.

Voici le problème : À un certain moment de mon application, j'ai besoin d'exécuter certaines commandes système (restons-en à reboot, halt... pour l'exemple). J'ai créé une classe "de base" (abstraite-mmnnno-ne ressemblant pas vraiment à ça) "Command" et un "CommandManager" qui recevra une chaîne de caractères avec la commande à exécuter, créera l'instance appropriée pour cette commande, la mettra en file d'attente et distribuera les commandes quand cela sera nécessaire (ou quand il le pourra). Toutes les instances de la classe "Command" écraseront une méthode "execute" qui exécute réellement la commande. Si je veux "redémarrer", j'étendrai la classe "Command" à une classe "RebootCommand", et j'écraserai la méthode "execute" qui appelle l'application externe "shutdown -r" ou "reboot".

Le but final est d'être capable de faire quelque chose comme :

CommandManager.execute("reboot")

Et le système va redémarrer.

Pour ce faire, le " redémarrer "doit être enregistrée dans un dictionnaire à l'intérieur du CommandManager. Ce dictionnaire aura " redémarrer "(chaîne de caractères) comme clé et la classe Commande de redémarrage comme valeur. Le CommandManager recevra la chaîne " redémarrer La commande "RebootCommand" va aller dans son "commandDictionary", créer une instance de la classe RebootCommand et appeler sa méthode execute() (ce qui permet de redémarrer le système).

Quelque chose comme :

#------------- CommandManager.py -------------
@classmethod
def execute(cls, parameter):
    if parameter in cls.validCommands:
        tmpCommand = cls.validCommands[parameter]()
        tmpCommand.execute()
#---------------------------------------------

Pour effectuer l'enregistrement, l'objet de classe RebootCommand doit être créé, et pour ce faire, je dois l'importer quelque part. Mais je ne veux pas le faire. Je n'en ai pas besoin. J'ai juste besoin que l'objet de classe RebootCommand soit créé, afin qu'il exécute la commande __new__ pour la MetaClass Command, où l'enregistrement a lieu (comme indiqué dans la réponse à l'autre question ci-dessus).

Y a-t-il un moyen de le faire ? J'ai essayé d'importer les commandes réelles (les classes qui héritent de Command) dans le répertoire de l'utilisateur. __init__.py mais ensuite, si je crée une nouvelle commande, je dois l'ajouter à la liste des commandes. __init__.py et j'aimerais éviter cela (afin qu'un autre programmeur puisse créer une nouvelle commande sans se soucier d'oublier d'ajouter la classe à la classe __init__.py )

Voici la structure de mon répertoire :

commands/
    __init__.py
    CommandManager.py
    Command.py
    RebootCommand.py

Et ici, les contenus de Command et RebootCommand qui font l'enregistrement :

#--------------- Command.py -------------------
def register(newCommand):
    if newCommand and newCommand._command:
        import CommandManager
        CommandManager.CommandManager.registerCommand(newCommand._command, newCommand)

# Fancy registering! https://stackoverflow.com/questions/5189232/how-to-auto-register-a-class-when-its-defined
class CommandMetaClass(type):
    def __new__(cls, clsname, bases, attrs):
        newCommand = super(cls, CommandMetaClass).__new__(cls, clsname, bases, attrs)
        register(newCommand)  # here is your register function
        return newCommand

class Command(object):
    __metaclass__ = CommandMetaClass
    _command = None
#-----------------------------------------------------

et...

#--------------- RebootCommand.py -------------------
class RebootCommand(Command.Command):
    _command = "reboot"
#---------------------------------------------------

Le processus d'enregistrement est correctement exécuté si j'ouvre __init__.py et j'écris :

import RebootCommand

(à ce stade, l'objet de classe RebootCommand est créé,

CommandMetaClass.__new__

est exécutée, "reboot" est enregistré comme la classe RebootCommand, et tout le monde est content)

mais j'aimerais éviter d'avoir à le faire (en touchant __init__.py ), si possible

J'ai essayé d'importer avec le caractère générique * sans succès.

Pour résumer, j'aimerais qu'un autre programmeur qui souhaite implémenter une nouvelle commande, n'ait qu'à créer une nouvelle classe héritant de Command, mais sans avoir à l'ajouter à la liste des classes. __init__.py

Je sais qu'il n'y a rien qui ne puisse être corrigé avec un commentaire dans le fichier Command.py, disant quelque chose comme "toute classe héritant de celle-ci doit être ajoutée dans le fichier __init__.py "mais je suis curieux de savoir si je peux le faire d'une manière plus "élégante".

2voto

Niklas B. Points 40619

Je pense que votre approche est un peu compliquée pour mettre en œuvre la fonctionnalité que vous souhaitez. Je vous suggère d'utiliser une stratégie comme celle-ci :

  1. Nommez vos modules de manière à ce que les classes de commandes soient définies dans des fichiers portant le nom des commandes qu'elles implémentent. Par exemple, le module RebootCommand va dans le fichier commands/reboot.py .

  2. Chaque module de commande définit une variable de premier niveau command_cls qui contient une référence à la classe implémentant la commande. commands/reboot.py contiendrait donc un code comme celui-ci :

    class RebootCommand:
      def execute(self):
        # [...]
    
    command_cls = RebootCommand
  3. Pour exécuter une commande, vous utilisez la commande __import__ pour charger dynamiquement votre module. Votre CommandManager.py se trouverait logiquement dans un fichier en dehors du commands (qui est réservé aux modules de commande) et serait implémenté comme suit :

    class CommandManager:
      @classmethod
      def execute(cls, command, *args, **kw):
        # import the command module
        commands_mod = __import__("commands", globals(), locals(), [command])
        mod = getattr(commands_mod, command)
        return mod.command_cls(*args, **kw).execute()

EDIT : Il semble que j'ai confondu l'utilisation de la imp module. Mise à jour du code pour utiliser __import__ .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X