Если ваш модуль y изменит sys.path
, значение будет таким же в вашем скрипте A.py, даже если вы выполните 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
до того же значения, которое было присвоено интерпретатором в начале.Из документации:
Список строк, в которых указывается путь поиска для модулей.Инициализируется из переменной среды PYTHONPATH, плюс зависящее от установки значение по умолчанию.
И ...
Как инициализируется при запуске программы, первый элемент этого списка, путь [0], является каталогом, содержащим скрипт, который использовался для вызова интерпретатора Python
Предположим, что интерпретатор не работает в интерактивном режиме и не читает из 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 Документация:
Если модуль находится в sys.modules, то sys.modules [имя]. loader возвращается
И из документации sys.modules :
Это словарь, который отображает имена модулей в уже загруженные модули.
РЕДАКТИРОВАТЬ:Это сложное решение, которое вы можете использовать для решения этой проблемы: вы можете создать функцию, которая вызывается при каждой загрузке модуля.Функция проверяет, изменяется ли
sys.path
после загрузки модуля.Если true, установите для него исходное значение
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', ...]