在没有前缀的目录中加载所有模块(文件)

Posted

技术标签:

【中文标题】在没有前缀的目录中加载所有模块(文件)【英文标题】:Load all modules (files) in directory without prefixes 【发布时间】:2017-01-02 04:14:18 【问题描述】:

我正在使用 python 2.7。我有一个文件夹,其中包含许多 .py 文件,这些文件定义了我想在我的主脚本中使用的函数、加载对象等。我想完成以下两个目标:

    从该目录下的所有 .py 文件中加载所有对象,无需事先知道文件名。 在我的主脚本中访问这些对象而不使用前缀,例如无需使用filename.function_name()

我了解这不符合公认的 Python 模块最佳实践。尽管如此:

我编写代码纯粹是为了我自己的使用。它永远不会与他人共享或被他人使用。

在我的开发工作过程中,我经常更改文件的名称,将对象定义从一个文件移动到另一个文件等。因此,需要进入我的主脚本并更改名称很麻烦每次我这样做时,函数名称前缀的数量。

我是成年人。我理解名称冲突风险的概念。我对我创建的函数和对象使用自己的命名约定,以确保它们不会与其他名称冲突。

我对此的第一次尝试是使用os.listdir() 遍历目录中的文件,然后在这些文件上调用execfile()。当这不起作用时,我在这里查看了回复:Loading all modules in a folder in Python。我发现了很多有用的东西,但没有一个能把我带到我想要的地方。具体来说,如果在我的__init__.py 文件中包含响应here:

from os.path import dirname, basename, isfile
import glob
modules = glob.glob(dirname(__file__)+"/*.py")
__all__ = [ basename(f)[:-3] for f in modules if isfile(f)]

然后在我的主脚本中使用:

os.chdir("/path/to/dir/") # folder that contains `Module_Dir`
from Module_Dir import *

