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

gle*_*ker 6 python python-3.x

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

/first/foo.py
/first/bar.py
/first/fizz/buzz.py
/first/numbers/one.py
/first/numbers/two.py
Run Code Online (Sandbox Code Playgroud)

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

import first.foo
import first.bar
import first.fizz.buzz
import first.numbers.one
import first.numbers.two
Run Code Online (Sandbox Code Playgroud)

但相反,我希望能够做类似的事情:

import_everything_under('first')
Run Code Online (Sandbox Code Playgroud)

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

但给出的答案和所谓的重复并不能回答这个问题。


这个问题被标记为可能重复:如何加载文件夹中的所有模块?

再说一遍,这并不能回答这个问题。该问题的答案不是递归的 - 它只会从直接目录导入项目,并且不包含子目录中的脚本,这是我的用例所需要的。

Syn*_*ica 3

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

  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
Run Code Online (Sandbox Code Playgroud)

现在您已经有了 .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
Run Code Online (Sandbox Code Playgroud)

现在我们只需要把它们放在一起,编写一个函数来调用您的get_py_files函数,然后循环结果,使用dynamic_import.

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

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
Run Code Online (Sandbox Code Playgroud)

请注意,我们必须调用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)
Run Code Online (Sandbox Code Playgroud)

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