如何在python中发现特定包中的类?

如何在python中发现特定包中的类?,python,reflection,Python,Reflection,我有一个插件式模块包。看起来是这样的: /Plugins /Plugins/__init__.py /Plugins/Plugin1.py /Plugins/Plugin2.py etc... for klass in iter_plugins(project.Plugins): action = klass() action.run() 我已经看到了一些其他的答案,但我的情况不同。我有一个到基本包的实际导入(即:import project.Plugins),我需要在发现

我有一个插件式模块包。看起来是这样的:

/Plugins /Plugins/__init__.py /Plugins/Plugin1.py /Plugins/Plugin2.py etc...
for klass in iter_plugins(project.Plugins):
    action = klass()
    action.run()

我已经看到了一些其他的答案,但我的情况不同。我有一个到基本包的实际导入(即:
import project.Plugins
),我需要在发现模块后找到类。

如果您事先不知道
Plugins
中会有什么,您可以在包的目录中获得一个python文件列表,然后像这样导入它们:

# 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']
很抱歉,对于python新手来说,这种理解可能有点过分。下面是一个更详细的版本(可能更容易理解):

然后,您可以使用
\uuuuuuuuuuuuuuuuuuuuuuuuuu
获取模块:

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

编辑:这里有一个修改后的解决方案。我意识到我在测试前一个时犯了一个错误,但它并没有像你期望的那样工作。因此,这里有一个更完整的解决方案:

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
我以前的解决方案是:

您可以尝试以下方法:

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)
事实上,如果您想要一些模块化,那就更好了:

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
您可以(也可能应该)在
\uuuu init\uuuuuuuuuuuuuuupy
中定义
\uuuuuuuuu all\uuuuuuuuuuuuuuuuuu>作为包中的子模块列表;这样您就可以支持人们从插件导入中执行
*
。如果您已经这样做了,您可以使用

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

此处发布的另一个答案失败的原因是
\uuuuu import\uuuu
导入最低级别的模块,但返回最高级别的模块(请参阅)。我不知道为什么。

扫描模块不是个好主意。如果您需要类注册表,您应该查看或使用现有的解决方案,如。 通过元类的简单解决方案可能如下所示:

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())

你能要求他们为他们的类使用一个神奇的名称吗?请原谅(我对python比较陌生),但是什么是神奇的类名?类似于
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。就个人而言,我认为这可能对模块有用(因为它们是,或者可能是,文件名),但是从基类继承是一个足够好的标记(如果不是更好的话)。这会得到模块名,但是当我像您所展示的那样导入时,我会得到一个更低的模块。因此,
\uuu导入(“Plugins.Plugin1”)
打印出
。这和我在其他方法中遇到的问题是一样的。这似乎不适用于发现插件模块。如果我传递它
Plugins.Plugin1
,它将一直工作到
isinstance
调用,该调用不起作用,因为我们还没有实例化该类。
issubclass
起作用。只需要知道如何加载包的子模块。啊,对不起,我指的是“issubclass”,而不是“isinstance”。我正在修正答案。你说的“子模块”是指包可能有任意数量的模块“级别”吗?如果是这样,您可以在“for c in dir(module)”行中搜索类和模块,并继续向下挖掘。明白我的意思吗?我不明白为什么
\uuuu import\uuuu
返回
,这比我传递给它的模块低一个模块。你知道吗?我想避免这样。插件包一次最多可以有500个活动插件。@Jason:我相信这是实现这一点的唯一可靠方法,因为例如符号链接、模块的动态创建等等。但是,如果保证所有模块都将以Python文件的形式存在于目录中,则可以迭代
(在os.listdir(“.”)中的模块对模块,如果module.endswith(“.py”)
。NB编辑以评论为什么
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu导入
返回错误的值。
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。现在我已经解决了这个问题,看来
dir()
实际上并没有返回子模块。你听说过有没有其他方法可以做到这一点?@Jason:啊,我有点天真
dir
不会返回子模块,因为它不能返回——如果返回,则
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。如果您没有定义
\uuuu all\uuuu
的话,唯一的方法就是列出目录中的文件:
(m代表os.listdir(“.”)中的m,如果m.endswith(“.py”)
请参见-
似乎不支持从…导入*
的主要原因是速度和可能产生副作用。
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())