Переменная область и разрешение имен в Python

Я думаю, что я принципиально не понимаю, как Python делает такие вещи, как переменная область видимости и разрешение имен. В частности, тот факт, что функция broken() ниже не работает, действительно удивляет меня. И хотя я какое-то время ловил рыбу в Интернете в поисках полезного объяснения, но я до сих пор не понимаю. Может кто-нибудь объяснить или дать ссылку на хорошее описание того, как этот материал работает в Python, с достаточным количеством подробностей, чтобы было очевидно, почему broken() не работает после прочтения соответствующих материалов?

# Why does this code work fine
def okay0():
    def foo():
        L = []
        def bar():
            L.append(5)
        bar()
        return L
    foo()

# and so does this
def okay1():
    def foo():
        def bar():
            L.append(5)
        L = []
        bar()
        return L
    foo()

# but the following code raises an exception?
def broken():
    def foo():
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()

# Example
test_list = [okay0, okay1, broken]
for test_function in test_list:
    try:
        test_function()
    except:
        print("broken")
    else:
        print("okay")
24
задан wjandrea 5 September 2019 в 17:26
поделиться

6 ответов

Это более просто, чем это смотрит.

первый случай является, вероятно, самым очевидным:

 def okay0():
    def foo():
        L = []
        def bar():
            L.append(5)
        bar()
        return L
    foo()

Здесь все, что Вы имеете, является обычными правилами объема. L и bar принадлежат тому же объему, и L объявляется сначала. Так bar() может получить доступ L.

второй образец также подобен:

def okay1():
    def foo():
        def bar():
            L.append(5)
        L = []
        bar()
        return L
    foo()

Здесь и L и bar() принадлежат тому же объему. Они локальны для [1 110]. Это может выглядеть по-другому, потому что Python использует динамическое связывание. Таким образом, разрешение имени L в [1 112] только разрешено, когда функция вызвана. К тому времени Python уже знает, что L локальная переменная к той же функции, которая содержит foo(), таким образом, доступ допустим.

Однако, в то время как Python имеет динамическое связывание, он делает не , имеют динамический контекст, таким образом, это перестанет работать:

def broken():
    def foo():
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()

Здесь, существует две переменные, названные L. Каждый локален для [1 116], и другой локален для [1 117]. Так как эти функции не вкладываются, и Python не имеет динамического контекста, они - две различных переменные. Поскольку bar() не использует L в присвоении, Вы получаете исключение.

6
ответ дан 28 November 2019 в 07:52
поделиться

Поврежденный () функция бросает следующую ошибку:

NameError: name 'L' is not defined

Это - потому что L определяется в нечто () и локален для той функции. Когда Вы пытаетесь сослаться на него в некоторой другой функции, такой как панель (), это не будет определенным.

def broken():
    def foo():
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()

В основном, если Вы объявляете переменную в функции, это будет локальным для той функции....

2
ответ дан 28 November 2019 в 07:52
поделиться

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

В Вашем конкретном случае, L всегда определяется в foo(). На первых двух примерах, bar() определяется в foo() также, таким образом, это может получить доступ L по правилу выше (т.е., foo() bar() родитель).

Однако на broken(), bar() и foo() одноуровневые элементы. Они не знают ничего из объемов друг друга, таким образом bar() не видят L.

От документация :

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

  • самый внутренний объем, который ищется сначала, содержит локальные имена
  • объемы каких-либо функций включения, которые ищутся, начиная с ближайшего объема включения, содержит нелокальные, но также и неглобальные имена
  • , предпоследний объем содержит текущие module’s глобальные имена
  • наиболее удаленный объем (искавший в последний раз), пространство имен содержит встроенные имена

Теперь, почему okay1 работает, если L определяется дословно после bar()?

Python не пытается разрешить идентификаторы, пока он не должен на самом деле выполнять код ( динамическое связывание , как объяснено в ответе @Giusti).

, Когда Python добирается для выполнения функции, он видит идентификатор L и ищет его на локальном пространстве имен. На cpython реализации это - фактический словарь, таким образом, это считает словарь для ключа, названного L.

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

Примечание, которое, даже если L , определило после bar(), когда bar() названы , L, было уже определено. Так, когда bar() выполняется, L уже существует на локальном пространстве имен [1 123], который ищется, когда Python не видит L в [1 125].

часть Поддержки документации:

пространство имен А является отображением от имен до объектов. Большинство пространств имен в настоящее время реализуется как словари Python, но that’s, обычно не примечательный всегда (за исключением производительности), и это может измениться в будущем.

(...)

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

объемом А является текстовый регион программы Python, где пространство имен непосредственно доступно. “Directly accessible” здесь означает, что неполная ссылка на имя пытается найти имя в пространстве имен.

18
ответ дан 28 November 2019 в 07:52
поделиться

Самое важное понятие, которое Вы хотите знать, environment evaluation model, который прост, но мощен.

Позволяют мне предложить Вам пользу материал .

, Если Вы хотите прочитать документ Python, можно читать 4. Модель выполнения — документация Python 3.7.4 , это очень кратко.

, Когда имя используется в блоке кода, оно разрешено с помощью ближайшего объема включения. Набор всех таких объемов, видимых к блоку кода, называют средой block’sВ .

1
ответ дан 28 November 2019 в 07:52
поделиться

Строка с L = ... в fixed объявляет L в fixed объем. (return, прежде чем это удостоверится, присвоение на самом деле не выполняется, просто используется для определения объема.) Строка с nonlocal L объявляет, что L внутренний foo относится к внешнему объему L, в этом случае, fixed. Иначе, так как присвоение на [1 111] существует в foo, оно относилось бы к L переменная в foo.

В основном:

  • присвоение на переменную делает, оно определило объем к функции включения.
  • А nonlocal или global объявление переопределяет объем, вместо этого с помощью (самый внутренний? наиболее удаленный?) определяют объем с объявленной переменной или глобальная область видимости, соответственно.
def fixed():
    def foo():
        nonlocal L  # Added
        L = []
        bar()
        return L
    def bar():
        L.append(5)
    foo()
    return  # Added
    L = ...  # Added
1
ответ дан 28 November 2019 в 07:52
поделиться

Честно, я думаю, что существующие ответы сверхусложняют вещи. Несомненно, информация там, но трудно понять для неспециалиста.

ключевой пункт - это: под системой, названной , статический обзор , что Python (и большая часть другого современного использования языков программирования), отношения между именами переменной и ячейками памяти определяется местом, которое функция , определил , не место, где это названо . Это в отличие от динамического обзора, в котором отношения между именами переменной и ячейками памяти определяются местом, функция вызвана, не, где это определяется. Таким образом как caxcaxcoatl объясняет, функция broken() не работает, потому что, в том контексте, bar() и foo() одноуровневые элементы и таким образом не знают ничего из объемов друг друга. Но базовая причина этого не состоит в том, что broken() сбои для работы на всех мыслимых языках программирования, а скорее что Python (и большинство других современных языков программирования) использует одну конвенцию обзора вместо другого.

0
ответ дан 28 November 2019 в 07:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: