自动导入文件夹中所有子模块并调用同名函数 - Python 运行时检查相关

0 投票
1 回答
4071 浏览
提问于 2025-04-16 12:03

我在电脑上有一个打开的文件夹,

OpenFunctions\
    ____Template.py
    function1.py
    function2.py
    ......
    functionx.py

这个文件夹是用来实验的,目的是扩展整个应用程序的功能。所以我们就把它当作一个快速尝试,不考虑安全性。

我的目标是,如果我把一个叫 functionx.py 的文件放在 ____Template.py 旁边,应用程序能够识别出这个新文件里的函数,并以某种方式调用这些新加入的函数——这有点像插件系统,但应该有些不同。

所以我写了一个 ____inspect.py,希望让应用程序能够知道输入了什么。

这里是

____inspect.py

def get_this_file_defined_functions(name_filter = "__"):
    import inspect, sys 
    f = inspect.getmembers(sys.modules[__name__], inspect.isfunction) 
    return [x for x in f if not x[0].startswith(name_filter)] 

def get_this_module_sub_modules(name_filter = "__"):
    import os.path, pkgutil
    pkgpath = os.path.dirname(__file__)
    m = [name for _, name, _ in pkgutil.iter_modules([pkgpath])] 
    return [x for x in m if not x[0].startswith(name_filter)]

def import_sub_modules_under_me(auto_exec_function = "auto_exec"):
    m = get_this_module_sub_modules()
    for i in m:  # need try except later
        exec "global %s; import %s" % (i, i)  
        #this will auto invoke __init__.py if sub modules folder is included
    for i in m:
        try:
            eval(i).eval(auto_exec_function)()
        except AttributeError:
            print "module %s has no function %s", % (i, auto_exec_function) 
        else:
            print "error on execute %s in module %s", % (auto_exec_function, i)

def execute_all_homonymy_functions(exec_function = "exec"):
    m = get_this_module_sub_modules()
    for i in m:
        #I need here for test if the module has been imported
        eval(i).eval(exec_function)()

这里是

____Template.py

def __you_can_not_see_me(): pass  # because filtered by str.startswith()
def auto_exec(): pass             # this will be auto executed
def you_can_get_me(): pass
def you_can_get_me1(): pass
def you_can_get_me2(): pass

基于以上想法,我还想扩展结构到下面

main.py
____inspect.py
OpenFunctions\
    __init__.py
    ____Template.py
    function1.py
    function2.py
    ......
    functionx.py
    module_aa
        \__init__.py
          aa.py
          aa1.py

这里是 main.py,而 __init__.py 可能看起来像这样

import ____inspect
____inspect.import_sub_modules_under_me()
____inspect.execute_all_homonymy_functions("what_ever_i_want")

问题:

  1. 上面的 __init__ 代码不会工作,因为在调用时 sys.modules[__name__]____inspect,而不是我想要的 OpenFunctionsmodule_aa。有没有办法避免将 sys.modules[__name__] 传递给 import_sub_modules_under_me()main.py__init__.py 中?

  2. 我认为 execute_all_homonymy_functions() 会执行文件夹中所有同名的函数,无论它们是在子模块中还是在单个文件中,但我想调用所有最新版本的函数,以防模块新添加或源代码在运行时发生变化。然后我想使用代码 import aa, reload(aa),但在下面的链接中可能被认为是错误的,有什么建议吗?我在 __inspect.py 中标记的问题是 我需要在这里测试模块是否已被导入

    [http://stackoverflow.com/questions/5027352/how-to-test-if-one-python-module-has-been-imported]

  3. 我还想在调用一个文件中的函数之前知道它的返回类型,有人建议在每个函数上加一个装饰器。所以我的计划是:

\n

____decorate.py

def attrs(**kwds):
     def decorate(f):
         for k in kwds:
             setattr(f, k, kwds[k])
         return f
     return decorate

functionx.py
import ../____decorate.py

@attrs(argument_types=(int, int,),returns=int)
def __you_can_not_see_me(): pass

@attrs(argument_types=(int, int,),returns=int)
def auto_exec(): pass             # I will be auto executed

@attrs(argument_types=(int, int,),returns=int)
def you_can_get_me(): pass

@attrs(argument_types=(int, int,),returns=int)
def you_can_get_me1(): pass

@attrs(argument_types=(int, int,),returns=int)
def you_can_get_me2(): pass

这样做在检查的情况下可以吗?还是有更好的解决方案?

最后一点:下面的代码

exec "global %s; import %s" % (i, i) 
eval(i).eval(auto_exec_function)()

看起来很丑,有没有更好的替代方案?

谢谢你的帮助。

祝好,
KC

1 个回答

2

首先回答你的最后一个问题:在最近版本的Python中,如果你想动态导入模块,可以使用 importlib.import_module(),这个就是为这个目的设计的。如果你用的是旧版本的Python,那就得用 __import__,不过要注意这种直接的方法在文档中有一些特别之处——这也是为什么后来添加了 import_module() 函数作为替代的原因。

接着回答你的第一个问题:没有官方的便携方式来获取调用函数的全局环境信息,所以最正确的做法就是在需要的地方直接把 __name__ 作为参数传进去。

在Python中重新加载模块总是有点危险,因为并不是所有东西在重新加载后都能正常工作(甚至很多标准库模块在你重新加载它们或者它们引用的模块时也会出问题)。

我真的建议你花点时间去看看这个关于Python插件架构的现有问题的答案:构建一个最小的Python插件架构

撰写回答