即使其中一些操作 sys.path,PYTHONPATH 在多个导入语句中是否一致?
Is PYTHONPATH consistent across multiple import statements even if some of them manipulate the sys.path?
PYTHONPATH 文档 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH 说 "The search path can be manipulated from within a Python program as the variable sys.path."
即另一个模块可以自由编辑 sys.path 并将其附加到列表的任何位置,甚至可以将其清空。
我的理解是,为了保持一致的搜索顺序,使用了 PYTHONPATH,不是吗?
让我们假设 "y" 模块在脚本中更改 sys.path A.py
导入 x
导入y
导入 z
- Python 解释器看到 PYTHONPATH 并且 sys.path 被解释器更新,然后导入 x
- import y 将新路径附加到 sys.path 列表的开头或结尾,这意味着它也将导入 sys 模块。
- 现在,由于 sys.path 已在 y 中更改,根据导入语句的工作方式 https://docs.python.org/3.7/library/sys.html#sys.modules 我的理解是 sys.path 会永久更改,直到解释器关闭。也许在 A.py 中再次重新加载 sys 模块会重置 sys.path 以使用 PYTHONPATH 搜索顺序?
我希望在顶层模块中有一个一致的搜索顺序路径,我相信它可能会受到另一个子 module/imports 更改它的影响。 PYTHONPATH 是获得这个的方法还是有一些我还不知道的其他 tip/trick?
如果您的模块 y 更改 sys.path
,即使您执行 importlib.reload(sys)
,您的 A.py 脚本中的值也会相同
所以假设模块 'y' 执行
from sys import path
path.clear()
在您的 A.py 脚本中:
import sys, importlib
import x, y
importlib.reload(sys)
print(sys.path) # is []
import z
找不到模块 z。
要解决此问题,您可以将脚本 sys.path
变量恢复为解释器开始时指定的相同值。
来自文档:
A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default.
还有……
As initialized upon program startup, the first item of this list, path[0], is the directory containing the script that was used to invoke the Python interpreter
假设解释器不在交互模式下 运行 或从 stdin 读取(它正在执行文件脚本)并且它位于当前工作目录
我们的 A.py 可能看起来像:
import importlib
import x, y
# We can still load (sys, os, ...)
from sys import path
from os import getcwd
import site
print(sys.path) # []
path.append(getcwd()) # Add directory where script is executed
path.append(os.environ.get('PYTHONPATH')) # Add PYTHONPATH
site.main() # Add site packages
import z # Now this dont fail
注意:即使删除所有 sys.path
项,importlib
也能够找到包 os
、site
、sys
、...
这是因为 importlib
使用 sys.modules
访问这样的包:
来自 importlib.find_loader 文档:
If the module is in sys.modules, then sys.modules[name].loader is returned
并且来自 sys.modules 文档:
This is a dictionary that maps module names to modules which have already been loaded.
编辑:
这是一个可用于解决此问题的棘手解决方案:您可以创建一个函数,每次加载模块时都会调用该函数。该函数检查模块加载后 sys.path
是否被更改。
如果为真,将其设置为原始值
from copy import copy
import warnings
import sys
sys.path = list(sys.path)
_original_path = copy(sys.path)
_base_import = __import__
def _import(*args, **kwargs):
try:
module = _base_import(*args, **kwargs)
return module
finally:
if type(sys.path) != list or sys.path != _original_path:
warnings.warn('System path was modified', Warning)
# Restore path
sys.path = copy(_original_path)
__builtins__.__import__ = _import
现在执行这段代码:
import sys
before = copy(sys.path)
import y # 'y' tries to change sys.path
after = copy(sys.path)
print(before == after) # True
它还会在标准输出上显示一条警告消息
编辑#2(另一种解决方案):
这仅适用于 python >=3.7 因为它依赖于 PEP 562
这里我基本上替换了模块 'sys' 这样我就可以避免外部模块改变实际的 sys.path
首先用下一个代码创建一个脚本(proxy.py):
import importlib
from sys import path, modules
from copy import copy
path = copy(path)
modules = copy(modules)
def __getattr__(name):
if name in globals():
return getattr(globals(), name)
return getattr(importlib.import_module('sys'), name)
def __dir__():
return dir(importlib.import_module('sys'))
现在,在您的 A.py 上输入下一个代码:
import proxy
import sys
sys.modules['sys'] = proxy
import y # y imports 'sys' but import sys returns the 'proxy' module
# 'y' thinks he changes sys.path but it only modifies proxy.path
print(proxy.path) # []
print(sys.path) # Unchanged
y 模块上的代码:
import sys
sys.path.clear() # a.k: proxy.path.clear()
# You can still access to all properties from the sys module
print(dir(sys)) # ['ps1', 'ps2', 'platform', ...]
PYTHONPATH 文档 https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH 说 "The search path can be manipulated from within a Python program as the variable sys.path."
即另一个模块可以自由编辑 sys.path 并将其附加到列表的任何位置,甚至可以将其清空。
我的理解是,为了保持一致的搜索顺序,使用了 PYTHONPATH,不是吗?
让我们假设 "y" 模块在脚本中更改 sys.path A.py 导入 x 导入y 导入 z
- Python 解释器看到 PYTHONPATH 并且 sys.path 被解释器更新,然后导入 x
- import y 将新路径附加到 sys.path 列表的开头或结尾,这意味着它也将导入 sys 模块。
- 现在,由于 sys.path 已在 y 中更改,根据导入语句的工作方式 https://docs.python.org/3.7/library/sys.html#sys.modules 我的理解是 sys.path 会永久更改,直到解释器关闭。也许在 A.py 中再次重新加载 sys 模块会重置 sys.path 以使用 PYTHONPATH 搜索顺序?
我希望在顶层模块中有一个一致的搜索顺序路径,我相信它可能会受到另一个子 module/imports 更改它的影响。 PYTHONPATH 是获得这个的方法还是有一些我还不知道的其他 tip/trick?
如果您的模块 y 更改 sys.path
,即使您执行 importlib.reload(sys)
所以假设模块 'y' 执行
from sys import path
path.clear()
在您的 A.py 脚本中:
import sys, importlib
import x, y
importlib.reload(sys)
print(sys.path) # is []
import z
找不到模块 z。
要解决此问题,您可以将脚本 sys.path
变量恢复为解释器开始时指定的相同值。
来自文档:
A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default.
还有……
As initialized upon program startup, the first item of this list, path[0], is the directory containing the script that was used to invoke the Python interpreter
假设解释器不在交互模式下 运行 或从 stdin 读取(它正在执行文件脚本)并且它位于当前工作目录
我们的 A.py 可能看起来像:
import importlib
import x, y
# We can still load (sys, os, ...)
from sys import path
from os import getcwd
import site
print(sys.path) # []
path.append(getcwd()) # Add directory where script is executed
path.append(os.environ.get('PYTHONPATH')) # Add PYTHONPATH
site.main() # Add site packages
import z # Now this dont fail
注意:即使删除所有 sys.path
项,importlib
也能够找到包 os
、site
、sys
、...
这是因为 importlib
使用 sys.modules
访问这样的包:
来自 importlib.find_loader 文档:
If the module is in sys.modules, then sys.modules[name].loader is returned
并且来自 sys.modules 文档:
This is a dictionary that maps module names to modules which have already been loaded.
编辑:
这是一个可用于解决此问题的棘手解决方案:您可以创建一个函数,每次加载模块时都会调用该函数。该函数检查模块加载后
sys.path
是否被更改。
如果为真,将其设置为原始值
from copy import copy
import warnings
import sys
sys.path = list(sys.path)
_original_path = copy(sys.path)
_base_import = __import__
def _import(*args, **kwargs):
try:
module = _base_import(*args, **kwargs)
return module
finally:
if type(sys.path) != list or sys.path != _original_path:
warnings.warn('System path was modified', Warning)
# Restore path
sys.path = copy(_original_path)
__builtins__.__import__ = _import
现在执行这段代码:
import sys
before = copy(sys.path)
import y # 'y' tries to change sys.path
after = copy(sys.path)
print(before == after) # True
它还会在标准输出上显示一条警告消息
编辑#2(另一种解决方案):
这仅适用于 python >=3.7 因为它依赖于 PEP 562
这里我基本上替换了模块 'sys' 这样我就可以避免外部模块改变实际的 sys.path
首先用下一个代码创建一个脚本(proxy.py):
import importlib
from sys import path, modules
from copy import copy
path = copy(path)
modules = copy(modules)
def __getattr__(name):
if name in globals():
return getattr(globals(), name)
return getattr(importlib.import_module('sys'), name)
def __dir__():
return dir(importlib.import_module('sys'))
现在,在您的 A.py 上输入下一个代码:
import proxy
import sys
sys.modules['sys'] = proxy
import y # y imports 'sys' but import sys returns the 'proxy' module
# 'y' thinks he changes sys.path but it only modifies proxy.path
print(proxy.path) # []
print(sys.path) # Unchanged
y 模块上的代码:
import sys
sys.path.clear() # a.k: proxy.path.clear()
# You can still access to all properties from the sys module
print(dir(sys)) # ['ps1', 'ps2', 'platform', ...]