递归导入所有接口实现,并在Python3中实例化它们

时间:2020-03-06 17:57:15

标签: python python-3.x

我正在构建一个脚本运行器,因此可以很容易地重用脚本代码,但是我想让人们轻松添加更多脚本,而不必更改run-script.py方法。理想情况下,我希望能够调用run-scripts.py <ClassName>并使其执行类中的接口。现在,我正在测试导入功能,因此如果运行run-script.py list,它将列出基类接口的所有实现并在其上运行功能。

我的文件夹布局如下:

run-script.py
|- a
   |- script-a.py
   |- script-ab.py
|- b
   |- script-b.py
   |- ...
|- ...

我正在利用一个抽象基类来定义接口协定,像这样:

class ScriptRunnerBaseClass(metaclass=ABCMeta):

    @abstractmethod
    def run(self):
        pass

    @abstractmethod
    def description(self):
        pass

然后执行这样的实现:

class AppStats(ScriptRunnerBaseClass, metaclass=ABCMeta):

    def __init__(self):
        pass

    def description(self):
        return "Here is my description!"

    def run(self):
        self.do_things()

在我的run-script.py入口点中,我当前正在尝试列出抽象基类的所有实现的描述实现。我找到了article on dynamic imports,这是我到目前为止的内容。

import argparse
import gc
import importlib
import inspect
import pkgutil
import sys
from inspect import isclass
from pathlib import Path

from cloudfoundry.runner import ScriptRunnerBaseClass


class ScriptLoader(object):
    ignored_locations = ["venv"]

    def __init__(self):
        pass

    @staticmethod
    def load_all():
        for (_, name, _) in pkgutil.iter_modules([Path(__file__).parent]):
            imported_module = importlib.import_module(name, package=__name__)
            for i in dir(imported_module):
                attribute = getattr(imported_module, i)
                if inspect.isclass(attribute) and issubclass(attribute, ScriptRunnerBaseClass):
                    setattr(sys.modules[__name__], name, attribute)


def main():
    parser = argparse.ArgumentParser(description="Run a script against Cloud Foundry!")
    parser.add_argument("list", help="List all available cf scripts.")
    args = parser.parse_args()

    ScriptLoader.load_all()

    if args.list:
        print_scripts()


def print_scripts():
    for obj in gc.get_objects():
        if isclass(obj):
            if issubclass(ScriptRunnerBaseClass, obj):
                # AppStats: Here is my description!
                print("{0}: {1}".format(obj.__name__, obj().description()))


if __name__ == "__main__":
    main()

我已经验证了模块是通过REPL加载的,所以我知道ScriptLoader.load_all()功能正在实现我想要的功能。当我运行它时,我得到TypeError并不确定100%了解:

✗ python3 run-script.py list
Traceback (most recent call last):
  File "run-script.py", line 50, in <module>
    main()
  File "run-script.py", line 39, in main
    print_scripts()
  File "run-script.py", line 46, in print_scripts
    print("{0}: {1}".format(obj.__name__, obj().description()))
TypeError: Can't instantiate abstract class Hashable with abstract methods __hash__

我在做什么错?如何才能获得加载所有接口实现然后在它们上调用方法的预期结果?

0 个答案:

没有答案