Shell IPython yang disematkan dapat kehilangan jejak variabel lokal.
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".
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()
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
. Membuatclass 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.