我应该始终使用最 pythonic 的方式来导入模块吗?

Should I always use the most pythonic way to import modules?

我正在使用 pygame 制作一个小型游戏框架,我希望在其上实现基本代码以快速启动新项目。这将是一个模块,无论谁使用,都应该只创建一个文件夹,其中包含精灵 类、地图、关卡等的子文件夹。 我的问题是,我的框架模块应该如何加载这些客户端模块?我正在考虑设计它,以便开发人员可以将目录名称传递给主对象,例如:

game = Game()
game.scenarios = 'scenarios'

然后游戏会将'scenarios'附加到sys.path并使用__import__()我已经测试过并且有效。 但后来我又研究了一下,看看 python 中是否已经有一些自动加载器,所以我可以避免重写它,我发现了这个问题 基本上,不建议在 python 中使用自动加载器,因为 "explicit is better than implicit" 和 "Readability counts"。

那样的话,我想,我应该强迫我的模块的用户手动导入每个 his/her 模块,并将它们传递给游戏实例,例如:

import framework.Game
import scenarios
#many other imports
game = Game()
game.scenarios = scenarios
#so many other game.whatever = whatever

但是这对我来说不太好,不太舒服。看,我习惯于使用 php,我喜欢它与自动加载器一起工作的方式。 所以,第一个例子有一定的崩溃或麻烦的可能性,或者它不是 'pythonic'?

注意:这不是网络应用程序

我不会考虑让库从我当前的路径或模块好的样式中导入东西。相反,我只希望从两个地方导入一个库:

  1. 从全局模块 space 绝对导入,就像您使用 pip 安装的东西一样。如果一个图书馆这样做,这个图书馆也必须在它的install_requires=[]列表

  2. 中找到
  3. 从内部相对导入。如今这些都是从 .:

    明确导入的
    from . import bla
    from .bla import blubb
    

这意味着传递一个对象或模块本地到我的当前范围必须总是显式地发生:

from . import scenarios
import framework

scenarios.sprites  # attribute exists
game = framework.Game(scenarios=scenarios)

这允许你做一些事情,比如模拟 scenarios 模块:

import types
import framework

# a SimpleNamespace looks like a module, as they both have attributes
scenarios = types.SimpleNamespace(sprites='a', textures='b')
scenarios.sprites  # attribute exists
game = framework.Game(scenarios=scenarios)

你也可以实现一个framework.utils.Scenario() class实现某个接口来提供sprites,maps等。原因是:Sprites和Maps通常被保存在单独的文件中:您绝对不想做的事情 是查看 scenarios__file__ 属性并开始在其文件中猜测。而是实现一个为其提供统一接口的方法。

class Scenario():
    def __init__(self):
        ...

    def sprites(self):
        # optionally load files from some default location
        # If no such things as a default location exists, throw a NotImplemented error
        ...

你的 user-specific 场景将从它派生并可选择重载加载方法

import framework.utils
class Scenario(framework.utils.Scenario):
    def __init__(self):
        ...

    def sprites(self):
        # this method *must* load files from location
        # accessing __file__ is OK here
        ...

你还可以做的是让 framework 发布它自己的 framework.contrib.scenarios 模块,在没有使用 scenarios= 关键字 arg 的情况下使用(即方形默认地图和一些彩色默认纹理)

from . import contrib

class Game()
    def __init__(self, ..., scenarios=None, ...):
        if scenarios is None:
            scenarios = contrib.scenarios
        self.scenarios = scenarios