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

107

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

a = None

def foobar():
    print a

и __init__.py:

from bar import a, foobar

Затем я выполняю этот скрипт:

import bar

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

Вот чего я ожидал:

None
1
1

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

None
1
None

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

сино
источник

Ответы:

103

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

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

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

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

Подобные вещи - убийца, когда пытаются reload получить модуль из интерактивного интерпретатора.

ааронастерлинг
источник
Спасибо! Ваш ответ, вероятно, просто сэкономил мне несколько часов на поиск виновника.
jnns
Похоже, что даже такой bar.bar.aподход в большинстве случаев не поможет. Вероятно, это слабая идея смешивать компиляцию или загрузку модуля и назначения времени выполнения, потому что вы в конечном итоге запутаете себя (или других, которые используют ваш код). Особенно в языках, которые ведут себя несколько непоследовательно, как, возможно, делает python.
Матанстер
26

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

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

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

from bar import a

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

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

и определяет новую переменную ( bar/__init__.py:aесли хотите). Таким образом, ваш from bar import ain __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()определена, она связывает имена переменных раз и навсегда, так что ain bar.pyесть bar.py:a, а не любая другая aпеременная, определенная в другом модуле, поскольку может быть много aпеременных во всех импортированных модулях ). Отсюда последний Noneвывод.

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

Эрик О Лебигот
источник
12

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

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

ТЕМ НЕ МЕНИЕ:

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

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

Другими словами, импорт НЕ похож на importJAVA, externalобъявление в C / 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;

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

Марк Геролиматос
источник
На самом деле, я считаю, что способ Python importнамного чище, чем Java, потому что пространства имен / области всегда должны быть должным образом разделены и никогда не мешают друг другу неожиданным образом. Как здесь: изменение привязки объекта к имени в пространстве имен (читай: присвоение чего-либо глобального свойства модуля) никогда не влияет на другие пространства имен (читай: ссылка, которая была импортирована) в Python. Но делает это в Java и т. Д. В Python вам нужно только понимать, что импортируется, в то время как в Java вы также должны понимать другой модуль на случай, если он изменит это импортированное значение позже.
Тино
2
Я должен решительно не согласиться. Импорт / включение / использование имеет долгую историю использования справочной формы, а не формы значений в Nust о всех других языках.
Марк Геролиматос
4
Черт возьми, я нажимаю return… это ожидание импорта по ссылке (согласно @OP). Фактически, новому программисту на Python, независимо от того, насколько он опытен, следует говорить об этом «осторожно». Этого никогда не должно происходить: из-за обычного использования Python выбрал неправильный путь. При необходимости создайте «значение импорта», но не объединяйте символы со значениями во время импорта.
Марк Геролиматос