Хитрый Python вопрос о переменной области - PullRequest
1 голос
/ 25 января 2020

В моих Python проектах часто есть переменные, которые мне нужно разделить между модулями. Я знаю, что это можно сделать с помощью аргументов для вызовов функций, но иногда удобнее создать один модуль global_vars.py и добавить туда все переменные, которые нужно разделить, что я часто и делаю. Эти переменные затем могут быть импортированы из любого другого модуля и легко доступны. Это обычно работает хорошо для меня. Однако иногда происходят неожиданные вещи.

Например:

У меня есть 3 файла:

main.py:

from global_vars import var
from mod import modify_variable

print(f"{hex(id(var))} - address of var in main module after import")
modify_variable()
print(f"{hex(id(var))} - address of var in main module after modify_variable() call")

global_vars.py:

var = 'hi'
print(f"{hex(id(var))} - address of var during import")

mod.py: из global_vars import var

def modify_variable():
    global var

    print(f"{hex(id(var))} - address of var before modifying it in modify_variable()")
    var = 'hello'
    print(f"{hex(id(var))} - address of var after modifying it in modify_variable()")

Если вы запустите main.py с Python3, вы получите вывод, который выглядит следующим образом:

0x7f0f993bb7f0 - address of var during import
0x7f0f993bb7f0 - address of var in main module after import
0x7f0f993bb7f0 - address of var before modifying it in modify_variable()
0x7f0f993bb870 - address of var after modifying it in modify_variable()
0x7f0f993bb7f0 - address of var in main module after modify_variable() call

По сути, все ведет себя как ожидалось, пока мы не назовем modify_variable В пределах modify_variable адрес var начинается, как мы ожидаем. Затем мы присваиваем ему новую строку. Это делает несколько вещей:

  • Создает новую строку в памяти
  • Изменяет var, указывая на адрес этой новой строки
  • сборщик мусора, вероятно, в какой-то момент очистит адрес старой строки

. Учитывая это, я ожидаю, что последняя проверка адреса var также будет указывать на адрес нового строка. Поэтому я ожидаю, что результат будет выглядеть так:

0x7f0f993bb7f0 - address of var during import
0x7f0f993bb7f0 - address of var in main module after import
0x7f0f993bb7f0 - address of var before modifying it in modify_variable()
0x7f0f993bb870 - address of var after modifying it in modify_variable()
0x7f0f993bb870 - address of var in main module after modify_variable() call

, но это не так. var в modify_variable и var в main.py теперь указывают на совершенно разные адреса и больше не могут использовать одни и те же данные.

Что случилось? Из того, что я прочитал, если бы я не использовал global var в mod.py, вполне возможно, что я создал бы локальную переменную с тем же именем, что и глобальная переменная var, что могло бы вызвать описанные выше симптомы. , но использование global var, как я, гарантирует, что я имею дело с global_vars.var, не так ли?

1 Ответ

3 голосов
/ 25 января 2020

Спецификация c

Python на самом деле не имеет глобальных переменных. Вот что python определяет как глобальное:

Если имя связано на уровне модуля, оно является глобальной переменной. (Переменные блока кода модуля являются локальными и глобальными.)

И оператор global означает:

Это означает, что перечисленные идентификаторы должны интерпретироваться как глобальные.

т.е. как переменные уровня модуля.

Если вы посмотрите на то, что import делает:

  1. найти модуль, указанный в предложении from, при необходимости загрузить и инициализировать его;
  2. для каждого из идентификаторов, указанных в предложениях импорта:

    [...]

  3. [...] ссылка на это значение сохраняется в локальном пространстве имен с использованием имени в предложении as, если оно присутствует, в противном случае используется имя атрибута

Ваш случай

Итак, global_vars.var указывает на адрес 0x7f0f993bb7f0, а когда вы импортируете его в mod, он становится mod.var, который также указывает на 0x7f0f993bb7f0.

Когда вы делаете global var, вы указываете парсеру Python связывать var с mod.var, а затем с var = 'hello' вы указываете mod.var на 0x7f0f993bb870.

Но в main.py, var связывается с main.var, который был назначен оператором import для global_vars.var, а именно 0x7f0f993bb7f0.

Что делать

Реализуйте глобальные переменные как атрибуты одного глобального объекта:

g.py:

class Global:
    pass

g = Global()

setattr(g, 'var', 'hi')

print(f"{hex(id(g.var))} - address of var during import")

mod.py:

from g import g

def modify_variable():
    print(f"{hex(id(g.var))} - address of var before modifying it in modify_variable()")
    g.var = 'hello'
    print(f"{hex(id(g.var))} - address of var after modifying it in modify_variable()")

main.py:

#!/usr/bin/python3.6
from g import g
from mod import modify_variable

print(f"{hex(id(g.var))} - address of var in main module after import")
modify_variable()
print(f"{hex(id(g.var))} - address of var in main module after modify_variable() call")

Выход:

0x7ff2a47a1998 - address of var during import
0x7ff2a47a1998 - address of var in main module after import
0x7ff2a47a1998 - address of var before modifying it in modify_variable()
0x7ff2a47ae500 - address of var after modifying it in modify_variable()
0x7ff2a47ae500 - address of var in main module after modify_variable() call
...