Ipython: IPythonShellEmbedがローカル変数の認識に失敗する

作成日 2010年07月19日  ·  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)でコードを実行し、生成されたシェルを終了します。 最後のprintステートメントは正しく「15」を表示します。 コードを再度実行しますが、今回は生成されたシェルにsum(foo.values[x] for x in foo.indices)と入力すると、エラーが発生します。

「NameError:グローバル名 'foo'が定義されていません」。

bug

最も参考になるコメント

私はこの問題を少し調べましたが、間違いなく修正可能です(ただし、Python 2と3の両方の修正を維持するのは面倒かもしれません)。

ChainMapソリューションは、IPython本体に含めるのが最も簡単です。 ただし、eval / execでグローバルがdict必要があるというわずかな問題があります。 class MyChainMap(ChainMap, dict): passを作成すると、これを回避できます。

また、クロージャーセルをシミュレートし、Pythonコンパイラーに正しいバイトコードを出力させて、それらを処理するという別の戦略に基づいて、Python3.5以降の修正を作成しました。 関連するファイルはここにあり、私のxdbgデモの一部です。 get_ipython().run_ast_nodes置き換えることで機能します。

私の知る限り、2つのアプローチは、クロージャの処理のみが異なります。 xdbgが一部の変数を閉じたスコープに埋め込まれている場合、参照によってそれらの変数に正しくアクセスし、それらを変更できます。 さらに、対話型インタープリターで関数が作成された場合、それらは必要なローカル変数を閉じ、残りのローカルスコープをガベージコレクションできるようにします。

全てのコメント18件

うーん、どこで「foo」を定義しましたか?

関数バーの外で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機能します。 しかし、それらに対して新しいスコープを閉じることはできません。

Python 3.3のcollections.ChainMapを使用してこれを機能させ、IPythonがグローバル変数として埋め込まれているローカル変数とグローバル変数の両方を認識できるようにすることは可能だと思います。 ただし、過去2年間のノイズの不足から、優先度が高いとは思わないので、それに応じてタグを付け直し、1.0以降にこれに到達することを願っています。

賛成票を投じてもかまいません、:+ 1:? これは私にも影響します。 必要に応じて、ユースケースを追加できます。

これに対する修正がPython3でのみ機能する場合、私は100%大丈夫です。

Python3でも同じ問題が発生しています。再度開いていただきありがとうございます。

ここで同じ問題:+1:Python2と3の両方。

私はPython2と3の両方でこの問題を抱えています。それは日常的に私に影響を与えます。

私はこの問題を少し調べましたが、間違いなく修正可能です(ただし、Python 2と3の両方の修正を維持するのは面倒かもしれません)。

ChainMapソリューションは、IPython本体に含めるのが最も簡単です。 ただし、eval / execでグローバルがdict必要があるというわずかな問題があります。 class MyChainMap(ChainMap, dict): passを作成すると、これを回避できます。

また、クロージャーセルをシミュレートし、Pythonコンパイラーに正しいバイトコードを出力させて、それらを処理するという別の戦略に基づいて、Python3.5以降の修正を作成しました。 関連するファイルはここにあり、私のxdbgデモの一部です。 get_ipython().run_ast_nodes置き換えることで機能します。

私の知る限り、2つのアプローチは、クロージャの処理のみが異なります。 xdbgが一部の変数を閉じたスコープに埋め込まれている場合、参照によってそれらの変数に正しくアクセスし、それらを変更できます。 さらに、対話型インタープリターで関数が作成された場合、それらは必要なローカル変数を閉じ、残りのローカルスコープをガベージコレクションできるようにします。

IPython6.0以降のバージョンは@ nikitakitまたは他の誰かが、テストケースとこれに対する修正を含むpulllリクエストを開きたい場合は、それを歓迎します。

ここでの最後のコメントから1年が経ちましたが、この間に問題に対する私の理解は少し変わりました。

ローカルスコープをインタラクティブに検査することは私にとって重要な機能ですが、実際にはローカル変数と埋め込み機構に関して相互に関連する問題がいくつかあります。 たとえば、埋め込みシェル内のローカル変数の変更は機能しません。

>>> 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ベースのアプローチは、この状況では役に立ちません。

もう1つの質問は、埋め込みシェル内で定義されたクロージャがグローバルスコープにリークされたときに何が起こるかです。 上記と同じコードを実行することを検討してください。ただし、埋め込まれたIPythonシェルのIPython.get_my_x = lambda: xの行に沿って何かを入力します。 ChainMapベースのソリューションは、この状況でNameErrorが発生するのを回避しますが、互いに独立して存在するx 2つの同時コピーを導入する可能性があります(1つはChainMap 、およびPythonインタープリターによって使用される他のローカル変数/クロージャーセル)。

状況の複雑さを考慮して、私は問題へのより包括的なアプローチに注力することにしました(これは、私自身のIPythonの使用法ともよりよく一致します)。 これにより、本質的にIPythonと統合するデバッガーであるxdbgが開発されました。 重要なアイデアは、マジックを介してデバッガーコマンドを提供することによって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と、locals()とglobals()をexecに明示的に渡すと、@ takluyverによるバグ

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 評価