在导入时有条件地覆盖python模块:设计问题

时间:2016-07-29 10:30:16

标签: python python-2.7 import module

在我的设置/设计中,请求子包的模块自动加载该包中的所有其他模块。我想办法绕过这个。

声明

几个月前,我问了一个相关的问题。但是,由于这个问题是新的和不同的,我将其创建为一个新问题。

对于想要阅读上一个问题并尝试阅读的人,请参阅Mask a python submodule from its package's __init__.py

设置

在我的一个软件包中,我有一个名为config的子包,其中包含一堆用于其他子模块和子包的配置文件:

mypackage
 |
 +-- subpkg_a
 |    |
 |    +-- __init__.py
 |    +-- <some modules here>.py
 |
 +-- config
 |    |
 .    +-- __init__.py
 .    +-- subpkg_a_sample.py
      +-- .gitignore (ignores everything except __init__ or *_sample.py)

要求

由于上述设置位于存储库中,因此同事可以(并且必须)克隆它,以便在其本地系统上使用mypackage。但是,它们的配置可能不同,这就是为什么我想提供覆盖*_sample.py中给出的示例配置并让它们仅在本地提供自己的配置文件 的能力。

基本原理是我希望人们可以直接使用配置设置来提供代码(为了使代码尽可能保持通用):

from config import subpkg_a as conf_a
print(conf_a.MY_CONFIG_SETTING)

但是,如果他们必须对配置文件进行本地调整以使某些设置适合其本地设置,则不应该更改*_sample.py ,因为它是存储库,包含通用设置和示例。而且,如果他们要更改*_sample.py配置文件,总会有用户意外地检查他们的本地更改(尤其是删除),因此需要保护他们自己......

因此,如果存在本地副本,我需要使用本地副本覆盖*_sample.py,否则在导入特定配置时加载*_sample.py

我提出的解决方案

目前,我在config/__init__.py内使用以下代码:

import os
import sys
import imp
import re

# Extend the __all__ list for all sub-packages that provide <pkg>_sample.py config files
__all__ = []
_cfgbase = os.path.dirname(os.path.realpath(__file__))
_r = re.compile('^(?P<key>.+?)_sample\.py$')
for f in os.listdir(_cfgbase):
    m = _r.match(f)
    if m: __all__.append(m.group('key'))

# Load local override file, if any. Otherwise load respective *_sample.py
for cfgmodule in __all__:
    if os.path.isfile(os.path.join(_cfgbase, cfgmodule + '.py')):
        locals()[cfgmodule] = imp.load_source('mypackage.config.' + cfgmodule, os.path.join(_cfgbase, cfgmodule + '.py'))
    else:
        locals()[cfgmodule] = imp.load_source('mypackage.config.' + cfgmodule, os.path.join(_cfgbase, cfgmodule + '_sample.py'))

这段代码的作用是:

  1. 扫描 __init__.py的目录,查找以_sample.py结尾的文件,并将其名称(即_sample.py之前的所有内容)存储在包& #39; s __all__列表。这些是可以加载的配置文件的包。

  2. 迭代__all__列表并重新导入本地覆盖(如果有),否则导入相应的*_sample.py。无论导入哪个文件,都可以通过配置模块以名称​​访问它,而无需_sample.py扩展名。我这样做是为了让依赖于配置模块的代码尽可能干净和通用。

  3. 最后,问题

    我现在面临的问题是,即使用户只导入单个子包的配置,即from config import subpkg_a as conf_a,该目录中可用的所有其他配置也会立即加载。

    我的__init__.py中的条件导入显示了这种行为(见上文)。但由于存在子包的配置文件,这依赖于其他导入(例如celerympl_toolkits.basemap),并且可能需要在特定环境下安装大量工作,任何只需要一小部分的用户需要配置和子包来安装在配置文件中导入的所有包和模块。即使用户和他/她的相应代码不需要相应的配置。

    我觉得自己已经建立了糟糕的设计,但我不知道如何做得更好。所以我问你:

    您是否认为有可能更改配置__init__.py(甚至整个设置),以便在用户请求单个配置文件时不会加载所有配置文件?

    我很感激任何形式的提示和答案。干杯!

1 个答案:

答案 0 :(得分:2)

只需将本地配置显式

让想要覆盖配置的人创建local_config.py文件。您可以将其添加到.gitignore。让用户在自己的代码中导入那个模块。

local_config.py模块的顶部,教导您的用户导入所需的示例配置:

from config.subpkg_a_sample import *

请注意import *。现在 subpkg_a_sample 中的所有名称都会导入local_config。用户可以轻松覆盖他们需要的任何内容。然后在软件的其余部分中,使用

try:
    import local_config as config
except ImportError:
    warn('No local configuration available')
    import config.default_config as config

或类似的方法来获得默认配置。

另一种方法是添加:

try:
    from local_config import *
except ImportError:
    pass

到所有*_sample.py模块,使这些模块负责应用本地配置。这样,名称也会被本地配置覆盖。

要求用户创建local_config.py对于您已经拥有的内容不再那么费劲,您要求用户选择示例配置并为您进行神奇插值提供覆盖。