Как изменить модульную переменную из другого модуля?

275
12

Предположим, у меня есть пакет с именем bar и он содержит bar.py:


a = None

def foobar():
print a


и __init__.py:


from bar import a, foobar

Затем я выполняю этот script:


import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()


Вот что я ожидаю:


None
1
1

Вот что я получаю:


None
1
None

Может ли кто-нибудь объяснить мое заблуждение?

спросил(а) 2010-08-21T09:34:00+04:00 9 лет, 3 месяца назад
3
Решение
264

Вы используете from bar import a. a становится символом в глобальной области видимости модуля импорта (или любой другой области видимости оператора import).

Когда вы присваиваете новое значение a, вы просто меняете, какое значение a указывает точка, а не фактическое значение. Попробуйте импортировать bar.py напрямую с помощью import bar в __init__.py и проведите там свой эксперимент, установив bar.a = 1. Таким образом, вы на самом деле будете изменять bar.__dict__['a'] который является "реальным" значением a в этом контексте.

Это немного bar.a = 1 с тремя слоями, но bar.a = 1 изменяет значение a в модуле, называемом bar который на самом деле является производным от __init__.py. Он не изменяет значение, что a foobar видит, потому что foobar живет в самом файле bar.py. Вы можете установить bar.bar.a если хотите изменить это.

Это одна из опасностей использования формы from foo import bar оператора import: она разделяет bar на два символа: один видимый глобально изнутри foo который начинает указывать на исходное значение, а другой символ - в области видимости, где Оператор import выполнен. Изменение точки, на которую указывает символ, не меняет значение, на которое он указывал.

Подобные вещи убивают при попытке reload модуль из интерактивного интерпретатора.

ответил(а) 2010-08-21T09:41:00+04:00 9 лет, 3 месяца назад
Еще 2 ответа
133

Одним из источников трудности с этим вопросом является то, что у вас есть программа с именем bar/bar.py: import bar импортирует либо bar/__init__.py, либо bar/bar.py, в зависимости от того, где это делается, что делает ее немного громоздкой для отслеживания того, a - bar.a.


Вот как это работает:


Ключом к пониманию происходящего является осознание того, что в __init__.py,


from bar import a

по сути делает что-то вроде


a = bar.a  # … with bar = bar/bar.py (as if bar were imported locally from __init__.py)

и определяет новую переменную (bar/__init__.py:a, если хотите). Таким образом, ваш from bar import a в __init__.py связывает имя bar/__init__.py:a с исходным объектом bar.py:a (None). Вот почему вы можете сделать from bar import a as a2 в __init__.py: в этом случае ясно, что у вас есть как bar/bar.py:a, так и различное имя переменной bar/__init__.py:a2 (в вашем случае имена двух переменных просто происходят оба они a, но они все еще живут в разных пространствах имен: в __init__.py они bar.a и a).


Теперь, когда вы делаете


import bar

print bar.a


вы получаете доступ к переменной bar/__init__.py:a (поскольку import bar импортирует ваш bar/__init__.py). Это переменная, которую вы изменяете (до 1). Вы не трогаете содержимое переменной bar/bar.py:a. Поэтому, когда вы впоследствии выполняете


bar.foobar()

вы вызываете bar/bar.py:foobar(), который обращается к переменной a из bar/bar.py, которая все еще None (когда foobar() определена, она связывает имена переменных раз и навсегда, поэтому a in bar.py is bar.py:a, а не любая другая переменная a, определенная в другом модуле, так как во всех импортированных модулях может быть много переменных a). Следовательно, последний вывод None.

ответил(а) 2010-08-21T12:05:00+04:00 9 лет, 3 месяца назад
103

Другими словами:
Оказывается, это заблуждение очень легко сделать.
Он скрытно определен в ссылке на языке Python: использование объекта вместо символа. Я бы предположил, что ссылка на языке Python делает это более понятным и менее разреженным.


Форма from не связывает имя модуля: она проходит через список идентификаторов, выглядит каждый из них в модуле, найденном в (1) и связывает имя в локальном пространстве имен с объектом таким образом найдено.



ОДНАКО:


При импорте вы импортируете текущее значение импортированного символа и добавьте его в свое пространство имен, как определено. Вы не импортируете ссылку, вы фактически импортируете значение.


Таким образом, чтобы получить обновленное значение i, вы должны импортировать переменную, содержащую ссылку на этот символ.


Иными словами, импорт не является как import в объявлении JAVA, external в C/С++ или даже в предложении use в PERL.


Скорее, следующий оператор в Python:


from some_other_module import a as x

больше похож на следующий код в K & R C:


extern int a; /* import from the EXTERN file */

int x = a;


(caveat: в случае Python "a" и "x" являются, по существу, ссылкой на фактическое значение: вы не копируете INT, вы копируете ссылочный адрес)

ответил(а) 2017-05-08T21:47:00+03:00 2 года, 7 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема