Импорт создает неожиданные ссылки на объекты в зависимости от того, является ли модуль файлом или частью пакета

У меня есть небольшая проблема с импортом, из-за которой создаются переменные со ссылками на объекты, отличные от ожидаемых, и все зависит от того, импортируется ли модуль, который я пишу, «как есть» или как часть пакета.

Допустим, у меня есть простой модуль, который определяет класс Config и функцию для его настройки:

class Config:
    def __init__(self, **kwargs):
        if kwargs.get("print_keys", None):
            self.print_keys = kwargs.pop("print_keys")
        else:
            self.print_keys = ["centre", "shortName"]


rcParams = Config()


def set_config(config=None):
    global rcParams
    print("Before change:", id(rcParams))
    if config is None:
        config = Config()
    rcParams = config
    print("After change:", id(rcParams))

Если я сохраню это в модуле с именем simple.py, то смогу успешно протестировать это с помощью этого модуля:

# import gribtool as gt # note this line is uncommented later
import simple as gt     # note this line is commented later


def test_set_config():
    config = gt.Config(print_keys=["centre"])
    print("Initial rcParams id:", id(gt.rcParams))
    gt.set_config(config=config)
    print("Updated rcParams id:", id(gt.rcParams))
    assert gt.rcParams.print_keys == ["centre"]


test_set_config()

Результат:

Initial rcParams id: 128205347716160
Before change: 128205347716160
After change: 128205347716832
Updated rcParams id: 128205347716832

Все идет нормально. Проблема в том, что я хочу, чтобы это было частью пакета. Я скопировал (фактически связал) этот модуль в пакет (каталог) с именем gribtool, а затем создал файл __init__.py с ожидаемым содержимым:

from .simple import *

Содержимое каталога такое:


├── gribtool
│   ├── __init__.py
│   └── simple.py -> ../simple.py
├── simply.py
└── test.py

Затем я раскомментирую строку с import gribtool as gt в тестовом модуле выше, и затем происходит сбой с таким выводом:

Initial rcParams id: 134251413862864
Before change: 134251413862864
After change: 134251413864064
Updated rcParams id: 134251413862864
Traceback (most recent call last):
  File "/home/navarro/AEMET/GRIB_TOOL/test_config.py", line 13, in <module>
    test_set_config()
  File "/home/navarro/AEMET/GRIB_TOOL/test_config.py", line 10, in test_set_config
    assert gt.rcParams.print_keys == ["centre"]
AssertionError

Как видите, проблема в том, что rcParams, который модифицируется внутри функции set_config, отличается от модуля simple, несмотря на инструкцию global.

Как мне убедиться, что я ссылаюсь на правильные объекты?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Просто измените свой код на этот (test.py):

from gribtool import simple


def test_set_config():
    config = simple.Config(print_keys=["centre"])
    print("Initial rcParams id:", id(simple.rcParams))
    simple.set_config(config=config)
    print("Updated rcParams id:", id(simple.rcParams))
    assert simple.rcParams.print_keys == ["centre"]


test_set_config()

Это поможет. Я не знаю, почему изменяется значение объекта, но я знаю, что ваш исходный оператор import не будет работать. Просто импортируйте соответствующий модуль напрямую и используйте форму module.object для доступа к нужному объекту.

Другие вопросы по теме