如何动态从给定目录和所有子目录导入所有* .py文件?

时间:2019-09-10 22:03:55

标签: python python-3.x

假设我在一个公共目录下有一堆Python文件,但是在其中,可以存在任意数量的子目录:

/first/foo.py
/first/bar.py
/first/fizz/buzz.py
/first/numbers/one.py
/first/numbers/two.py

我有一些任意文件要导入所有这些文件。手动,我可以做:

import first.foo
import first.bar
import first.fizz.buzz
import first.numbers.one
import first.numbers.two

但是我希望能够做类似的事情:

import_everything_under('first')

我知道已经出现了类似的问题:Recursively import all .py files from all folders

但是给出的答案和所谓的重复项无法回答这个问题。


该问题被标记为How to load all modules in a folder?的可能重复项

同样,它不能回答这个问题。该问题的答案不是递归的-它只会从直接目录中导入项目,并且不会在子目录中包含脚本,这是我的用例。

3 个答案:

答案 0 :(得分:1)

您的问题分为两个步骤(从我的角度来看):

  1. 遍历目录和子目录,找到要导入的所有.py文件的名称

  2. 导入它们!

要解决(1),我们可以使用os.walk方法来查找所有.py文件。该函数如下所示:

import os
def get_py_files(src):
    cwd = os.getcwd() # Current Working directory
    py_files = [] 
    for root, dirs, files in os.walk(src):
        for file in files:
            if file.endswith(".py"):
                py_files.append(os.path.join(cwd, root, file))
    return py_files

现在您有了.py文件的列表,我们需要一个可以动态导入它们的函数。

import importlib
def dynamic_import(module_name, py_path):
    module_spec = importlib.util.spec_from_file_location(module_name, py_path)
    module = importlib.util.module_from_spec(module_spec)
    module_spec.loader.exec_module(module)
    return module

现在我们只需要将它们放在一起,编写一个调用您的get_py_files函数的函数,然后遍历结果,并用dynamic_import加载模块。

我假设您希望在python脚本中使用的模块名称与文件名相同,但是您可以通过修改以下函数中的module_name变量来更改它。 / strong>

def dynamic_import_from_src(src, star_import = False):
    my_py_files = get_py_files(src)
    for py_file in my_py_files:
        module_name = os.path.split(py_file)[-1].strip(".py")
        imported_module = dynamic_import(module_name, py_file)
        if star_import:
            for obj in dir(imported_module):
                globals()[obj] = imported_module.__dict__[obj]
        else:
            globals()[module_name] = imported_module
    return

注意,我们必须调用globals()才能将模块添加到全局名称空间。不这样做,模块将被导入,但是您将无法访问其中的任何内容。如果希望将其作为星号导入,则可以将star_import = True传递给dynamic_import_from_src。 (例如from first.foo import *。请注意,这可能会覆盖名称空间中的变量,但这仍然是使用*导入的缺点之一。

将所有内容都扔在一个块中,这样更容易一次查看所有内容:

import os
import importlib


def get_py_files(src):
    cwd = os.getcwd() # Current Working directory
    py_files = [] 
    for root, dirs, files in os.walk(src):
        for file in files:
            if file.endswith(".py"):
                py_files.append(os.path.join(cwd, root, file))
    return py_files


def dynamic_import(module_name, py_path):
    module_spec = importlib.util.spec_from_file_location(module_name, py_path)
    module = importlib.util.module_from_spec(module_spec)
    module_spec.loader.exec_module(module)
    return module


def dynamic_import_from_src(src, star_import = False):
    my_py_files = get_py_files(src)
    for py_file in my_py_files:
        module_name = os.path.split(py_file)[-1].strip(".py")
        imported_module = dynamic_import(module_name, py_file)
        if star_import:
            for obj in dir(imported_module):
                globals()[obj] = imported_module.__dict__[obj]
        else:
            globals()[module_name] = imported_module
    return

if __name__ == "__main__":
    dynamic_import_from_src("first", star_import = False)

此时,您可以像导入import first.whatever一样完全访问导入的任何模块。例如,如果first/numbers/one.py包含x=1,那么您可以说出one.x来访问它。

答案 1 :(得分:1)

@SyntaxVoid的解决方案应该可以工作,但与此同时,我本人也一直在解决这个问题,我想我已经找到了另一种解决方法。我相信,我自己的解决方案或@SyntaxVoid的解决方案都应该起作用。

from importlib import util
from os import path
from glob import glob


def import_submodules(start_path, include_start_directory=True):
    start_path = path.abspath(start_path)
    pattern = '**/*.py' if include_start_directory else '*/**/*.py'
    py_files = [f for f in glob(path.join(start_path, pattern), recursive=True) if not f.endswith('__.py')]

    for py_file in py_files:
        spec = util.spec_from_file_location('', py_file)
        module = util.module_from_spec(spec)
        spec.loader.exec_module(module)

答案 2 :(得分:0)

对不起。我不知道这种方式还好。但是,尝试一下。

首次导入*