2011-01-24 11 views
10

Estoy escribiendo una aplicación. Sin interfaz gráfica de usuario sofisticada: s o algo, simplemente una aplicación de consola vieja y sencilla. Esta aplicación, vamos a llamarla Aplicación, necesita poder cargar complementos al inicio. Así que, naturalmente, he creado una clase para los plugins para heredar de:Importación dinámica de módulos seguida de creación de instancias de objetos con cierta clase base de dichos módulos

class PluginBase(object): 
    def on_load(self): 
     pass 
    def on_unload(self): 
     pass 
    def do_work(self, data): 
     pass 

La idea es que en el arranque, la aplicación sería caminar por el directorio actual, incluyendo subdirectorios, en busca de módulos que contiene las clases que ellos mismos son subclases de PluginBase.

Más código:

class PluginLoader(object): 
    def __init__(self, path, cls): 
     """ path=path to search (unused atm), cls=baseclass """ 
     self.path=path 
    def search(self): 
     for root, dirs, files in os.walk('.'): 
      candidates = [fname for fname in files if fname.endswith('.py') \ 
            and not fname.startswith('__')] 
     ## this only works if the modules happen to be in the current working dir 
     ## that is not important now, i'll fix that later 
     if candidates: 
      basename = os.path.split(os.getcwd())[1] 
      for c in candidates: 
       modname = os.path.splitext(c)[0] 
       modname = '{0}.{1}'.format(basename, modname) 
       __import__(mod) 
       module = sys.modules[mod] 

Después de la última línea en search me gustaría de alguna manera a) encontrar todas las clases en el módulo recién cargada, b) comprobar si una o más de esas clases son subclases de PluginBase yc) (si b) crea una instancia de esas/esas clases y agrega a la lista de módulos cargados de la aplicación.

He intentado varias combinaciones de issubclass y otros, seguidas por un período de intensa dir: ing y una hora de google en pánico. Encontré un enfoque similar al mío here e intenté simplemente copiar y pegar eso, pero recibí un error al decir que Python no admite las importaciones por nombre de archivo, en ese momento perdí mi concentración y, como resultado, esta publicación fue escrito.

Estoy en mi ingenio final aquí, toda la ayuda apreciada.

Respuesta

4

Es posible hacer algo como esto:

for c in candidates: 
    modname = os.path.splitext(c)[0] 
    try: 
     module=__import__(modname) #<-- You can get the module this way 
    except (ImportError,NotImplementedError): 
     continue 
    for cls in dir(module):   #<-- Loop over all objects in the module's namespace 
     cls=getattr(module,cls) 
     if (inspect.isclass(cls)    # Make sure it is a class 
      and inspect.getmodule(cls)==module # Make sure it was defined in module, not just imported 
      and issubclass(cls,base)):   # Make sure it is a subclass of base 
      # print('found in {f}: {c}'.format(f=module.__name__,c=cls)) 
      classList.append(cls) 

Para probar lo anterior, tuve que modificar su código un poco; a continuación está el guión completo.

import sys 
import inspect 
import os 

class PluginBase(object): pass 

def search(base): 
    for root, dirs, files in os.walk('.'): 
     candidates = [fname for fname in files if fname.endswith('.py') 
         and not fname.startswith('__')] 
     classList=[] 
     if candidates: 
      for c in candidates: 
       modname = os.path.splitext(c)[0] 
       try: 
        module=__import__(modname) 
       except (ImportError,NotImplementedError): 
        continue 
       for cls in dir(module): 
        cls=getattr(module,cls) 
        if (inspect.isclass(cls) 
         and inspect.getmodule(cls)==module 
         and issubclass(cls,base)): 
         # print('found in {f}: {c}'.format(f=module.__name__,c=cls)) 
         classList.append(cls) 
     print(classList) 

search(PluginBase) 
+0

funcionó casi perfectamente, gracias! Agregué 'y cls .__ name__! = Base .__ name__' para evitar que la clase base se agregue a la lista de subclases. – manneorama

0

¿Podría usar execfile() en lugar de importar con un dict de espacio de nombres especificado, luego iterar sobre ese espacio de nombres con issubclass, etc.?

3

Lo haría mucho más fácil si forzaba algunas restricciones en el escritor de complementos, por ejemplo, que todos los complementos deben ser paquetes que contienen una función load_plugin(app, config) que devuelve una instancia de Complemento. Entonces, todo lo que tiene que hacer es tratar de importar estos paquetes y ejecutar la función.

2

Aquí es una forma de meta-más clase para registrar los plugins:

Definir PluginBase a ser de tipo PluginType. PluginType registra automáticamente cualquier instancia (clase) en el conjunto plugins.

plugin.py:

plugins=set() 
class PluginType(type): 
    def __init__(cls, name, bases, attrs): 
     super(PluginType, cls).__init__(name, bases, attrs) 
     # print(cls, name,cls.__module__) 
     plugins.add(cls) 

class PluginBase(object): 
    __metaclass__=PluginType 
    pass 

Esta es la parte que el usuario escribe. Tenga en cuenta que no hay nada especial aquí.

pluginDir/myplugin.PY:

Esto es lo que la función de búsqueda podría ser:

test.py:

import plugin 
import os 
import imp 

def search(plugindir): 
    for root, dirs, files in os.walk(plugindir): 
     for fname in files: 
      modname = os.path.splitext(fname)[0] 
      try: 
       module=imp.load_source(modname,os.path.join(root,fname)) 
      except Exception: continue 

search('pluginDir') 
print(plugin.plugins) 

Correr rendimientos test.py

set([<class 'myplugin.Foo'>]) 
Cuestiones relacionadas