Как изменить переменную модуля из другого модуля? - PullRequest
81 голосов
/ 21 августа 2010

Предположим, у меня есть пакет с именем 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

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

Ответы [ 3 ]

74 голосов
/ 21 августа 2010

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

Когда вы присваиваете новое значение a, вы просто изменяете, какое значение a также указывает, а не фактическое значение.Попробуйте импортировать bar.py напрямую с import bar в __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 модуля из интерактивного интерпретатора.

18 голосов
/ 21 августа 2010

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

10 голосов
/ 08 мая 2017

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

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

ОДНАКО:

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

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

Другими словами, импорт НЕ похож на import в JAVA, 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, вы копируете ссылочный адрес)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...