然后我可以访问我目录中文件中定义的所有对象,而无需提前知道这些文件的名称(从而满足我的目标中的#1)。但是,这仍然需要我使用filename.function_name() 等调用这些函数和对象。同样,如果我明确地包含在我的__init__.py 文件中:

from filename1 import *
from filename2 import *
etc.

然后我可以在我的主脚本中使用与上面相同的from Module_Dir import *。现在,我可以在没有前缀的情况下访问我的对象,但它需要我明确指定 __init__.py 中的文件名。

是否有可以将这两者结合起来的解决方案,从而实现我的两个目标?我也试过(例如建议here,在__init__.py中包括以下内容:

import os
for module in os.listdir(os.path.dirname(__file__)):
    if module == '__init__.py' or module[-3:] != '.py':
        continue
    __import__(module[:-3], locals(), globals())
del module

但同样,这仍然需要名称前缀。我试图查看是否有可选参数或修改我在此处使用__import__ 的方式,或者使用python 2.7 的importlib 的应用程序,但在这两个方面都没有取得进展。

【问题讨论】:

一个 hacky 解决方案是在文件名更改时更新 __init__.py 的辅助脚本。 @meetaig 这实际上不是一个坏主意——事实上,我可以在我的主脚本中使用一个非常小的、简单的函数来编写 __init__.py 文件,然后将其导入。谢谢! 没问题,很高兴能帮上忙! 无论如何请注意,importlib 也存在于 python2.7 中,即使功能很少。您可能想使用importlib.import_module 而不是__import__,然后遍历该模块的所有属性并将它们分配给全局变量。 @Bakuriu 谢谢。究竟什么是最好的方法,特别是如果我不希望某些对象本身是全局变量(例如,为了将它们用作函数中的参数)? 【参考方案1】:

好的,这是我根据@meetaig 的建议提出的解决方案。我很高兴接受并支持针对这种情况的任何其他想法或建议的实现:

import os

def Load_from_Script_Dir(DirPath):
    os.chdir(os.path.dirname(os.path.dirname(DirPath))) ## so that the from .. import syntax later properly finds the directory.
    with open(DirPath + "__init__.py", 'w') as f:
        Scripts = [FileName for FileName in os.listdir(DirPath) if FileName[-3:] == '.py' and '__init__' not in FileName]
        for Script in Scripts: 
            f.write('from %s import *\n'  % Script.replace('.py',''))


# Script_Dir = "/path/to/Script_Dir/"  ## specify location of Script_Dir manually
Script_Dir = os.path.dirname(os.path.abspath(__file__)) + "/Script_Dir/" ## assume Script_Dir is in same path as main.py that is calling this.  Note: won't work when used in REPL.
Load_from_Script_Dir(Script_Dir)
from Script_Dir import *

注意事项:

    这还要求 Script_Dir 中的 .py 文件符合 python 对象的命名约定,即它们不能以数字开头,不能有空格等。

    此处加载的 Script_Dir 中的 .py 文件将无法访问主脚本中定义的对象(即使它们是在调用 from Script_Dir import * 之前定义的)。同样,他们将无权访问 Script_Dir 中其他脚本中定义的对象。因此,可能仍然需要在其他 .py 文件中使用诸如 from filename import objectname 之类的语法。这是不幸的,并且部分地破坏了我希望实现的一些便利,但它至少是对现状的适度改进。

【讨论】:

【参考方案2】:

这是另一种解决方案,基于与我的其他实现相同的一般原则(其灵感来自 cmets 中 @meetaig 的建议)。然而,这个绕过了使用 python 模块框架的尝试,而是开发了一种解决方法来能够使用execfile()。对于我的目的来说,这似乎更可取,因为:

    1234563我希望获得的便利)。相反,就像脚本都放在一个大文件中一样,函数/对象只需要在调用给定函数时定义。

    同样,支持脚本可以利用在运行支持脚本之前创建的主脚本中的对象。

    这样可以避免创建一堆 .pyc 文件,这些文件只会弄乱我的目录。

    这同样绕过了(从我的其他解决方案中)确保将解释器的工作目录导航到正确位置的需要,以确保主脚本中的 from ... import ... 语句成功。

.

import os

Script_List_Name = "Script_List.py"

def Load_from_Script_Dir(DirPath):
    with open(DirPath + Script_List_Name, 'w') as f:
        Scripts = [FileName for FileName in os.listdir(DirPath) if FileName[-3:] == '.py' and FileName != Script_List_Name]
        for Script in Scripts: 
            f.write('execfile("%s")\n'  % (DirPath + Script))

Script_Dir = "/path/to/Script_Dir/"

Load_from_Script_Dir(Script_Dir)
execfile(Script_Dir + Script_List_Name)

或者,如果不需要在函数内部进行加载,如果包含在主脚本中,以下循环也可以正常工作(我最初被抛弃,因为当我尝试为了构建一个函数来执行这个循环,我认为它只将对象加载到该函数的范围内,因此它们无法在其他地方访问,这一操作与 Julia 等其他语言不同,其中相同的函数可以成功放置对象到函数外的范围内):

import os
Script_Dir = "/path/to/Script_Dir/"
for FileName in os.listdir(Script_Dir):
    if FileName[-3:] == '.py':
        print "Loading %s" %FileName
        execfile(Script_Dir + FileName)

【讨论】:

所以你本质上想要大量的模块隐式共享全局状态?这听起来像是一场等待发生的灾难。为什么要这样做? @Blender 基本上,它只是将原本庞大、冗长、复杂的脚本分解成一堆更易于管理的部分——通常每个函数都有一个文件(通常相对长),但有时将几个函数组合在一起,或者创建一堆占用大量空间来定义的对象。例如,我发现这也使得找到编辑需要更改的内容的确切位置变得容易得多,而不是通过冗长的脚本进行搜索。 这样做还可以更轻松地提升一个“项目”的各个部分并更轻松地将它们拼接到其他项目中,因为它们已经被划分为逻辑上更独立且至少部分独立的块 @Blender 当然,欢迎您提出任何其他想法、建议等 - 我不拘泥于任何特定的方法,所以如果您有一些您认为更有用的东西,我全神贯注! 您已经在逻辑上将函数分组到(几乎)模块中,但是通过让它们依赖于共享的全局变量来使它们不可重用似乎会让您以后想要提取一个时感到痛苦代码块,但不容易,因为你隐藏了它的依赖关系。【参考方案3】:

使用路径

"""
Imports all symbols from all modules in the package
"""
from pathlib import Path
from importlib import import_module


for module_path in Path(__file__).parent.glob('*.py'):
    if module_path.is_file() and not module_path.stem.startswith('_'):
        module = import_module(f'.module_path.stem', package=__package__)
        symbols = [symbol for symbol in module.__dict__ if not symbol.startswith("_")]
        globals().update(symbol: getattr(module, symbol) for symbol in symbols)

【讨论】:

以上是关于在没有前缀的目录中加载所有模块(文件)的主要内容,如果未能解决你的问题,请参考以下文章

angular js:是不是需要在模块文件中加载所有控制器和工厂/服务

如何在模块中加载未知类?

在 Android Studio 中加载一个简单的文本文件

WKWebView 无法在 HTML 中加载本地资源

ruby 在目录中加载多个文件并要求它们

在 XmlReader .NET 4.0 中加载失败目录文件