Это связано с тем, как Python преобразует ваш код в байт-код (шаг компиляции).
При компиляции функции Python обрабатывает всю переменную, назначаемую как локальную переменную, и выполняет оптимизацию, чтобы уменьшить количество поисков имен, которые она должна будет выполнять. Каждой локальной переменной присваивается индекс, а когда функция вызывается, их значение будет храниться в локальном массиве стека, адресованном индексом. Компилятор выдаст код LOAD_FAST
и STORE_FAST
для доступа к переменной.
Синтаксис global
указывает вместо этого компилятору, что даже если переменной присваивается значение, ее нельзя рассматривать как локальную переменную, не следует назначать индекс. Вместо этого он будет использовать код LOAD_GLOBAL
и STORE_GLOBAL
для доступа к переменной. Эти коды операций медленнее, так как они используют имя для поиска в возможно многих словарях (locals, globals).
Если переменная доступна только для чтения значения, компилятор всегда испускает LOAD_GLOBAL
, так как он не знает, должна ли она быть локальной или глобальной переменной и, следовательно, предположить, что она является глобальной.
Итак, в вашей первой функции, используя global x
, компилятор сообщает компилятору, что он должен обрабатывать доступ на запись к x
как запись глобальной переменной вместо локальной переменной. Коды операций для функции дают понять:
>>> dis.dis(changeXto1)
3 0 LOAD_CONST 1 (1)
3 STORE_GLOBAL 0 (x)
6 LOAD_CONST 0 (None)
9 RETURN_VALUE
В вашем третьем примере, вы импортировать __main__
модуль в локальную переменную с именем __main__
, а затем присвоить его x
поле. Поскольку модуль является объектом, который хранит все отображения верхнего уровня в виде полей, вы назначаете переменную x
в модуле __main__
. И, как вы нашли, поля модуля __main__
непосредственно отображают значения в словаре globals()
, потому что ваш код определен в модуле __main__
. В опкоды показывают, что у вас нет доступа x
непосредственно:
>>> dis.dis(changeXto3)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 0 (None)
6 IMPORT_NAME 0 (__main__)
9 STORE_FAST 0 (__main__)
3 12 LOAD_CONST 2 (3)
15 LOAD_FAST 0 (__main__)
18 STORE_ATTR 1 (x)
21 LOAD_CONST 0 (None)
24 RETURN_VALUE
Второй пример интересен. Поскольку вы назначаете значение переменной x
, компилятор предполагает, что это локальная переменная и выполняет оптимизацию. Затем from __main__ import x
импортирует модуль __main__
и создает новое связывание значения x
в модуле __main__
с локальной переменной с именем x
. Это всегда так, from ${module} import ${name}
просто создайте новое связывание текущего пространства имен. Когда вы назначаете новое значение переменной x
, вы просто меняете текущую привязку, а не привязку в модуле __main__
, которая не связана (хотя, если значение изменено и вы его мутируете, изменение будет видно через все привязки). Вот опкоды:
>>> dis.dis(f2)
2 0 LOAD_CONST 1 (-1)
3 LOAD_CONST 2 (('x',))
6 IMPORT_NAME 0 (__main__)
9 IMPORT_FROM 1 (x)
12 STORE_FAST 0 (x)
15 POP_TOP
3 16 LOAD_CONST 3 (2)
19 STORE_FAST 0 (x)
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
Хороший способ думать об этом является то, что в Python все присваивания являются обязательными имя на значение в словаре, и разыменования просто делает поиск по словарю (это грубое приближение , но довольно близко к концептуальной модели). Когда вы делаете obj.field
, то вы ищете скрытый словарь obj
(доступно через obj.__dict__
) для ключа "field"
.
Если у вас есть голое имя переменной, оно отображается в словаре locals()
, а затем в словаре globals()
, если оно отличается (они одинаковы, когда код выполняется на уровне модуля). Для присваивания он всегда помещает привязку в словарь locals()
, если вы не заявили, что хотите получить глобальный доступ, выполнив global ${name}
(этот синтаксис также работает на верхнем уровне).
Итак перевод вашу функцию, это почти, если бы вы написали:
# NOTE: this is valid Python code, but is less optimal than
# the original code. It is here only for demonstration.
def changeXto1():
globals()['x'] = 1
def changeXto2():
locals()['x'] = __import__('__main__').__dict__['x']
locals()['x'] = 2
def changeXto3():
locals()['__main__'] = __import__('__main__')
locals()['__main__'].__dict__['x'] = 3
changeXto2 только устанавливает локальную переменную: http://www.saltycrane.com/blog/2008/01/python-variable- scope-notes/ –
Я ничего не вижу об импорте модулей. –