Встроенные оболочки IPython могут терять отслеживание локальных переменных.
Минимальный тестовый пример:
class Foo(object):
""" Container-like object """
def __setattr__(self, obj, val):
self.__dict__[obj] = val
def __getattr__(self, obj, val):
return self.__dict__[obj]
f = Foo()
f.indices = set([1,2,3,4,5])
f.values = {}
for x in f.indices:
f.values[x] = x
def bar(foo):
import IPython
IPython.Shell.IPShellEmbed()()
return sum(foo.values[x] for x in foo.indices)
print bar(f)
Чтобы увидеть ошибку, сначала запустите код на Python (или IPython) и выйдите из порожденной оболочки; в последнем операторе печати правильно отображается «15». Запустите код еще раз, но на этот раз введите sum(foo.values[x] for x in foo.indices)
в созданной оболочке, и мы получим ошибку
«NameError: глобальное имя 'foo' не определено».
Хм, а где вы определили «фу»?
Если вы просто запускаете call foo за пределами панели функций, foo не должно существовать (и, очевидно, это не так).
Когда вы вместо этого запускаете «sum (f.values [x] for x in f.indices)», вы снова получаете 15 ...
Совершенно верно. Однако я имею в виду использование foo
_inside_ порожденной оболочки IPython, которая, в свою очередь, порождается внутри определения функции, где foo
- это локальная переменная.
Вероятно, это та же проблема, что и # 62?
Нет, @takluyver : это отдельная проблема и действительно настоящая ошибка в нашем коде встраивания. Я надеялся, что ваша недавняя работа с пространствами имен исправит это, но этого не произошло. Для справки, вот пример кода для запуска с текущим API внедрения:
class Foo(object):
""" Container-like object """
def __setattr__(self, obj, val):
self.__dict__[obj] = val
def __getattr__(self, obj, val):
return self.__dict__[obj]
f = Foo()
f.indices = set([1,2,3,4,5])
f.values = {}
for x in f.indices:
f.values[x] = x
def bar(foo):
import IPython
IPython.embed()
return sum(foo.values[x] for x in foo.indices)
print bar(f)
Затем в созданном встроенном IPython это не удается:
In [1]: sum(foo.values[x] for x in foo.indices)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
/home/fperez/tmp/junk/ipython/foo.py in <module>()
----> 1 sum(foo.values[x] for x in foo.indices)
/home/fperez/tmp/junk/ipython/foo.py in <genexpr>((x,))
----> 1 sum(foo.values[x] for x in foo.indices)
NameError: global name 'foo' is not defined
И это не сработает, даже если мы явно перейдем к вызову встраивания user_ns=locals()
, но в этом случае мы получим дополнительно сбой при выходе:
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
File "/usr/lib/python2.7/atexit.py", line 24, in _run_exitfuncs
func(*targs, **kargs)
File "/home/fperez/usr/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 2702, in atexit_operations
self.reset(new_session=False)
File "/home/fperez/usr/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 1100, in reset
self.displayhook.flush()
File "/home/fperez/usr/lib/python2.7/site-packages/IPython/core/displayhook.py", line 319, in flush
self.shell.user_ns['_oh'].clear()
KeyError: '_oh'
Похоже, что наше встраивающее оборудование в очень плохом состоянии ...
Поручил себе это посмотреть.
Отлично, спасибо.
К сожалению, я считаю, что это ограничение самого Python. Похоже, что код, скомпилированный динамически, не может определить замыкание, что, по сути, мы делаем здесь с выражением генератора. Вот минимальный тестовый пример:
def f():
x = 1
exec "def g(): print x\ng()"
f()
Который дает:
Traceback (most recent call last):
File "scopetest.py", line 5, in <module>
f()
File "scopetest.py", line 3, in f
exec "def g(): print x\ng()"
File "<string>", line 2, in <module>
File "<string>", line 1, in g
NameError: global name 'x' is not defined
Обратите внимание, что вы все еще можете видеть локальные переменные в IPython - в приведенном примере работает простой print foo
. Но над ними нельзя закрывать новый простор.
Я думаю, что можно сделать эту работу, используя collections.ChainMap
из Python 3.3, чтобы IPython видел как локальные, так и глобальные переменные, где он встроен как глобальные. Однако из-за отсутствия шума по этому поводу в течение последних двух лет я не думаю, что это высокий приоритет, поэтому я соответствующим образом поменяю теги и, надеюсь, доберусь до этого через некоторое время после 1.0.
Допустимо ли голосовать «за»: +1:? Это влияет и на меня. Если потребуется, я могу добавить свой вариант использования.
Я был бы на 100% в порядке, если бы исправление этого сработало только на Python 3.
У меня такая же проблема с Python 3. Спасибо за повторное открытие.
Здесь та же проблема: +1: как в Python 2, так и в 3.
У меня есть эта проблема как в Python 2, так и в 3. Она затрагивает меня ежедневно.
Я немного изучил эту проблему, и она определенно поправима (хотя поддержание исправлений для Python 2 и 3 может быть беспорядочным).
Решение ChainMap было бы проще всего включить в собственно IPython. Однако есть небольшая загвоздка: eval / exec требует, чтобы глобальные переменные были dict
. Создание class MyChainMap(ChainMap, dict): pass
может обойти это.
Я также написал исправление Python 3.5+, основанное на другой стратегии имитации закрывающих ячеек и принуждение компилятора python выдавать правильный байт-код для работы с ними. Соответствующий файл находится здесь , это часть моей демонстрации xdbg . Работает путем замены get_ipython().run_ast_nodes
.
Насколько я могу судить, эти два подхода различаются только обработкой замыканий. Когда xdbg
внедряется в область видимости, которая закрыта для некоторых переменных, она может правильно обращаться к этим переменным по ссылке и изменять их. Кроме того, если в интерактивном интерпретаторе создаются какие-либо функции, они закроют любые локальные переменные, которые им нужны, а остальную часть локальной области можно будет собрать сборщиком мусора.
IPython 6.0 и более поздние версии работают только в Python 3, поэтому, если @nikitakit или кто-либо еще захочет открыть запрос pullll с тестовым примером и исправлением для этого, это будет приветствоваться.
С момента моего последнего комментария здесь прошел год, и за это время мое понимание проблемы немного изменилось.
Интерактивная проверка локальной области остается для меня важной функцией, но на самом деле существует ряд взаимосвязанных проблем, касающихся локальных переменных и механизма встраивания. Например, изменение локальных переменных внутри встроенной оболочки не работает:
>>> import IPython
>>> def test():
... x = 5
... IPython.embed()
... print('x is', x)
...
>>> test()
Python 3.5.1 |Continuum Analytics, Inc.| (default, Dec 7 2015, 11:24:55)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: x
Out[1]: 5
In [2]: x = 6
In [3]:
Do you really want to exit ([y]/n)? y
x is 5
Подход, основанный на ChainMap
, не поможет в этой ситуации.
Другой вопрос: что происходит, когда замыкания, определенные внутри встроенной оболочки, просачиваются в глобальную область видимости. Подумайте о том, чтобы запустить тот же код, что и выше, но ввести что-то вроде IPython.get_my_x = lambda: x
во встроенную оболочку IPython. Решение на основе ChainMap
не вызовет NameError
в этой ситуации за счет потенциального введения двух одновременных копий x
которые существуют независимо друг от друга (одна из которых ChainMap
, а другая - локальная переменная / закрывающая ячейка, используемая интерпретатором python).
Учитывая сложность ситуации, я решил сосредоточить свои усилия на более комплексном подходе к проблеме (который также лучше согласуется с моим собственным использованием IPython). Это привело к разработке xdbg , который по сути является отладчиком, интегрируемым с IPython. Ключевая идея - расширить оболочку IPython, предлагая команды отладчика с помощью магии (например, %break
для установки точек останова). Тот факт, что точки останова устанавливаются извне, а не путем вызова функции embed
на месте, позволил реализовать реализацию, которая решает многие из этих проблем с помощью локальных переменных.
В настоящее время я не планирую запрашивать исправление узконаправленной ошибки в ядре IPython. Однако мне очень интересно узнать, что пользователи и разработчики IPython думают об использовании интерфейса, вдохновленного отладчиком, для проверки локального кода. IPython (а теперь и Jupyter) оказали огромное влияние на возможность интерактивного кодирования на Python, но еще предстоит сделать много улучшений в том, как он взаимодействует с тяжелой инкапсуляцией с использованием классов / функций / модулей.
Меня много раз обжигала эта ошибка. Например
[mcgibbon<strong i="6">@xps13</strong>:~]$ cat test.py
x = 1
def func():
x = 2
import IPython
IPython.embed()
if __name__ == "__main__":
func()
[mcgibbon<strong i="9">@xps13</strong>:~]$ python test.py
Python 3.7.6 (default, Dec 18 2019, 19:23:55)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.12.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: [x for _ in range(2)]
Out[1]: [1, 1] # lol whoops. looked up in wrong scope.
Я не уверен, что можно сделать, но просто добавляю свой голос к хору.
На основании https://bugs.python.org/issue13557 и тот факт , что явно проходя местных жителей () и глобалам () до Exec исправления @takluyver «s воспроизведение ошибки , похоже , этого _ought_ возможным исправить в IPython, передав правильные локальные и глобальные пространства имен.
x = 1
def func():
x = 2
exec("def g():\n print(x)\ng()") # prints 1 :(
exec("def g():\n print(x)\ng()", locals(), globals()) # prints 2 yay!
if __name__ == "__main__":
func()
Самый полезный комментарий
Я немного изучил эту проблему, и она определенно поправима (хотя поддержание исправлений для Python 2 и 3 может быть беспорядочным).
Решение ChainMap было бы проще всего включить в собственно IPython. Однако есть небольшая загвоздка: eval / exec требует, чтобы глобальные переменные были
dict
. Созданиеclass MyChainMap(ChainMap, dict): pass
может обойти это.Я также написал исправление Python 3.5+, основанное на другой стратегии имитации закрывающих ячеек и принуждение компилятора python выдавать правильный байт-код для работы с ними. Соответствующий файл находится здесь , это часть моей демонстрации xdbg . Работает путем замены
get_ipython().run_ast_nodes
.Насколько я могу судить, эти два подхода различаются только обработкой замыканий. Когда
xdbg
внедряется в область видимости, которая закрыта для некоторых переменных, она может правильно обращаться к этим переменным по ссылке и изменять их. Кроме того, если в интерактивном интерпретаторе создаются какие-либо функции, они закроют любые локальные переменные, которые им нужны, а остальную часть локальной области можно будет собрать сборщиком мусора.