Як змінити змінну модуля з іншого модуля?


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.
matanster

26

Одним із джерел труднощів з цим питанням є те, що у вас є програма з ім’ям bar/bar.py: import barimport 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
Я повинен дуже не погоджуватися. Імпорт / включення / використання має давню історію використання довідкової форми, а не форми значення в усіх інших мовах.
Марк Героліматос,

4
Чорт, я натиснув return ... є це очікування імпорту за посиланням (згідно @OP). Насправді, новому програмісту на Python, яким би досвідченим він не був, доводиться про це повідомляти в такий спосіб. Це ніколи не повинно статися: на основі загальновживаного використання Python пішов неправильним шляхом. Створіть "імпортне значення", якщо це необхідно, але не змішуйте символи зі значеннями під час імпорту.
Марк Героліматос
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.