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