Уведомления

Группа в Telegram: @pythonsu

#1 Июль 9, 2013 04:24:41

shau-kote
Зарегистрирован: 2013-02-17
Сообщения: 17
Репутация: +  0  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

Всем доброго времени суток.

Наткнулся я тут на слегка непонятный момент в использовании 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() просто не происходит обращение к переменной таким именем в объемлющей области видимости.

Разъясните, пожалуйста, что здесь происходит?..

Отредактировано shau-kote (Июль 9, 2013 04:25:37)

Офлайн

#2 Июль 9, 2013 05:49:21

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

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

Для обхода этой неприятности во второй ветке можно использовать такую хитрость

def  f1():
    f1.x = 10
    def f2():
        print f1.x
        f1.x = 1
        print f1.x
    f2()
f1()



Офлайн

#3 Июль 9, 2013 06:11:26

shau-kote
Зарегистрирован: 2013-02-17
Сообщения: 17
Репутация: +  0  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

Да, я знаю. У меня у самого Python 3, но я не про nonlocal.
К примеру, С аналогичный код вполне работает - http://codepad.org/eroG1o3S
Первое обращение к x обрабатывается как обращение к глобальной переменной, второй - как к локальной, потому что уже есть локальная переменная x.

Здесь же такая логика почему-то не срабатывает. Интерпретатор считает x локальной переменной ещё до присваивания! Ерунда какая-то получается.
Для сравнения с приведённым выше примером на С - http://codepad.org/4SkOKG2D

Отредактировано shau-kote (Июль 9, 2013 06:11:56)

Офлайн

#4 Июль 9, 2013 07:39:16

FishHook
От:
Зарегистрирован: 2011-01-08
Сообщения: 8312
Репутация: +  568  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

Ну питон - это все таки не С, особенно если вспомнить, что функции в питоне - это объекты.
Как тебе такой код?

x = 0
def f():
    x = 31
    r = 90
    print x
c = f.func_code
print zip(c.co_consts[1:], c.co_varnames)



Офлайн

#5 Июль 9, 2013 09:43:06

shau-kote
Зарегистрирован: 2013-02-17
Сообщения: 17
Репутация: +  0  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

Хм. То есть уже после выполнения инструкции def мы имеем объект-функцию, которая содержит список всех своих (будущих) локальных переменных, хотя им даже не присвоены значения?..

Офлайн

#6 Июль 10, 2013 01:40:07

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 10016
Репутация: +  857  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

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, он её считает локальной
а локальные переменные он ищет в локальной области

какое-то наследование локальной области есть ещё: видимо, когда у функции нет локальных переменных, то она наследует локальную область от внешней функции



Отредактировано py.user.next (Июль 10, 2013 02:16:55)

Офлайн

#7 Июль 10, 2013 14:51:34

bismigalis
Зарегистрирован: 2010-10-02
Сообщения: 449
Репутация: +  47  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

нашел интересную статью в тему http://blog.amir.rachum.com/post/55024295793/python-common-newbie-mistakes-part-2

Офлайн

#8 Июль 11, 2013 09:28:24

shau-kote
Зарегистрирован: 2013-02-17
Сообщения: 17
Репутация: +  0  -
Профиль   Отправить e-mail  

Совпадение имён в локальной и объемлющих областях видимости

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
Последний вопрос. Подобное поведение - “статичность” локальных областей видимости - имеет какие-то причины? Ну, кроме, облегчения жизни интерпретатору.

Отредактировано shau-kote (Июль 11, 2013 10:16:59)

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version