Подтвердить что ты не робот

Как ссылки на переменные разрешаются в Python

Это сообщение немного длинное со многими примерами, но я надеюсь, что это поможет мне и другим лучше понять всю историю переменных и поиск атрибутов в Python 2.7.

Я использую термины PEP 227 (http://www.python.org/dev/peps/pep-0227/) для блоков кода (таких как модули, определение класса, определения функций и т.д.) и привязки переменных (например, назначения, объявления аргументов, класс и объявление функции, для циклов и т.д.)

Я использую термины переменные для имен, которые можно вызывать без точка и атрибуты для имен, которые должны быть квалифицированы с помощью объекта name (например, obj.x для атрибута x объекта obj).

Для Python для всех блоков кода существует три области действия, но функции:

  • Local
  • Global
  • Builtin

Для Python существует только четыре блока для функций (согласно PEP 227):

  • Local
  • Функции закрытия
  • Global
  • Builtin

Правило для привязки переменной и ее нахождения в блоке довольно просто:

  • любое связывание переменной с объектом в блоке делает эту переменную локально к этому блоку, если переменная не объявлена ​​глобальной (в этом случай, когда переменная принадлежит глобальной области)
  • ссылка на переменную просматривается с использованием правила LGB (локального, глобальный, встроенный) для всех блоков, но функции
  • ссылка на переменную просматривается с использованием правила LEGB (local, охватывающий, глобальный, встроенный) только для функций.

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

пример 1:

x = "x in module"
class A():
    print "A: "  + x                    #x in module
    x = "x in class A"
    print locals()
    class B():
        print "B: " + x                 #x in module
        x = "x in class B"
        print locals()
        def f(self):
            print "f: " + x             #x in module
            self.x = "self.x in f"
            print x, self.x
            print locals()

>>>A.B().f()
A: x in module
{'x': 'x in class A', '__module__': '__main__'}
B: x in module
{'x': 'x in class B', '__module__': '__main__'}
f: x in module
x in module self.x in f
{'self': <__main__.B instance at 0x00000000026FC9C8>}

В классах нет ограничений (правило LGB) и функция в класс не может получить доступ к атрибутам класса, не используя квалифицированное имя (self.x в этом примере). Это хорошо описано в PEP227.

Пример 2:

z = "z in module"
def f():
    z = "z in f()"
    class C():
        z = "z in C"
        def g(self):
            print z
            print C.z
    C().g()
f()
>>> 
z in f()
z in C

Здесь переменные в функциях просматриваются с использованием правила LEGB, но если класс находится в пути, аргументы класса пропускаются. Снова здесь, это то, что объясняет PEP 227.

Пример 3:

var = 0
def func():
    print var
    var = 1
>>> func()

Traceback (most recent call last):
  File "<pyshell#102>", line 1, in <module>
func()
  File "C:/Users/aa/Desktop/test2.py", line 25, in func
print var
UnboundLocalError: local variable 'var' referenced before assignment

Мы ожидаем, что с динамическим языком, таким как python, все будет разрешено динамически. Но это не относится к функциям. Местный переменные определяются во время компиляции. PEP 227 и http://docs.python.org/2.7/reference/executionmodel.html описывают это поведение таким образом

"Если операция привязки имени происходит где угодно внутри блока кода, все использование имени внутри блока рассматривается как ссылка на текущий блок. "

пример 4:

x = "x in module"
class A():
    print "A: " + x
    x = "x in A"
    print "A: " + x
    print locals()
    del x
    print locals()
    print "A: " + x
>>> 
A: x in module
A: x in A
{'x': 'x in A', '__module__': '__main__'}
{'__module__': '__main__'}
A: x in module

Но мы видим здесь, что это утверждение в PEP227 "Если привязка имени операция выполняется в любом месте внутри кодового блока, все использования имени внутри блока рассматриваются как ссылки на текущий блок. " неправильно, когда блок кода является классом. Более того, для классов, кажется что привязка локального имени не выполняется во время компиляции, но во время выполнение с использованием пространства имен классов. В этом отношении, PEP227 и модель исполнения в документе Python вводит в заблуждение и для некоторые части ошибочны.

Пример 5:

x = 'x in module'
def  f2():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        class MyClass(object):
            x = x
            print x
        return MyClass
    myfunc()
f2()
>>> 
x in module

мое понимание этого кода следующее. Инструкция x = x сначала найдите объект правой рукой x выражения. к. В этом случае объект просматривается локально в классе, тогда следуя правилу LGB, он просматривается в глобальном масштабе, что строка 'x в модуле'. Тогда локальный атрибут x для MyClass созданный в словаре классов и указывающий на объект string.

пример 6:

Теперь вот пример, который я не могу объяснить. Это очень близко к примеру 5, я просто меняю локальный MyClass атрибут от x до y.

x = 'x in module'
def  f2():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        class MyClass(object):
            y = x
            print y
        return MyClass
    myfunc()
f2()
>>>
x in myfunc

Почему в этом случае ссылка x в MyClass просматривается в самая внутренняя функция?

4b9b3361

Ответ 1

В двух словах разница между примером 5 и примером 6 заключается в том, что в примере 5 переменная x также назначается в той же области действия, но не в примере 6. Это вызывает разницу, которая может быть понята историческим причины.

Это вызывает UnboundLocalError:

x = "foo"
def f():
    print x
    x = 5
f()

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

x = "foo"
def f():
    if some_condition:
        x = 42
    print x
f()

Теперь это правило не работает для областей класса: там мы хотим, чтобы выражения типа x = x работали, захватывая глобальную переменную x в области уровня класса. Это означает, что области уровня класса не следуют основному правилу выше: мы не можем знать, относится ли x к этой области к некоторой внешней переменной или к локально определенному x --- например:

class X:
    x = x     # we want to read the global x and assign it locally
    bar = x   # but here we want to read the local x of the previous line

class Y:
    if some_condition:
        x = 42
    print x     # may refer to either the local x, or some global x

class Z:
    for i in range(2):
        print x    # prints the global x the 1st time, and 42 the 2nd time
        x = 42

Таким образом, в классах используется другое правило: там, где он обычно поднимает UnboundLocalError --- и только в этом случае - вместо этого он ищет глобальные возможности модуля. Это все: он не соответствует цепочке вложенных областей.

Почему бы и нет? Я действительно сомневаюсь, что есть лучшее объяснение, что "по историческим причинам". В более технических терминах он мог бы считать, что переменная x как локально определена в области видимости класса (поскольку она назначена), так и должна быть передана из родительской области как лексически вложенная переменная (потому что она читается). Было бы возможно реализовать его, используя другой байт-код, чем LOAD_NAME, который ищет в локальной области, и возвращается к использованию ссылки вложенной области, если не найден.

EDIT: благодарит wilberforce за ссылку http://bugs.python.org/issue532860. Возможно, у нас будет возможность возобновить обсуждение с предлагаемым новым байт-кодом, если мы почувствуем, что он должен быть исправлен в конце концов (отчет об ошибке рассматривает возможность убийства x = x, но закрыт из-за боязни сломать слишком много существующего кода; то, что я предлагаю здесь, будет заключаться в том, чтобы сделать работу x = x в большем числе случаев). Или мне может не хватать другой тонкой точки...

EDIT2: кажется, что CPython сделал именно это в текущем тубе 3.4: http://bugs.python.org/issue17853... или нет? Они ввели байт-код по несколько иной причине и не используют его систематически...

Ответ 2

В идеальном мире вы были бы правы, и некоторые из несоответствий, которые вы обнаружили, были бы неправильными. Тем не менее, CPython оптимизировал некоторые сценарии, особенно функции locals. Эти оптимизации, вместе с тем, как взаимодействует цикл компилятора и оценки и исторический прецедент, приводят к путанице.

Python преобразует код в байт-коды, а затем интерпретируется контуром интерпретатора. "Обычный" код операции для доступа к имени - LOAD_NAME, который ищет имя переменной, как в словаре. LOAD_NAME сначала ищет имя как локальное, и если это не удается, он ищет глобальный. LOAD_NAME генерирует исключение NameError, если имя не найдено.

Для вложенных областей поиск имен за пределами текущей области осуществляется с помощью закрытий; если имя не назначено, но доступно во вложенной (не глобальной) области, то такие значения обрабатываются как закрытие. Это необходимо, поскольку родительская область может содержать разные значения для заданного имени в разное время; два вызова родительской функции могут приводить к различным значениям закрытия. Таким образом, Python имеет коды LOAD_CLOSURE, MAKE_CLOSURE и LOAD_DEREF для этой ситуации; первые два кода операций используются при загрузке и создании замыкания для вложенной области, а LOAD_DEREF будет загружать значение закрытого значения, когда ему нужна вложенная область.

Теперь LOAD_NAME относительно медленный; он будет консультироваться с двумя словарями, что означает, что он должен сначала хэшировать ключ и выполнить несколько тестов на равенство (если имя не было интернировано). Если имя не является локальным, то оно должно сделать это снова для глобального. Для функций, которые потенциально можно назвать десятками тысяч раз, это может стать утомительным. Таким образом, функции locals имеют специальные коды операций. Загрузка локального имени осуществляется LOAD_FAST, который ищет локальные переменные по индексу в специальном массиве локальных имен. Это намного быстрее, но для этого требуется, чтобы компилятор сначала обнаружил, является ли имя локальным, а не глобальным. Чтобы все еще иметь возможность искать глобальные имена, используется другой код операции LOAD_GLOBAL. Компилятор явно оптимизирует для этого случая создание специальных кодов операций. LOAD_FAST будет генерировать исключение UnboundLocalError, если еще нет значения для имени.

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

Итак, для не-функциональных областей LOAD_NAME и LOAD_DEREF используются для локальных и глобальных переменных и для закрытий соответственно. Для функций используются LOAD_FAST, LOAD_GLOBAL и LOAD_DEREF.

Обратите внимание, что тела классов выполняются, как только Python выполняет строку class! Таким образом, в примере 1, class B внутри class A выполняется, как только выполняется class A, то есть при импорте модуля. В примере 2 C не выполняется до тех пор, пока не будет вызываться f(), а не раньше.

Давайте рассмотрим ваши примеры:

  • В классе A вы вложили класс A.B. Тела класса не образуют вложенные области, поэтому даже если тело класса A.B выполняется, когда выполняется класс A, компилятор будет использовать LOAD_NAME для поиска x. A.B().f() - это функция (связанная с экземпляром B() как метод), поэтому она использует LOAD_GLOBAL для загрузки x. Мы будем игнорировать доступ к атрибутам здесь, это очень четко определенный шаблон имени.

  • Здесь f().C.z находится в области класса, поэтому функция f().C().g() пропустит область C и рассмотрит область f(), используя LOAD_DEREF.

  • Здесь var определяется как локальный компилятором, потому что вы назначаете его в пределах области. Функции оптимизированы, поэтому LOAD_FAST используется для поиска локального объекта и генерируется исключение.

  • Теперь все становится немного странным. class A выполняется в области класса, поэтому LOAD_NAME используется. A.x был удален из словаря locals для области, поэтому второй доступ к x приводит к тому, что вместо этого будет найден глобальный x; LOAD_NAME искал локальный первый и не нашел его там, возвращаясь к глобальному поиску.

    Да, это не соответствует документации. Python-the-language и CPython - реализация здесь немного конфликтуют. Вы, однако, выдвигаете границы того, что возможно и практично на динамическом языке; проверка того, что x должен был быть локальным в LOAD_NAME, был бы возможен, но требует драгоценного времени выполнения для углового случая, который большинство разработчиков никогда не столкнется.

  • Теперь вы запутываете компилятор. Вы использовали x = x в области видимости класса, и, таким образом, вы устанавливаете локальное имя из области вне области. Компилятор считает, что x является локальным здесь (вы его назначаете), поэтому он никогда не считает, что это также может быть имя области действия. Компилятор использует LOAD_NAME для всех ссылок на x в этой области, потому что это не оптимизированное тело функции.

    При выполнении определения класса x = x сначала требуется поиск x, поэтому для этого используется LOAD_NAME. Нет x, LOAD_NAME не находит локального, поэтому глобальный x найден. Полученное значение сохраняется как локальное, которое также называется x. print x снова использует LOAD_NAME и теперь находит новое локальное значение x.

  • Здесь вы не путаете компилятор. Вы создаете локальный y, x не является локальным, поэтому компилятор распознает его как имя области с родительской функцией f2().myfunc(). x просматривается с помощью LOAD_DEREF из замыкания и сохраняется в y.

Вы могли видеть путаницу между 5 и 6 как ошибку, хотя и не заслуживающую внимания, по моему мнению. Он был, конечно, подан как таковой, см. issue 532860 в отладчике ошибок Python, он существует уже более 10 лет.

Компилятор может проверить имя области x, даже если x также является локальным, для этого первого присваивания в примере 5. Или LOAD_NAME может проверить, является ли это имя локальным, и выбросить UnboundLocalError, если локальный не найден, за счет большей производительности. Если бы это было в области функций, LOAD_FAST использовалось бы, например, 5, а UnboundLocalError было бы немедленно отправлено.

Однако, как показывает ссылка, по историческим причинам поведение сохраняется. Вероятно, сегодня существует код, который будет ломаться, если бы эта ошибка была исправлена.

Ответ 3

Короче говоря, это угловой пример Python, который немного противоречив, но должен быть сохранен для обратной совместимости (и потому, что это не ясно, какой должен быть правильный ответ). Вы можете увидеть много оригинальной дискуссии об этом в списке рассылки Python, когда PEP 227 был реализован, а некоторые в bug, для которого это поведение является исправлением.

Мы можем решить, почему существует разница с помощью модуля dis, который позволяет нам заглянуть внутрь объектов кода, чтобы увидеть байт-код, который был скомпилирован фрагмент кода к. Я на Python 2.6, поэтому детали этого могут быть немного разными - но я вижу такое же поведение, поэтому я думаю, что он, вероятно, достаточно близко к 2.7.

Код, инициализирующий каждый вложенный MyClass, живет в объекте кода, который вы можете получить через атрибуты функций верхнего уровня. (Я переименовываю функции из примера 5 и примера 6 в f1 и f2 соответственно.)

Объект кода имеет кортеж co_consts, который содержит объект кода myfunc, который, в свою очередь, имеет код, который запускается при создании MyClass:

In [20]: f1.func_code.co_consts
Out[20]: (None,
 'x in f2',
 <code object myfunc at 0x1773e40, file "<ipython-input-3-6d9550a9ea41>", line 4>)
In [21]: myfunc1_code = f1.func_code.co_consts[2]
In [22]: MyClass1_code = myfunc1_code.co_consts[3]
In [23]: myfunc2_code = f2.func_code.co_consts[2]
In [24]: MyClass2_code = myfunc2_code.co_consts[3]

Затем вы можете увидеть разницу между ними в байт-коде, используя dis.dis:

In [25]: from dis import dis
In [26]: dis(MyClass1_code)
  6           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  7           6 LOAD_NAME                2 (x)
              9 STORE_NAME               2 (x)

  8          12 LOAD_NAME                2 (x)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 LOAD_LOCALS         
             18 RETURN_VALUE        

In [27]: dis(MyClass2_code)
  6           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  7           6 LOAD_DEREF               0 (x)
              9 STORE_NAME               2 (y)

  8          12 LOAD_NAME                2 (y)
             15 PRINT_ITEM          
             16 PRINT_NEWLINE       
             17 LOAD_LOCALS         
             18 RETURN_VALUE        

Таким образом, единственное различие заключается в том, что в MyClass1, x загружается с помощью LOAD_NAME op, а в MyClass2 - с помощью LOAD_DEREF. LOAD_DEREF ищет имя в закрывающей области, поэтому он получает "x в myfunc". LOAD_NAME не соответствует вложенным областям - поскольку он не может видеть имена x, связанные в myfunc или f1, он получает привязку на уровне модуля.

Тогда возникает вопрос: почему код двух версий MyClass получается скомпилированным для двух разных кодов операций? В f1 привязка затеняет x в области видимости класса, а в f2 - привязывает новое имя. Если области MyClass были вложенными функциями вместо классов, строка y = x в f2 была бы скомпилирована одинаково, но x = x in f1 была бы LOAD_FAST - это потому, что компилятор знайте, что x связан в функции, поэтому для извлечения локальной переменной следует использовать LOAD_FAST. Это произойдет с помощью UnboundLocalError, когда он был вызван.

In [28]:  x = 'x in module'
def  f3():
    x = 'x in f2'
    def myfunc():
        x = 'x in myfunc'
        def MyFunc():
            x = x
            print x
        return MyFunc()
    myfunc()
f3()
---------------------------------------------------------------------------
Traceback (most recent call last)
<ipython-input-29-9f04105d64cc> in <module>()
      9         return MyFunc()
     10     myfunc()
---> 11 f3()

<ipython-input-29-9f04105d64cc> in f3()
      8             print x
      9         return MyFunc()
---> 10     myfunc()
     11 f3()

<ipython-input-29-9f04105d64cc> in myfunc()
      7             x = x
      8             print x
----> 9         return MyFunc()
     10     myfunc()
     11 f3()

<ipython-input-29-9f04105d64cc> in MyFunc()
      5         x = 'x in myfunc'
      6         def MyFunc():
----> 7             x = x
      8             print x
      9         return MyFunc()

UnboundLocalError: local variable 'x' referenced before assignment

Это не работает, потому что функция myfunc использует LOAD_FAST:

In [31]: myfunc_code = f3.func_code.co_consts[2]
MyFunc_code = myfunc_code.co_consts[2]
In [33]: dis(MyFunc_code)
  7           0 LOAD_FAST                0 (x)
              3 STORE_FAST               0 (x)

  8           6 LOAD_FAST                0 (x)
              9 PRINT_ITEM          
             10 PRINT_NEWLINE       
             11 LOAD_CONST               0 (None)
             14 RETURN_VALUE        

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