Отладчик iPython вызывает `NameError: name ... не определено` - PullRequest
0 голосов
/ 04 июля 2018

Я не могу понять следующее исключение, которое возникает в этом сеансе отладчика Python:

(Pdb) p [move for move in move_values if move[0] == max_value]
*** NameError: name 'max_value' is not defined
(Pdb) [move for move in move_values]
[(0.5, (0, 0)), (0.5, (0, 1)), (0.5, (0, 2)), (0.5, (1, 0)), (0.5, (1, 1)), (0.5, (1, 2)), (0.5, (2, 0)), (0.5, (2, 1)), (0.5, (2, 2))]
(Pdb) max_value
0.5
(Pdb) (0.5, (0, 2))[0] == max_value
True
(Pdb) [move for move in move_values if move[0] == 0.5]
[(0.5, (0, 0)), (0.5, (0, 1)), (0.5, (0, 2)), (0.5, (1, 0)), (0.5, (1, 1)), (0.5, (1, 2)), (0.5, (2, 0)), (0.5, (2, 1)), (0.5, (2, 2))]
(Pdb) [move for move in move_values if move[0] == max_value]
*** NameError: name 'max_value' is not defined

Почему иногда мне говорят, что max_value не определено, а иногда нет?

Кстати, это код непосредственно перед запуском отладчика:

max_value = max(move_values)[0]
best_moves = [move for move in move_values if move[0] == max_value]
import pdb; pdb.set_trace()

Я использую Python 3.6, работающий в PyCharm.

ИЗМЕНЕНО ОБНОВЛЕНИЕ:

После дополнительного тестирования кажется, что локальные переменные не видны в списках в сеансе pdb, когда я делаю следующее из iPython REPL или в PyCharm:

$ ipython
Python 3.6.5 | packaged by conda-forge | (default, Apr  6 2018, 13:44:09) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import pdb; pdb.set_trace()
--Call--
> /Users/billtubbs/anaconda/envs/py36/lib/python3.6/site-packages/IPython/core/displayhook.py(247)__call__()
-> def __call__(self, result=None):
(Pdb) x = 1; [x for i in range(3)]
*** NameError: name 'x' is not defined

Но в обычном Python REPL это работает:

$ python
Python 3.6.5 | packaged by conda-forge | (default, Apr  6 2018, 13:44:09) 
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pdb; pdb.set_trace()
--Return--
> <stdin>(1)<module>()->None
(Pdb) x = 1; [x for i in range(3)]
[1, 1, 1]

Я тестировал выше с версиями 3.4, 3.5, 3.6, поэтому он не зависит от версии.

ОБНОВЛЕНИЕ 2

Обратите внимание, что вышеуказанный тест («ИЗМЕНЕННОЕ ОБНОВЛЕНИЕ») проблематичен, поскольку в интерактивном REPL используется import pdb; pdb.set_trace().

Кроме того, исходная проблема не ограничивается iPython.

См. ответ пользователя2357112 ниже для подробного объяснения того, что здесь происходит.

Извините, если я вызвал замешательство!

1 Ответ

0 голосов
/ 24 января 2019

У вас есть две основные проблемы здесь. Во-первых, (при интерактивном вызове pdb.set_trace() в IPython) вы отлаживаете кишки IPython вместо желаемой области. Во-вторых, правила области видимости списков плохо взаимодействуют со случаями, когда переменные, присутствующие во вложенных областях, не могут быть определены статически, например, в отладчиках или телах классов .

Первая проблема в значительной степени возникает только при вводе pdb.set_trace() в интерактивную подсказку IPython, что не очень полезно, поэтому самый простой способ избежать этой проблемы - просто не делать этого. Если вы все равно хотите это сделать, вы можете несколько раз ввести команду r, пока pdb не скажет, что вы не в духе IPython. (Не промахивайтесь, иначе вы попадете в другую часть интуиции IPython.)

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


Здесь приведена полная комбинация эффектов.

  1. pdb.set_trace() запускает pdb при следующем событии трассировки , а не в точке, где вызывается pdb.set_trace().

Механизм функции трассировки , используемый pdb и другими отладчиками Python, запускается только при определенных определенных событиях, и «когда функция трассировки установлена», к сожалению, не является одним из этих событий. Обычно следующее событие является либо событием 'line' для следующей строки, либо событием 'return' для конца выполнения текущего объекта кода, но здесь это не так.

  1. IPython устанавливает displayhook для настройки обработки выражений.

Механизм, который Python использует для отображения результата операторов выражений, sys.displayhook. Когда вы делаете 1+2 в интерактивном режиме:

>>> 1+2
3

sys.displayhook - это то, что печатает 3 вместо того, чтобы выбросить его. Он также устанавливает _. Когда результатом выражения выражения является None, например, с выражением pdb.set_trace(), sys.displayhook ничего не делает, но все равно вызывается.

IPython заменяет sys.displayhook собственным настраиваемым обработчиком, отвечающим за печать Out[n]: штук, за настройку записей в записи Out, за вызов настраиваемой симпатичной печати IPython и за все другие удобства IPython. Для наших целей важно, чтобы displayyok IPython был написан на Python, поэтому следующее событие трассировки - это событие 'call' для displayhook.

pdb начинает отладку внутри displayhook IPython .

In [1]: import pdb; pdb.set_trace()
--Call--
> /Users/billtubbs/anaconda/envs/py36/lib/python3.6/site-packages/IPython/core/displayhook.py(247)__call__()
-> def __call__(self, result=None):
  1. Список представлений создает новую область видимости.

Людям не понравилось, как списочные выражения просочились в переменную цикла в содержащую область видимости в Python 2, поэтому списочные выражения получают свою собственную область в Python 3.

  1. pdb использует eval, который очень плохо взаимодействует с переменными замыкания.

Механизм переменных закрытия Python основан на статическом анализе области видимости, который полностью несовместим с принципами работы eval. Таким образом, новые области видимости, созданные внутри eval, не имеют доступа к переменным замыкания; они могут получить доступ только к глобалам.


Собирая все это вместе, в IPython вы в конечном итоге отлаживаете IPhothon displayhook вместо области, в которой вы запускаете интерактивный код. Поскольку вы находитесь внутри displayyok IPython, ваше назначение x = 1 входит в локальные объекты displayhook. Для последующего понимания списка потребуется доступ к локальным элементам displayhook для доступа к x, но это будет закрывающая переменная для понимания списка, которая не работает с eval.

За пределами IPython, sys.displayhook написано на C, поэтому pdb не может его ввести, и для него нет события 'call'. В итоге вы отлаживаете область, которую вы намеревались отлаживать. Поскольку вы находитесь в глобальной области видимости, x = 1 входит в глобальные значения, и понимание списка может получить к нему доступ.

Вы бы увидели тот же эффект, если бы попытались запустить x = 1; [x for i in range(3)] при отладке любой обычной функции.

...