Найти - Пользователи
Полная версия: Совпадение имён в локальной и объемлющих областях видимости
Начало » Python для новичков » Совпадение имён в локальной и объемлющих областях видимости
1
shau-kote
Всем доброго времени суток.

Наткнулся я тут на слегка непонятный момент в использовании nonlocal переменных.

Вот такой код:
def  f1():
    x = 0
    def f2():
        print(x)
        x = 1
        print(x)
    f2()
f1()
является нерабочим - вылетает с UnboundLocalError на первой строке f2(). Причём вылетает при попытке вызвать функцию f1(), а не при её создании (проверял в интерактивном шелле).

С другой стороны, такие варианты f2() вполне корректны:
def  f1():
    x = 0
    def f2():
        print(x)
        #x = 1
        print(x)
    f2()
def  f1():
    x = 0
    def f2():
        #print(x)
        x = 1
        print(x)
    f2()

Раньше я не сталкивался с отображением имён из объемлющей области видимости на локальную, но сейчас понял, что вообще не понимаю причин такого поведения.

Получается что, уже на первой строке f2() интерпретатор “знает” о том, что в функции есть (будет?) локальная переменная x и потому не даёт к ней обращаться?
Иначе я никак не могу не объяснить, почему в первой строке f() просто не происходит обращение к переменной таким именем в объемлющей области видимости.

Разъясните, пожалуйста, что здесь происходит?..
FishHook
Во втором питоне переменную, определенную в области видимости объемлющей функции можно читать внутри замыкания, но не изменять. В третьем вроде бы появился идентификатор nonlocal.

Для обхода этой неприятности во второй ветке можно использовать такую хитрость
def  f1():
    f1.x = 10
    def f2():
        print f1.x
        f1.x = 1
        print f1.x
    f2()
f1()
shau-kote
Да, я знаю. У меня у самого Python 3, но я не про nonlocal.
К примеру, С аналогичный код вполне работает - http://codepad.org/eroG1o3S
Первое обращение к x обрабатывается как обращение к глобальной переменной, второй - как к локальной, потому что уже есть локальная переменная x.

Здесь же такая логика почему-то не срабатывает. Интерпретатор считает x локальной переменной ещё до присваивания! Ерунда какая-то получается.
Для сравнения с приведённым выше примером на С - http://codepad.org/4SkOKG2D
FishHook
Ну питон - это все таки не С, особенно если вспомнить, что функции в питоне - это объекты.
Как тебе такой код?
x = 0
def f():
    x = 31
    r = 90
    print x
c = f.func_code
print zip(c.co_consts[1:], c.co_varnames)
shau-kote
Хм. То есть уже после выполнения инструкции def мы имеем объект-функцию, которая содержит список всех своих (будущих) локальных переменных, хотя им даже не присвоены значения?..
py.user.next
shau-kote
вылетает с UnboundLocalError на первой строке f2(). Причём вылетает при попытке вызвать функцию f1()

эти срабатывают
>>> def f1():
...     x = 0
...     def f2():
...         print(x)
...         x = 1
...         print(x)
...     #f2()
... 
>>> f1()
>>> 

>>> def f1():
...     x = 0
...     def f2():
...         nonlocal x
...         print(x)
...         x = 1
...         print(x)
...     f2()
... 
>>> f1()
0
1
>>>

смотрим области у несрабатывающей
>>> def f1():
...     x = 0
...     def f2():
...         print(globals(), locals())
...         print(x)
...         x = 1
...         print(x)
...     f2()
... 
>>> f1()
{'__doc__': None, 'f1': <function f1 at 0x7f2eceb3ad40>, '__builtins__': <module 'builtins'>, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x7f2eceacb810>, '__name__': '__main__', 'clear': <function clear at 0x7f2eceb3acb0>, '__package__': None, '__cached__': None} {}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in f1
  File "<stdin>", line 5, in f2
