Ipython: IPythonShellEmbed gagal mengenali variabel lokal

Dibuat pada 19 Jul 2010  ·  18Komentar  ·  Sumber: ipython/ipython

Isu

Shell IPython yang disematkan dapat kehilangan jejak variabel lokal.

Kasus cobaan

Kasus uji minimal:

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)

Untuk melihat kesalahan, pertama jalankan kode dengan Python (atau IPython) dan keluar dari shell spawned; pernyataan cetak akhir dengan benar menampilkan '15'. Jalankan kode lagi, tetapi kali ini ketik sum(foo.values[x] for x in foo.indices) di shell spawned, dan kami menerima kesalahan

" NameError: nama global 'foo' tidak ditentukan".

bug

Komentar yang paling membantu

Saya melihat masalah ini sedikit, dan itu pasti dapat diperbaiki (meskipun mempertahankan perbaikan untuk Python 2 dan 3 mungkin berantakan).

Solusi ChainMap akan paling mudah untuk dimasukkan ke dalam IPython yang tepat. Namun, ada sedikit tangkapan bahwa eval/exec mengharuskan global menjadi dict . Membuat class MyChainMap(ChainMap, dict): pass dapat mengatasi ini.

Saya juga menulis perbaikan Python 3.5+ berdasarkan strategi berbeda untuk mensimulasikan sel penutupan dan memaksa kompiler python untuk memancarkan bytecode yang benar untuk bekerja dengannya. File yang relevan ada di sini , bagian dari demo xdbg saya . Ia bekerja dengan mengganti get_ipython().run_ast_nodes .

Sejauh yang saya tahu, kedua pendekatan itu hanya berbeda dalam penanganan penutupannya. Ketika xdbg disematkan pada cakupan yang telah menutup beberapa variabel, variabel tersebut dapat mengakses variabel tersebut dengan referensi dan mengubahnya dengan benar. Selain itu, jika ada fungsi yang dibuat dalam interpreter interaktif, mereka akan menutup semua variabel lokal yang mereka perlukan sambil membiarkan lingkup lokal lainnya menjadi sampah yang dikumpulkan.

Semua 18 komentar

Hmm, di mana Anda mendefinisikan 'foo'?

Jika Anda baru saja menjalankan panggilan foo di luar bilah fungsi, foo seharusnya tidak ada (dan jelas tidak).

Ketika Anda menjalankan "sum(f.values[x] for x in f.indices)" Anda mendapatkan 15 lagi...

Cukup benar. Namun, saya mengacu pada penggunaan foo _inside_ shell IPython yang muncul, yang pada gilirannya muncul di dalam definisi fungsi di mana foo adalah variabel lokal.

Apakah ini mungkin masalah yang sama dengan #62?

Tidak, @takluyver : ini adalah masalah terpisah dan memang bug nyata dalam kode penyematan kami. Saya berharap pekerjaan Anda baru-baru ini dengan ruang nama akan memperbaikinya, tetapi ternyata tidak. Sebagai referensi, berikut adalah contoh kode untuk dijalankan dengan api penyematan saat ini:

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)

Kemudian, di IPython yang disematkan dan disematkan, ini gagal:

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

Dan itu tidak berfungsi bahkan jika kita meneruskan ke panggilan embed user_ns=locals() secara eksplisit, tetapi dalam kasus itu kita mendapatkan tambahan crash saat keluar:

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'

Sepertinya mesin embedding kami dalam kondisi yang sangat buruk...

Menugaskan ini untuk diri saya sendiri untuk dilihat.

Luar biasa, terima kasih.

Sayangnya, menurut saya ini adalah batasan dari Python itu sendiri. Tampaknya kode yang dikompilasi secara dinamis tidak dapat menentukan penutupan, yang pada dasarnya adalah apa yang kami lakukan di sini dengan ekspresi generator. Berikut ini adalah kasus uji minimal:

def f():
   x = 1
   exec "def g(): print x\ng()"

f()

Yang memberikan:

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

Perhatikan bahwa Anda masih dapat melihat variabel lokal di IPython - dalam contoh yang diberikan, print foo berfungsi. Tetapi Anda tidak dapat menutup ruang lingkup baru atas mereka.

Saya pikir dimungkinkan untuk membuat ini berfungsi menggunakan collections.ChainMap dari Python 3.3 sehingga IPython melihat variabel lokal dan global di mana ia disematkan sebagai global. Namun, dari kurangnya kebisingan tentang ini selama dua tahun terakhir, saya tidak berpikir ini adalah prioritas tinggi, jadi saya memberi tag ulang yang sesuai, dan mudah-mudahan mencapai ini beberapa saat setelah 1.0.

Apakah up-vote dapat diterima, :+1: ? Ini mempengaruhi saya juga. Saya dapat menambahkan kasus penggunaan saya jika diminta.

Saya akan 100% baik-baik saja jika perbaikan untuk ini hanya berfungsi pada Python 3.

Saya juga mengalami masalah yang sama di bawah Python 3. Terima kasih telah membuka kembali.

