2010-08-17 8 views
7

Ho un pacchetto di moduli stile plug-in. Ecco come si presenta:Come posso scoprire le classi in un pacchetto specifico in python?

 
/Plugins 
/Plugins/__init__.py 
/Plugins/Plugin1.py 
/Plugins/Plugin2.py 
etc... 

Ogni file .py contiene una classe che deriva da PluginBaseClass. Quindi ho bisogno di elencare tutti i moduli nel pacchetto Plugins e quindi cercare qualsiasi classe che implementa PluginBaseClass. Idealmente Voglio essere in grado di fare qualcosa del genere:

for klass in iter_plugins(project.Plugins): 
    action = klass() 
    action.run() 

Ho visto alcune altre risposte non mancano, ma la mia situazione è diversa. Ho un'importazione effettiva nel pacchetto base (es .: import project.Plugins) e ho bisogno di trovare le classi dopo aver scoperto i moduli.

+1

Puoi richiedere che usano un nome magico per la loro classe? – nmichaels

+1

Perdonami (sono relativamente nuovo a Python), ma cos'è un nome di classe magico? Qualcosa come "__Plugin1__'? –

+3

Penso che intendesse una specie di convenzione di denominazione (diciamo che tutti i plugin sono in file come "plugin_something".py "," plugin_somethingelse.py "), in modo che tu possa filtrare per nome invece di scorrere tutti i moduli e tutte le classi. Personalmente, penso che questo potrebbe essere utile per i moduli (come sono, o potrebbero essere, i nomi dei file), ma ereditare da una classe base è un indicatore abbastanza buono (se non migliore) – rbp

risposta

1

I moduli di scansione non sono una buona idea. Se hai bisogno del registro di classe dovresti dare un'occhiata a metaclasses o usare soluzioni esistenti come zope.interface. Soluzione semplice attraverso metaclassi può sembrare che:

from functools import reduce 
class DerivationRegistry(type): 
    def __init__(cls,name,bases,cls_dict): 
     type.__init__(cls,name,bases,cls_dict) 
     cls._subclasses = set() 
     for base in bases: 
      if isinstance(base,DerivationRegistry): 
       base._subclasses.add(cls) 

    def getSubclasses(cls): 
     return reduce(set.union, 
         (succ.getSubclasses() for succ in cls._subclasses if isinstance(succ,DerivationRegistry)), 
         cls._subclasses) 

class Base(object): 
    __metaclass__ = DerivationRegistry 

class Cls1(object): 
    pass 

class Cls2(Base): 
    pass 

class Cls3(Cls2,Cls1): 
    pass 

class Cls4(Cls3): 
    pass 

print(Base.getSubclasses()) 
0

Se non sai cosa sta per essere in Plugins prima del tempo, è possibile ottenere un elenco dei file Python nella directory del pacchetto e importarli in questo modo:

# compute a list of modules in the Plugins package 

import os 
import Plugins 
plugin_modules = [f[:-3] for f in os.listdir(os.path.dirname(Plugins.__file__)) 
        if f.endswith('.py') and f != '__init__.py'] 

Siamo spiacenti, che la comprensione potrebbe essere un boccone per qualcuno relativamente nuovo a Python. Ecco una versione più dettagliata (potrebbe essere più facile da seguire):

plugin_modules = [] 

package_path = Plugins.__file__ 
file_list = os.listdir(os.path.dirname(package_path)) 
for file_name in file_list: 
    if file_name.endswith('.py') and file_name != '__init__.py': 
     plugin_modules.append(file_name) 

quindi è possibile utilizzare __import__ per ottenere il modulo:

# get the first one 
plugin = __import__('Plugins.' + plugin_modules[0]) 
+0

Beh, questo ottiene i nomi dei moduli, ma quando impongo come si mostra, torno indietro il modulo che è uno in basso, quindi '__import __ (" Plugin. Plugin1 ")' stampa ''. Questo è lo stesso problema che ho avuto anche con altri metodi. –

4

Edit: ecco una soluzione riveduta. Mi sono reso conto che stavo commettendo un errore durante il test del mio precedente, e non funziona come vorresti. Così qui è una soluzione più completa:

import os 
from imp import find_module 
from types import ModuleType, ClassType 

def iter_plugins(package): 
    """Receives package (as a string) and, for all of its contained modules, 
    generates all classes that are subclasses of PluginBaseClass.""" 

    # Despite the function name, "find_module" will find the package 
    # (the "filename" part of the return value will be None, in this case) 
    filename, path, description = find_module(package) 

    # dir(some_package) will not list the modules within the package, 
    # so we explicitly look for files. If you need to recursively descend 
    # a directory tree, you can adapt this to use os.walk instead of os.listdir 
    modules = sorted(set(i.partition('.')[0] 
          for i in os.listdir(path) 
          if i.endswith(('.py', '.pyc', '.pyo')) 
          and not i.startswith('__init__.py'))) 
    pkg = __import__(package, fromlist=modules) 
    for m in modules: 
     module = getattr(pkg, m) 
     if type(module) == ModuleType: 
      for c in dir(module): 
       klass = getattr(module, c) 
       if (type(klass) == ClassType and 
        klass is not PluginBaseClass and 
        issubclass(klass, PluginBaseClass)): 
        yield klass 

mia soluzione precedente era:

si potrebbe provare qualcosa di simile:

from types import ModuleType 
import Plugins 

classes = [] 
for item in dir(Plugins): 
    module = getattr(Plugins, item) 
    # Get all (and only) modules in Plugins 
    if type(module) == ModuleType: 
     for c in dir(module): 
      klass = getattr(module, c) 
      if isinstance(klass, PluginBaseClass): 
       classes.append(klass) 

In realtà, ancora meglio, se volete un po modularità:

from types import ModuleType 

def iter_plugins(package): 
    # This assumes "package" is a package name. 
    # If it's the package itself, you can remove this __import__ 
    pkg = __import__(package) 
    for item in dir(pkg): 
     module = getattr(pkg, item) 
     if type(module) == ModuleType: 
      for c in dir(module): 
       klass = getattr(module, c) 
       if issubclass(klass, PluginBaseClass): 
        yield klass 
+0

Questo non sembra lavoro per scoprire i moduli del plugin Se passo "Plugins.Plugin1" allora funziona fino alla chiamata 'isinstance' che non funziona poiché non abbiamo ancora istanziato la classe. –

+0

' issubclass' fa il trucco. ho bisogno di sapere come caricare i sottomoduli del pacchetto. –

+0

Ah, scusa, intendevo "issubclass", non "Isinstance". Sto fissando la risposta. – rbp

5

È possibile (e probabilmente dovrebbe) definire __all__ in __init__.py come una lista dei sottomoduli nel tuo pacchetto; questo è così che tu supporti le persone che fanno from Plugins import *. Se avete fatto così, è possibile scorrere i moduli con

import Plugins 
import sys 
modules = { } 
for module in Plugins.__all__: 
    __import__(module) 
    modules[ module ] = sys.modules[ module ] 
    # iterate over dir(module) as above 

La ragione un'altra risposta postato qui non è che __import__ importa il modulo di livello più basso, ma ritorna alto a livello uno (si veda la docs). Non so perché.

+0

Vorrei evitare questo. Il pacchetto Plugin può contenere fino a 500 plug-in attivi alla volta. –

+0

@ Jason: Credo che questo sia l'unico modo efficace per eseguire questa operazione, ad esempio, ad es. collegamenti simbolici, creazione dinamica di moduli, quel genere di cose. Tuttavia, se è garantito che tutti i moduli esisteranno come file Python nella directory, è possibile invece eseguire iterazioni su '(modulo per modulo in os.listdir (". ") Se module.endswith (" .py "))' . NB Modificato per commentare perché '__import__' restituisce il valore errato. – katrielalex

+0

Il puntatore sull'istruzione '__import__' è stato utile. Ora che ho lavorato oltre, sembra che 'dir()' non restituisca effettivamente i sotto-moduli. C'è un altro modo per farlo, di cui hai sentito parlare? –

Problemi correlati