Избегайте использования переменных среды при импорте переменных - PullRequest
1 голос
/ 07 июля 2019

Я работал с проектом, для которого я предоставляю выбор использовать два бэкэнда (скажем, бэкэнды 1 и 2), следуя тому, что они делают в этом проекте . Однако этот проект рассчитывает на наличие уже определенных переменных среды, чтобы решить, какой бэкэнд использовать перед выполнением кода. Это не относится к коду, который я пишу.

Я хотел бы знать, есть ли какая-либо альтернатива для использования переменных среды в этом сценарии, чтобы во время выполнения я мог загружать один или другой бэкэнд в зависимости от значения переменной. Общая структура моего проекта выглядит следующим образом:

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

Я хочу уменьшить эту последнюю переменную среды чем-нибудь еще, но я не знаю, что я могу использовать.

1 Ответ

1 голос
/ 07 июля 2019

Как и спросил @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))

Недостатком является то, что это несколько неявно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...