【问题标题】:How do I dynamically import all *.py files from a given directory AND all sub-directories?如何从给定目录和所有子目录中动态导入所有 *.py 文件?
【发布时间】:2020-01-12 16:20:02
【问题描述】:

假设我在一个公共目录下有一堆 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?

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

【问题讨论】:

  • 您为什么要这样做?创建first/__init__.py 然后在那里导入所有相关模块(+ 所有子模块类似)怎么样?
  • @a_guest 我知道这绝对是一个不寻常的用例,但由于我使用的依赖框架的设置方式,它被引入了。本质上,它允许一种“插件”系统,在该系统中加载脚本以影响其从特定目录的行为 - 但仅限于该目录。它不允许子目录,这使得大规模组织成为一个巨大的痛苦。所以我想在那个主目录中有一个脚本,它可以从我定义的任意目录结构中动态加载我自己的脚本。我可以“手动”导入我需要的任何模块,但这也是一项维护工作。

标签: python python-3.x


【解决方案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 变量来更改它。

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 来访问它。

【讨论】:

  • 谢谢!这看起来应该可以工作。标记为已接受。
  • @glenn.barker 我做了一些更改。我不小心在某些函数中覆盖了内置关键字dir,所以我用src 代替了它。还添加了在 dynamic_import_from_src 中模拟 * 导入的功能
  • 非常感谢。非常感谢。当您输入您的解决方案时,我也找到了自己的解决方案,因此我还分享了我发现的单独答案。我认为任何一种解决方案都应该在这里工作,所以我们将两者都包括在内以供将来参考。 :-)
【解决方案2】:

@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)

【讨论】:

    【解决方案3】:

    对不起。不知道这样行不行。但是,试试吧。

    从第一次导入 *

    【讨论】:

    • 这不会从子目录递归导入,因此不适合本题的需要。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-14
    • 2019-08-14
    • 2016-11-07
    • 1970-01-01
    • 2019-04-09
    相关资源
    最近更新 更多