Почему функции в Python могут печатать переменные в закрытой области видимости, но не могут использовать их в присваивании?

Если я запускаю следующий код:

x = 1

class Incr:
    print(x)
    x = x + 1
    print(x)

print(x)

Это печатает:

1
2
1

Хорошо, никаких проблем, это именно то, что я ожидал. И если я сделаю следующее:

x = 1

class Incr:
    global x
    print(x)
    x = x + 1
    print(x)

print(x)

Это печатает:

1
2
2

Также, что я ожидал. Там нет проблем.

Теперь, если я начну делать функцию приращения следующим образом:

x = 1

def incr():
    print(x)

incr()

Он печатает 1, как я ожидал. Я предполагаю, что это делает это, потому что это не может найти x в его локальной области видимости, поэтому он ищет в своей области видимости и находит x там. Пока проблем нет.

Теперь, если я сделаю:

x = 1

def incr():
    print(x)
    x = x + 1

incr()

Это дает мне следующую ошибку в трассировке:

UnboundLocalError: локальная переменная 'x', на которую есть ссылка перед присваиванием.

Почему Python не просто ищет вмещающее пространство для x когда он не может найти значение x использовать для назначения, как мой class Incr сделал? Обратите внимание, что я не спрашиваю, как заставить эту функцию работать. Я знаю, что функция будет работать, если я сделаю следующее:

x = 1

def incr():
    global x
    print(x)
    x = x + 1
    print(x)

incr()

Это будет правильно печатать:

1
2

как я и ожидал. Все, что я спрашиваю, почему это не просто тянуть x из охватывающей области, когда ключевое слово global нет так же, как и для моего класса выше. Почему переводчик чувствует необходимость сообщить об этом как UnboundLocalError когда ясно, что некоторые x существует. Так как функция была в состоянии прочитать значение в x для печати, я знаю, что это имеет x как часть его охватывающей области... так почему это не работает так же, как пример класса?

Почему используется значение x для печати так отличается от использования его значения для присваивания? Я просто не понимаю

7 ответов

Решение

Классы и функции различны, переменные внутри класса фактически назначаются пространству имен класса в качестве его атрибутов, в то время как внутри функции переменные являются просто обычными переменными, к которым нельзя получить доступ вне его.

Локальные переменные внутри функции фактически определяются, когда функция анализируется впервые, и python не будет искать их в глобальной области видимости, поскольку он знает, что вы объявили ее как локальную переменную.

Итак, как только Python увидит x = x + 1 (назначение) и нет global объявленный для этой переменной, Python не будет искать эту переменную в глобальной или других областях.

>>> x = 'outer'
>>> def func():
...     x = 'inner'  #x is a local variable now
...     print x
...     
>>> func()
inner

Обычная ошибка:

>>> x = 'outer'
>>> def func():
...     print x       #this won't access the global `x`
...     x = 'inner'   #`x` is a local variable
...     print x
...     
>>> func()
...
UnboundLocalError: local variable 'x' referenced before assignment

Но когда вы используете global Заявление затем Python для поиска этой переменной в global объем.

Читайте: почему я получаю UnboundLocalError, когда переменная имеет значение?

nonlocal: Для вложенных функций вы можете использовать nonlocal инструкция в py3.x для изменения переменной, объявленной в функции включения.


Но классы работают по-разному, переменная x объявлено внутри класса A на самом деле становится A.x:

>>> x = 'outer'
>>> class A:
...    x += 'inside'  #use the value of global `x` to create a new attribute `A.x`
...    print x        #prints `A.x`
...     
outerinside
>>> print x
outer

Вы также можете получить доступ к атрибутам класса напрямую из глобальной области видимости:

>>> A.x
'outerinside'

С помощью global в классе:

>>> x = 'outer'
>>> class A:
...     global x
...     x += 'inner' #now x is not a class attribute, you just modified the global x
...     print x
...     
outerinner
>>> x
'outerinner'
>>> A.x
AttributeError: class A has no attribute 'x'

Функция getcha не вызовет ошибку в классах:

>>> x = 'outer'
>>> class A:
...     print x                      #fetch from globals or builitns
...     x = 'I am a class attribute' #declare a class attribute
...     print x                      #print class attribute, i.e `A.x`
...     
outer
I am a class attribute
>>> x
'outer'
>>> A.x
'I am a class attribute'

