Является ли PYTHONPATH согласованным для нескольких операторов импорта, даже если некоторые из них манипулируют sys.path? - PullRequest
0 голосов
/ 06 июля 2019

Документация PYTHONPATH https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH гласит, что "Путь поиска может быть изменен из программы Python как переменная sys.path."

т.е. другой модуль может свободно редактировать файл sys.path и добавлять его в список в любом месте или даже сделать его пустым.

Насколько я понимаю, для сохранения согласованного порядка поиска используется PYTHONPATH, не так ли?

Давайте предположим, что модуль "y" изменяет sys.path в скрипте A.py импорт х импорт у импорт z

  1. Интерпретатор Python видит PYTHONPATH, а интерпретатор sys.path обновляет его, затем импортирует x
  2. import y добавляет новый путь к началу или концу списка sys.path, это означает, что он также импортирует модуль sys.
  3. Теперь, поскольку sys.path был изменен в y, в зависимости от того, как работают операторы импорта https://docs.python.org/3.7/library/sys.html#sys.modules Насколько я понимаю, sys.path постоянно изменяется до тех пор, пока интерпретатор не отключится. Может быть, повторная загрузка модуля sys в A.py приведет к сбросу sys.path для использования порядка поиска PYTHONPATH?

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

1 Ответ

0 голосов
/ 06 июля 2019

Если ваш модуль 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', ...]
...