Masalah yang sama di sini :+1: keduanya di Python 2 dan 3.

Saya memiliki masalah ini di Python 2 dan 3. Ini memengaruhi saya setiap hari.

Saya melihat masalah ini sedikit, dan itu pasti dapat diperbaiki (meskipun mempertahankan perbaikan untuk Python 2 dan 3 mungkin berantakan).

Solusi ChainMap akan paling mudah untuk dimasukkan ke dalam IPython yang tepat. Namun, ada sedikit tangkapan bahwa eval/exec mengharuskan global menjadi dict . Membuat class MyChainMap(ChainMap, dict): pass dapat mengatasi ini.

Saya juga menulis perbaikan Python 3.5+ berdasarkan strategi berbeda untuk mensimulasikan sel penutupan dan memaksa kompiler python untuk memancarkan bytecode yang benar untuk bekerja dengannya. File yang relevan ada di sini , bagian dari demo xdbg saya . Ia bekerja dengan mengganti get_ipython().run_ast_nodes .

Sejauh yang saya tahu, kedua pendekatan itu hanya berbeda dalam penanganan penutupannya. Ketika xdbg disematkan pada cakupan yang telah menutup beberapa variabel, variabel tersebut dapat mengakses variabel tersebut dengan referensi dan mengubahnya dengan benar. Selain itu, jika ada fungsi yang dibuat dalam interpreter interaktif, mereka akan menutup semua variabel lokal yang mereka perlukan sambil membiarkan lingkup lokal lainnya menjadi sampah yang dikumpulkan.

IPython 6.0 dan versi yang lebih baru hanya berfungsi di Python 3, jadi jika @nikitakit atau siapa pun ingin membuka permintaan pulll dengan kasus uji dan perbaikan untuk ini, itu akan diterima.

Sudah setahun sejak komentar terakhir saya di sini, dan selama ini pemahaman saya tentang masalah ini sedikit berubah.

Memeriksa cakupan lokal secara interaktif tetap menjadi fitur penting bagi saya, tetapi sebenarnya ada sejumlah masalah yang saling terkait terkait variabel lokal dan mesin penyematan. Misalnya, memodifikasi variabel lokal di dalam shell yang disematkan tidak berfungsi:

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

Pendekatan berbasis ChainMap tidak akan membantu dalam situasi ini.

Pertanyaan lain adalah apa yang terjadi ketika penutupan yang didefinisikan di dalam shell tertanam bocor ke dalam lingkup global. Pertimbangkan untuk menjalankan kode yang sama seperti di atas, tetapi memasukkan sesuatu di sepanjang baris IPython.get_my_x = lambda: x di shell IPython yang disematkan. Solusi berbasis ChainMap akan menghindari menyebabkan NameError dalam situasi ini, dengan mengorbankan kemungkinan memperkenalkan dua salinan simultan x yang ada secara independen satu sama lain (salah satunya adalah ChainMap , dan yang lainnya adalah sel variabel/penutupan lokal yang digunakan oleh juru bahasa python).

Mengingat kompleksitas situasinya, saya telah memutuskan untuk memfokuskan upaya saya pada pendekatan yang lebih komprehensif untuk masalah tersebut (yang juga lebih selaras dengan penggunaan IPython saya sendiri). Ini telah mengarah pada pengembangan xdbg , yang pada dasarnya adalah debugger yang terintegrasi dengan IPython. Ide utamanya adalah untuk memperluas shell IPython dengan menawarkan perintah debugger melalui sihir (misalnya %break untuk mengatur breakpoints). Fakta bahwa breakpoint diatur secara eksternal, bukan dengan memanggil fungsi embed di tempat, telah memungkinkan implementasi yang mengatasi banyak masalah ini dengan variabel lokal.

Saat ini saya tidak berencana untuk meminta perbaikan bug yang ditargetkan secara sempit ke inti IPython. Namun, saya sangat tertarik untuk mengetahui apa yang dipikirkan pengguna dan pengembang IPython tentang penggunaan antarmuka yang terinspirasi debugger untuk pemeriksaan kode lokal. IPython (dan sekarang juga Jupyter) memiliki dampak besar pada kemampuan untuk melakukan pengkodean interaktif dengan Python, tetapi masih banyak perbaikan yang harus dilakukan dalam cara berinteraksi dengan enkapsulasi berat menggunakan kelas/fungsi/modul.

Saya telah dibakar beberapa kali oleh bug ini. Misalnya

[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.

Saya tidak yakin apa yang bisa dilakukan, tetapi hanya menambahkan suara saya ke paduan suara.

Berdasarkan https://bugs.python.org/issue13557 dan fakta yang secara eksplisit lewat penduduk setempat () dan GLOBALS () ke exec perbaikan @takluyver 's reproduksi bug , sepertinya _ought_ ini menjadi mungkin untuk memperbaiki dalam IPython dengan meneruskan ruang nama lokal dan global yang benar.

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()
Apakah halaman ini membantu?
0 / 5 - 0 peringkat