Как изменить модульную переменную из другого модуля?
Предположим, у меня есть пакет с именем 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
Может ли кто-нибудь объяснить мое заблуждение?
Вы используете 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
модуль из интерактивного интерпретатора.
Одним из источников трудности с этим вопросом является то, что у вас есть программа с именем 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
.
Другими словами:
Оказывается, это заблуждение очень легко сделать.
Он скрытно определен в ссылке на языке 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, вы копируете ссылочный адрес)