Python - temporarily modify the current process's environment
我使用以下代码临时修改环境变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @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] |
这个
1 2 3 4 | def test_myapp_respects_this_envvar(): with _setenv(MYAPP_PLUGINS_DIR='testsandbox/plugins'): myapp.plugins.register() [...] |
我的问题是:有没有一种简单/优雅的方法来写
我建议您执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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) |
编辑:更高级的实现
下面的上下文管理器可用于添加/删除/更新环境变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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] |
使用实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> 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 |
编辑2
GitHub上提供了此上下文管理器的演示。
1 2 3 4 5 6 7 8 | _environ = dict(os.environ) # or os.environ.copy() try: ... finally: os.environ.clear() os.environ.update(_environ) |
我本来想做同样的事情,但是对于单元测试,下面是我如何使用
1 2 3 4 | 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) |
基本上,我们将
删除
clear=True 将允许原始os.environ,并在{'hello': 'world'} 中添加/替换指定的键/值对。删除
{'hello': 'world'} 只会创建一个空字典,因此os.envrion 在with 中是空的。
对于单元测试,我更喜欢使用带有可选参数的decorator函数。这样我就可以为整个测试函数使用修改后的环境值。如果函数引发异常,下面的decorator还会还原原始环境值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import os def patch_environ(new_environ=None, clear_orig=False): if not new_environ: new_environ = dict() def actual_decorator(func): from functools import wraps @wraps(func) def wrapper(*args, **kwargs): original_env = dict(os.environ) if clear_orig: os.environ.clear() os.environ.update(new_environ) try: result = func(*args, **kwargs) except: raise finally: # restore even if Exception was raised os.environ = original_env return result return wrapper return actual_decorator |
单元测试中的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Something: @staticmethod def print_home(): home = os.environ.get('HOME', 'unknown') print("HOME = {0}".format(home)) class SomethingTest(unittest.TestCase): @patch_environ({'HOME': '/tmp/test'}) def test_environ_based_something(self): Something.print_home() # prints: HOME = /tmp/test unittest.main() |
使用这里的gist,您可以保存/恢复本地、全局范围变量和环境变量:https://gist.github.com/earonesty/ac0617a5672ae1a41be1eaf316dd63e4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import os from varlib import vartemp, envtemp x = 3 y = 4 with vartemp({'x':93,'y':94}): print(x) print(y) print(x) print(y) with envtemp({'foo':'bar'}): print(os.getenv('foo')) print(os.getenv('foo')) |
此输出:
1 2 3 4 5 6 | 93 94 3 4 bar None |