UnboundLocalError: local variable 'x' referenced before assignment
>>>

во втором питоне - тоже не срабатывает
>>> def f1():
...     x = 0
...     def f2():
...         print globals(), locals()
...         print x
...         x = 1
...         print x
...     f2()
... 
>>> f1()
{'f1': <function f1 at 0x7f6a8871e938>, '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, 'clear': <function clear at 0x7f6a8871e8c0>, '__name__': '__main__', '__doc__': None} {}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in f1
  File "<stdin>", line 5, in f2
UnboundLocalError: local variable 'x' referenced before assignment
>>>

пример для C на питоне
напрямую не работает, потому что области по-другому сделаны (в C нет вложенных функций)
>>> x = 0
>>>     
... def f1():
...     def f2():
...         print(globals(), locals())
...         print(x)
...         x = 1
...         print(x)
...     f2()
... 
>>> f1()
{'__loader__': <_frozen_importlib.SourceFileLoader object at 0x7f70a5d5c810>, 'f1': <function f1 at 0x7f70a5dcbd40>, 'x': 0, '__package__': None, '__builtins__': <module 'builtins'>, '__doc__': None, '__name__': '__main__', 'clear': <function clear at 0x7f70a5dcbcb0>, '__cached__': None} {}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in f1
  File "<stdin>", line 5, in f2
UnboundLocalError: local variable 'x' referenced before assignment
>>>

разделяем переменные и смотрим области
>>> x0 = 0
>>>     
... def f1():
...     x = 0
...     def f2():
...         print(globals(), locals())
...         print(x0)
...         x = 1
...         print(x)
...         print(globals(), locals())
...     f2()
... 
>>> f1()
{'__loader__': <_frozen_importlib.SourceFileLoader object at 0x7f70a5d5c810>, 'f1': <function f1 at 0x7f70a5d6f170>, 'x': 0, '__package__': None, '__builtins__': <module 'builtins'>, '__doc__': None, '__name__': '__main__', 'clear': <function clear at 0x7f70a5dcbcb0>, 'x0': 0, '__cached__': None} {}
0
1
{'__loader__': <_frozen_importlib.SourceFileLoader object at 0x7f70a5d5c810>, 'f1': <function f1 at 0x7f70a5d6f170>, 'x': 0, '__package__': None, '__builtins__': <module 'builtins'>, '__doc__': None, '__name__': '__main__', 'clear': <function clear at 0x7f70a5dcbcb0>, 'x0': 0, '__cached__': None} {'x': 1}
>>>

влияем на области с помощью присваивания
>>> def f1():
...     x = 0
...     def f2():
...         print(globals(), locals())
...         print(x)
...         #x = 1
...         print(x)
...     f2()
... 
>>> f1()
{'f1': <function f1 at 0x7f9a5aafed40>, '__package__': None, 'clear': <function clear at 0x7f9a5aafecb0>, '__doc__': None, '__loader__': <_frozen_importlib.SourceFileLoader object at 0x7f9a5aa8f810>, '__builtins__': <module 'builtins'>, '__name__': '__main__', '__cached__': None} {'x': 0}
0
0
>>>

вот из-за того, что есть присваивание и нет nonlocal, он её считает локальной
а локальные переменные он ищет в локальной области

какое-то наследование локальной области есть ещё: видимо, когда у функции нет локальных переменных, то она наследует локальную область от внешней функции
bismigalis
нашел интересную статью в тему http://blog.amir.rachum.com/post/55024295793/python-common-newbie-mistakes-part-2
shau-kote
bismigalis, спасибо за статью.
Вот это кусок:
What really happens here is that the local scope is in fact not completely dynamic. When the def statement is executed, Python statically gathers information regarding the local scope of the function.
расставил всё по местам.


UPD
Последний вопрос. Подобное поведение - “статичность” локальных областей видимости - имеет какие-то причины? Ну, кроме, облегчения жизни интерпретатору.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB