从文件夹动态导入所有模块

4

我知道有这个问题,但是它们不仅不能工作,而且也不完全符合我的要求。我正在开发一个赛车游戏,希望能够动态地从文件夹中加载所有的赛道(这些赛道被存储为.py文件而不是.json文件)。我不想知道这些赛道的名称,因为用户可以自由地进行修改或添加。我只是想要导入它们的数据。例如:

>tracks 
>>track0.py
>>track1.py
>>track2.py
>>track3.py
>>track4.py

每个轨道内,我都有如下的数据:

track_ground_data = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
    [1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1],
    [1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
    [1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]

我需要像这样导入每个轨道模块:
loaded_tracks = [t for t in tracks] # Where tracks is the folder.

然后可以像这样访问给定的track_ground_data

loaded_tracks[0].track_ground_data

如果我早知道Python在导入方面会这么麻烦,我就应该使用json而不是.py。


是的。我已经将那段代码放在一个 __init__.py 文件中,当我尝试导入一条轨道(import tracks):tracks.track0,我收到了一个 AttributeError - Ericson Willians
你是对的,你可能应该使用专门用于存储数据的文件格式,而不是可执行的.py文件... - martineau
你可以通过将我的答案应用到一个关于包的相关问题中来实现这一点。 - martineau
1
如果Python文件只包含静态数据,则使用它们来跟踪是无用的。但是,它们确实允许放置任意代码,这可能是一个特性...例如,某些轨道可以定义一些函数,为该特定轨道提供特定逻辑,这在简单的JSON中更难实现。如果您不需要灵活性,只需使用JSON文件(顺便说一句:您仍然可以通过使用自定义模块加载器将其导入为Python模块...)。 - Bakuriu
3个回答

6

Python不会自动导入包含在一个包内的子模块。因此,import tracks 仅会 加载 tracks/__init__.py

但是,您可以将代码放在__init__.py文件中,在该目录中导入它发现的所有模块。

例如,将类似于以下内容放入__init__.py中:

import os
import importlib

__globals = globals()

for file in os.listdir(os.path.dirname(__file__)):
    mod_name = file[:-3]   # strip .py at the end
    __globals[mod_name] = importlib.import_module('.' + mod_name, package=__name__)

当只导入tracks时,应将您的子模块作为tracks.trackX可用。
或者您可以使用exec:
import os
import importlib

for file in os.listdir(os.path.dirname(__file__)):
    mod_name = file[:-3]   # strip .py at the end
    exec('import .' + mod_name)

更加清晰的方法是使用导入挂钩或实现自己的自定义模块导入器。可以使用多种方式使用 importlib,也可以参见 sys.path_hooks


你还需要过滤掉__init__.py,所以只有在mod_name不匹配模式^__时才执行最后一条语句。类似这样:if not re.match(r'^__', file): - superhawk610

2

为了未来的Python程序员的利益,我将分享我是如何解决这个问题的。一位朋友帮助了我。无法使Bakuriu的解决方案工作,因为模块为空。在__init__.py中,我添加了以下内容:

import os

dir = os.path.dirname(os.path.abspath(__file__))
modules = [os.path.splitext(_file)[0] for _file in os.listdir(dir) if not _file.startswith('__')]

tracks = []
for mod in modules:
    exec('from tracks import {}; tracks.append({})'.format(mod, mod))

然后,在主文件中,我已经这样加载它:

dir = os.path.dirname(os.path.abspath(__file__))
sys.path.append(dir)

from tracks import tracks

然后:

loaded_tracks = [t for t in tracks]

这真的很好地解决了问题。我几乎要转向JSON / 放弃了。


1
通常,当框架有插件或附加组件系统供社区贡献时,就会面临动态导入模块的问题。每个插件或附加组件都是一个模块,包含符合框架架构和API的类和函数。
考虑到这一点,“连接框架代码和任意多个附加组件”的解决方案是通过Python标准库中的importlib实现的。您似乎面临着相同的结构性问题。
以下是一个stackoverflow question,它使用importlib进行了回答。 以及documentation

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接