进行变量导入时避免使用环境变量

Avoid using environment variables when doing variable imports

我一直在处理一个项目,我可以选择使用两个后端(假设是后端 1 和 2),遵循他们在 this project 中所做的事情。但是,该项目依靠已经定义的环境变量来决定在执行代码之前使用哪个后端。我正在编写的代码不会是这种情况。

我想知道在这种情况下是否有任何替代方法可以使用环境变量,以便在执行时,我可以根据变量的值加载一个或另一个后端。我的项目大体结构如下:

我想直接在python代码中直接设置环境变量(os.environ['NAME_OF_ENV_VARIABLE'] = 'BACKEND 1'),但是感觉潜在的不安全,我真的 不喜欢这个想法,即使变量名是...唯一的。鉴于这种需要,我想知道是否可以让某种变量跨越不同的文件,这样,当我 do 导入模块时,__init__.py 文件可以消除后端之间的歧义。

PS:也许我做的事情毫无意义。


[更新] 有关该问题的更多信息,已缩减为最小扩展。我的主文件处理一些数据如下:

from argparse import ArgumentParser
from utils.loader import load_data
from utils.do import do_stuff

def main(config):
    data = load_data(config)
    do_stuff(config, data)

if __name__ == '__main__':
    # Retrieve input data
    parser = ArgumentParser()
    parser.add_argument('--backend', type=str, default='backend 1', help='backend to use')
    inputs = parser.parse_args()

    config = "backend 1" if inputs.backend == "1" else "backend 2"

    # Call main function
    main(config)

数据加载器load_data(config) 我想这并不重要。然后,包含 do_stuff(data) 的文件如下:

import backend

def do_stuff(config, data):
    # Do some really important stuff that is coded in backend 1 and backend 2
    a = backend.do_something(data)
    print(a)

它只是加载后端 (!!!) 并执行某些操作。 do_stuff(data) 函数本身执行后端 1 或后端 2 中编码的某些操作:

def do_something(data):
    data.values = "Value obtained through functions of 'BACKEND 1' (same function names and inputs, different backends used)"

def do_something(data):
    data.values = "Value obtained through functions of 'BACKEND 2' (same function names and inputs, different backends used)"

最后,后端模块本身具有以下 __init__.py 文件:

from .load_backend import do_something

load_backend.py 文件加载,这只是消除了后端的歧义 给定一个环境变量:

from __future__ import absolute_import
from __future__ import print_function
import os
import sys

# Default backend: backend 1
if 'ENVIRONMENT_VARIABLE' in os.environ:
    _BACKEND = os.environ['ENVIRONMENT_VARIABLE']
else:
    _BACKEND = 'backend 1'

# Import backend functions.
if _BACKEND == "backend 1":
    sys.stderr.write('Using backend 1\n')
    from .backend_1 import *
elif _BACKEND == "backend 2":
    sys.stderr.write('Using backend 2\n')
    from .backend_2 import *
else:
    raise ValueError('Unable to import backend : ' + str(_BACKEND))


def backend():
    """Publicly accessible method
    for determining the current backend.
    # Returns
        String, the name of the backend
    # Example
    ```python
        >>> backend.backend()
        'backend 1'
    ```
    """
    return _BACKEND

我想要的是用其他任何东西减少最后一个环境变量,但我不知道我能用什么。

如@DanielRoseman 所问,我只想传递后端参数。例如在 load_backend 中,尽可能少地更改代码:

from __future__ import absolute_import
from __future__ import print_function
import os
import sys

def backend(backend):
    """Returns the wanted backend module"""
    # Import backend functions.
    if backend == "backend 1":
        sys.stderr.write('Using backend 1\n')
        from . import backend_1 as backend_module
    elif backend == "backend 2":
        sys.stderr.write('Using backend 2\n')
        from . import backend_2 as backend_module
    else:
        raise ValueError('Unable to import backend : ' + str(_BACKEND))

    return backend_module

一种改进可能是使用 importlib 动态导入后端并将魔术字符串移动到常量:

...
import importlib

BACKENDS = {
    "backend 1": "backend_1",
    "backend 2": "backend_2"
}

def load_backend(backend):
    try:
        module = importlib.import_module(
            BACKENDS[backend]
        )
    except KeyError:
        raise ImportError('Unable to import backend : %s' % backend)

    sys.stderr.write('Using %s\n' % backend)
    return module

因此您可以在 do_stuff 文件中执行此操作:

import load_backend

def do_stuff(config, data):
    # Do some really important stuff that is coded in backend 1 and backend 2
    backend = load_backend.backend(config)
    a = backend.do_something(data)
    print(a)

解决此问题的另一种方法是使用单例模式,您可以在其中设置一次后端变量(以及您希望广泛使用的其他设置):

settings.py 或任何地方:

class SettingSingleton(object):
    _backend = None

    def __new__(cls, backend=None, *args, **kwargs):
        cls._backend = cls._backend or backend
        return super(SettingsSingleton, cls).__new__(cls, *args, **kwargs)

    @property
    def backend(self):
        return self._backend

你可以在主程序中初始化它。

from argparse import ArgumentParser
from utils.loader import load_data
from utils.do import do_stuff
from settings import SettingSingleton


def main(config):
    SettingsSingleton(backend=config)
    data = load_data(config)
    do_stuff(config, data)

...

现在您可以执行以下操作:

from __future__ import absolute_import
from __future__ import print_function
import os
import sys

from settings import SettingsSingleton

_BACKEND = SettingsSingleton().backend

# Import backend functions.
if _BACKEND == "backend 1":
    sys.stderr.write('Using backend 1\n')
    from .backend_1 import *
elif _BACKEND == "backend 2":
    sys.stderr.write('Using backend 2\n')
    from .backend_2 import *
else:
    raise ValueError('Unable to import backend : ' + str(_BACKEND))

缺点是它有些含蓄。