Ipython: IPythonShellEmbed не распознает локальные переменные

Созданный на 19 июл. 2010  ·  18Комментарии  ·  Источник: ipython/ipython

Проблема

Встроенные оболочки 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' не определено».

Самый полезный комментарий

Я немного изучил эту проблему, и она определенно поправима (хотя поддержание исправлений для Python 2 и 3 может быть беспорядочным).

Решение ChainMap было бы проще всего включить в собственно IPython. Однако есть небольшая загвоздка: eval / exec требует, чтобы глобальные переменные были dict . Создание class MyChainMap(ChainMap, dict): pass может обойти это.

Я также написал исправление Python 3.5+, основанное на другой стратегии имитации закрывающих ячеек и принуждение компилятора python выдавать правильный байт-код для работы с ними. Соответствующий файл находится здесь , это часть моей демонстрации xdbg . Работает путем замены get_ipython().run_ast_nodes .

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

Все 18 Комментарий

Хм, а где вы определили «фу»?

Если вы просто запускаете 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()
Была ли эта страница полезной?
0 / 5 - 0 рейтинги