Правило LEGB: если нет global а также nonlocal Затем используется поиск Python в этом порядке.

>>> outer = 'global'
>>> def func():
        enclosing = 'enclosing'
        def inner():
                inner = 'inner'
                print inner           #fetch from (L)ocal scope
                print enclosing       #fetch from (E)nclosing scope
                print outer           #fetch from (G)lobal scope
                print any             #fetch from (B)uilt-ins
        inner()
...         
>>> func()
inner
enclosing
global
<built-in function any>

Из областей и пространств имен Python :

Важно понимать, что области действия определяются в текстовом виде: глобальная область действия функции, определенной в модуле, - это пространство имен этого модуля, независимо от того, откуда или каким псевдонимом вызывается функция. С другой стороны, фактический поиск имен выполняется динамически, во время выполнения - однако определение языка развивается в сторону статического разрешения имен, во время "компиляции", поэтому не полагайтесь на динамическое разрешение имен! (Фактически локальные переменные уже определены статически.)

Что означает, что сфера для x = x + 1 определяется статически, перед вызовом функции. А поскольку это присваивание, то "x" становится локальной переменной, а не ищется глобально.

Это также причина, почему from mod import * запрещено в функциях. Потому что интерпретатор не будет импортировать модули для вас во время компиляции, чтобы знать имена, которые вы используете в функции. т.е. он должен знать все имена, на которые ссылается функция во время компиляции.

Это правило, которому следует Python - привыкните к нему;-) Существует практическая причина: и читатель, и компилятор могут определить, какие переменные являются локальными, глядя только на функцию. Какие локальные имена не имеют никакого отношения к контексту, в котором появляется функция, и, как правило, очень хорошая идея следовать правилам, ограничивающим объем исходного кода, на который вы должны смотреть, чтобы ответить на вопрос.

Около:

Я предполагаю, что это происходит потому, что он не может найти x в своей локальной области видимости, поэтому он ищет в своей области видимости и находит x.

Не совсем: компилятор определяет во время компиляции, какие имена являются и не являются локальными. Там нет динамического "хм - это локальный или глобальный?" поиск продолжается во время выполнения. Точные правила изложены здесь.

Что касается того, почему вам не нужно объявлять имя global просто чтобы сослаться на его значение, мне нравится старый ответ Фредрика Лунда здесь. На практике действительно ценно, что global оператор предупреждает читателей кода о том, что функция может перепривязывать глобальное имя.

Потому что так оно и было задумано.

По сути, если у вас есть назначение где-либо в вашей функции, то эта переменная становится локальной для этой функции (если вы не использовали global, конечно).

Потому что это приведет к очень трудно выявлять ошибки!

Когда вы набираете это x = x + 1, вы могли иметь в виду увеличить значение x во внешней области видимости... или вы могли просто забыть, что вы уже использовали x где-то еще и пытались объявить локальную переменную.

Я бы предпочел, чтобы интерпретатор позволял вам изменять родительское пространство имен только в том случае, если вы собираетесь - таким образом, вы не можете сделать это случайно.

Я могу попытаться сделать обоснованное предположение, почему это так работает.

Когда Python встречает строку x = x + 1 в вашей функции, он должен решить, где искать x,

Можно сказать "первое появление x является глобальным, а второй локальным ", но это довольно неоднозначно (и, следовательно, противоречит философии Python). Это может быть частью синтаксиса, но это может привести к сложным ошибкам. Поэтому было решено быть последовательными в этом и обрабатывать все вхождения как глобальные или локальные переменные.

Есть назначение, поэтому если x должен был быть глобальным, был бы global заявление, но ничего не найдено.

Следовательно, x локально, но ни к чему не привязано, и все же оно используется в выражении x + 1, бросать UnboundLocalError,

В качестве дополнительного примера для вновь созданного Axe, созданного в классе. Переназначение x на "inner" внутри класса не обновляет глобальное значение x, потому что теперь оно является переменной класса.

x = 'outer'
class A:
    x = x
    print(x)
    x = 'inner'
    print(x)

print(x)
print(A.x)
Другие вопросы по тегам