[英]How can I discover classes in a specific package in python?
I have a package of plug-in style modules.我有一个插件风格的模块包。 It looks like this:它看起来像这样:
/Plugins /Plugins/__init__.py /Plugins/Plugin1.py /Plugins/Plugin2.py etc...
Each .py file contains a class that derives from PluginBaseClass
.每个 .py 文件都包含一个派生自PluginBaseClass
的类。 So I need to list every module in the Plugins
package and then search for any classes that implement PluginBaseClass
.所以我需要列出Plugins
包中的每个模块,然后搜索任何实现PluginBaseClass
的类。 Ideally I want to be able to do something like this:理想情况下,我希望能够做这样的事情:
for klass in iter_plugins(project.Plugins):
action = klass()
action.run()
I have seen some other answers out there, but my situation is different.我已经看到了其他一些答案,但我的情况有所不同。 I have an actual import to the base package (ie: import project.Plugins
) and I need to find the classes after discovering the modules.我有一个对基本包的实际导入(即: import project.Plugins
),我需要在发现模块后找到这些类。
Edit: here's a revised solution.编辑:这是一个修改后的解决方案。 I realised I was making a mistake while testing my previous one, and it doesn't really work the way you would expect.我意识到我在测试我之前的测试时犯了一个错误,它并没有像你期望的那样工作。 So here is a more complete solution:所以这里有一个更完整的解决方案:
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
My previous solution was:我之前的解决方案是:
You could try something like:您可以尝试以下方法:
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)
Actually, even better, if you want some modularity:实际上,如果您想要一些模块化,那就更好了:
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
You may (and probably should) define __all__
in __init__.py
as a list of the submodules in your package;您可以(并且可能应该)将__init__.py
中的__all__
定义为包中子模块的列表; this is so that you support people doing from Plugins import *
.这是为了让你支持人们from Plugins import *
做。 If you have done so, you can iterate over the modules with如果你这样做了,你可以用
import Plugins
import sys
modules = { }
for module in Plugins.__all__:
__import__( module )
modules[ module ] = sys.modules[ module ]
# iterate over dir( module ) as above
The reason another answer posted here fails is that __import__
imports the lowest-level module, but returns the top-level one (see the docs ).此处发布的另一个答案失败的原因是__import__
导入了最低级别的模块,但返回了顶级模块(请参阅文档)。 I don't know why.我不知道为什么。
Scanning modules isn't good idea.扫描模块不是一个好主意。 If you need class registry you should look at metaclasses or use existing solutions like zope.interface .如果您需要类注册表,您应该查看元类或使用现有的解决方案,如zope.interface 。 Simple solution through metaclasses may look like that:通过元类的简单解决方案可能如下所示:
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())
If you don't know what's going to be in Plugins
ahead of time, you can get a list of python files in the package's directory, and import them like so:如果你不提前知道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']
Sorry, that comprehension might be a mouthful for someone relatively new to python.抱歉,对于 python 相对较新的人来说,这种理解可能是满口的。 Here's a more verbose version (might be easier to follow):这是一个更详细的版本(可能更容易理解):
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)
Then you can use __import__
to get the module:然后你可以使用__import__
来获取模块:
# get the first one
plugin = __import__('Plugins.' + plugin_modules[0])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.