如何在python中发现特定包中的类?
我有一个插件式模块包。看起来是这样的: /Plugins /Plugins/__init__.py /Plugins/Plugin1.py /Plugins/Plugin2.py etc...如何在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),我需要在发现
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())