Python - 暂时修改当前进程的环境

Rei*_*ica 25 python environment-variables

我使用以下代码临时修改环境变量.

@contextmanager
def _setenv(**mapping):
    """``with`` context to temporarily modify the environment variables"""
    backup_values = {}
    backup_remove = set()
    for key, value in mapping.items():
        if key in os.environ:
            backup_values[key] = os.environ[key]
        else:
            backup_remove.add(key)
        os.environ[key] = value

    try:
        yield
    finally:
        # restore old environment
        for k, v in backup_values.items():
            os.environ[k] = v
        for k in backup_remove:
            del os.environ[k]
Run Code Online (Sandbox Code Playgroud)

with上下文主要用于测试用例.例如,

def test_myapp_respects_this_envvar():
    with _setenv(MYAPP_PLUGINS_DIR='testsandbox/plugins'):
        myapp.plugins.register()
        [...]
Run Code Online (Sandbox Code Playgroud)

我的问题:是否有简单/优雅的写作方式_setenv?我想过实际上做backup = os.environ.copy(),然后os.environ = backup..但我不知道是否会影响程序行为(例如:如果os.environ引用在Python解释器在其他地方).

Lau*_*RTE 33

我建议您使用以下实现:

import contextlib
import os


@contextlib.contextmanager
def set_env(**environ):
    """
    Temporarily set the process environment variables.

    >>> with set_env(PLUGINS_DIR=u'test/plugins'):
    ...   "PLUGINS_DIR" in os.environ
    True

    >>> "PLUGINS_DIR" in os.environ
    False

    :type environ: dict[str, unicode]
    :param environ: Environment variables to set
    """
    old_environ = dict(os.environ)
    os.environ.update(environ)
    try:
        yield
    finally:
        os.environ.clear()
        os.environ.update(old_environ)
Run Code Online (Sandbox Code Playgroud)

编辑:更高级的实施

下面的上下文管理器可用于添加/删除/更新您的环境变量:

import contextlib
import os


@contextlib.contextmanager
def modified_environ(*remove, **update):
    """
    Temporarily updates the ``os.environ`` dictionary in-place.

    The ``os.environ`` dictionary is updated in-place so that the modification
    is sure to work in all situations.

    :param remove: Environment variables to remove.
    :param update: Dictionary of environment variables and values to add/update.
    """
    env = os.environ
    update = update or {}
    remove = remove or []

    # List of environment variables being updated or removed.
    stomped = (set(update.keys()) | set(remove)) & set(env.keys())
    # Environment variables and values to restore on exit.
    update_after = {k: env[k] for k in stomped}
    # Environment variables and values to remove on exit.
    remove_after = frozenset(k for k in update if k not in env)

    try:
        env.update(update)
        [env.pop(k, None) for k in remove]
        yield
    finally:
        env.update(update_after)
        [env.pop(k) for k in remove_after]
Run Code Online (Sandbox Code Playgroud)

用法示例:

>>> with modified_environ('HOME', LD_LIBRARY_PATH='/my/path/to/lib'):
...     home = os.environ.get('HOME')
...     path = os.environ.get("LD_LIBRARY_PATH")
>>> home is None
True
>>> path
'/my/path/to/lib'

>>> home = os.environ.get('HOME')
>>> path = os.environ.get("LD_LIBRARY_PATH")
>>> home is None
False
>>> path is None
True
Run Code Online (Sandbox Code Playgroud)

EDIT2

GitHub上提供了此上下文管理器的演示.

  • 对于这个老问题的访问者,我没有看到这个答案有任何明显的缺陷,它比原始的更完整和有用. (5认同)
  • 这应该是 python 的一部分——或者其他东西。扰乱测试环境是令人讨厌的 - 但有时是必要的 - 东西,并且可能会严重破坏、无效或以其他方式破坏环境混乱测试功能下游的测试:( (2认同)

Ign*_*ams 30

_environ = dict(os.environ)  # or os.environ.copy()
try:

    ...

finally:
    os.environ.clear()
    os.environ.update(_environ)
Run Code Online (Sandbox Code Playgroud)

  • 好的。不过,我使用的是“.copy()”而不是“dict()”。 (2认同)
  • 好的,但是在[...]期间发生故障(异常)的情况下,环境变量不会恢复:需要`try ... finally ...`. (2认同)

Syl*_*are 5

我一直想做同样的事情,但是对于单元测试,这是我使用该unittest.mock.patch函数完成的方式:

def test_function_with_different_env_variable():
    with mock.patch.dict('os.environ', {'hello': 'world'}, clear=True):
        self.assertEqual(os.environ.get('hello'), 'world')
        self.assertEqual(len(os.environ), 1)
Run Code Online (Sandbox Code Playgroud)

基本上unittest.mock.patch.dict与with一起使用clear=True,我们将os.environ其仅包含为字典{'hello': 'world'}

  • 删除clear=True将会使原始os.environ并在其中添加/替换指定的键/值对{'hello': 'world'}

  • 删除{'hello': 'world'}只会创建一个空字典,os.envrion因此内将是空的with

  • 非常感谢@sylhare 和@NathanielFord!这是我找到有关如何在模拟时从字典中删除键的信息的唯一地方。将值设置为 None 会引发错误 - 它必须是字符串 (2认同)

Vla*_*eyn 5

pytest可以使用固定装置临时设置环境变量monkeypatch。有关详细信息,请参阅文档。为了您的方便,我在这里复制了一个片段。

import os
import pytest
from typing import Any, NewType

# Alias for the ``type`` of monkeypatch fixture.
MonkeyPatchFixture = NewType("MonkeyPatchFixture", Any)


# This is the function we will test below to demonstrate the ``monkeypatch`` fixture.
def get_lowercase_env_var(env_var_name: str) -> str:
    """
    Return the value of an environment variable. Variable value is made all lowercase.

    :param env_var_name:
        The name of the environment variable to return.
    :return:
        The value of the environment variable, with all letters in lowercase.
    """
    env_variable_value = os.environ[env_var_name]
    lowercase_env_variable = env_variable_value.lower()
    return lowercase_env_variable


def test_get_lowercase_env_var(monkeypatch: MonkeyPatchFixture) -> None:
    """
    Test that the function under test indeed returns the lowercase-ified
    form of ENV_VAR_UNDER_TEST.
    """
    name_of_env_var_under_test = "ENV_VAR_UNDER_TEST"
    env_var_value_under_test = "EnvVarValue"
    expected_result = "envvarvalue"
    # KeyError because``ENV_VAR_UNDER_TEST`` was looked up in the os.environ dictionary before its value was set by ``monkeypatch``.
    with pytest.raises(KeyError):
        assert get_lowercase_env_var(name_of_env_var_under_test) == expected_result
    # Temporarily set the environment variable's value.
    monkeypatch.setenv(name_of_env_var_under_test, env_var_value_under_test)
    assert get_lowercase_env_var(name_of_env_var_under_test) == expected_result


def test_get_lowercase_env_var_fails(monkeypatch: MonkeyPatchFixture) -> None:
    """
    This demonstrates that ENV_VAR_UNDER_TEST is reset in every test function.
    """
    env_var_name_under_test = "ENV_VAR_UNDER_TEST"
    expected_result = "envvarvalue"
    with pytest.raises(KeyError):
        assert get_lowercase_env_var(env_var_name_under_test) == expected_result
Run Code Online (Sandbox Code Playgroud)