Ipython: فشل IPythonShellEmbed في التعرف على المتغيرات المحلية

تم إنشاؤها على ١٩ يوليو ٢٠١٠  ·  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' غير محدد".

bug

التعليق الأكثر فائدة

لقد بحثت في هذه المشكلة قليلاً ، وهي بالتأكيد قابلة للإصلاح (على الرغم من أن الحفاظ على الإصلاحات لكل من Python 2 و 3 قد يكون فوضويًا).

سيكون حل ChainMap أسهل لتضمينه في IPython المناسب. ومع ذلك ، هناك مشكلة طفيفة في أن EVAL / exec تتطلب أن تكون الكرة الأرضية dict . يمكن أن يعمل إنشاء class MyChainMap(ChainMap, dict): pass حل هذه المشكلة.

لقد كتبت أيضًا إصلاح Python 3.5+ استنادًا إلى استراتيجية مختلفة لمحاكاة خلايا الإغلاق وإجبار مترجم python على إصدار الرمز الثنائي الصحيح للعمل معهم. الملف ذي الصلة موجود هنا ، جزء من العرض التوضيحي الخاص بي. يعمل عن طريق استبدال get_ipython().run_ast_nodes .

بقدر ما أستطيع أن أقول ، يختلف النهجان فقط في تعاملهما مع عمليات الإغلاق. عندما يتم تضمين xdbg في نطاق مغلق عند بعض المتغيرات ، يمكنه الوصول إلى هذه المتغيرات بشكل صحيح عن طريق المرجع وتغييرها. بالإضافة إلى ذلك ، إذا تم إنشاء أي وظائف في المترجم التفاعلي ، فسيتم إغلاقها عند أي متغيرات محلية يحتاجونها مع السماح بجمع بقية النطاق المحلي.

ال 18 كومينتر

حسنًا ، أين عرفتِ "foo"؟

إذا قمت فقط بتشغيل call foo خارج شريط الوظائف ، فلا يجب أن يكون foo موجودًا (وبصورة واضحة أنه غير موجود).

عند تشغيل "sum (f.values ​​[x] for x in f.indices) بدلاً من ذلك ، تحصل على 15 مرة أخرى ...

صحيح تماما. ومع ذلك ، فإنني أشير إلى استخدام foo _inside_ صدفة IPython الناتجة ، والتي بدورها نشأت داخل تعريف الوظيفة حيث foo هو متغير محلي.

هل من المحتمل أن تكون هذه هي نفس المشكلة مثل # 62؟

لا ، takluyver : هذه مشكلة منفصلة وهي بالفعل خطأ حقيقي في كود التضمين الخاص بنا. كنت آمل أن يكون عملك الأخير مع مساحات الأسماء قد أصلحه ، لكنه لم يفعل. كمرجع ، إليك مثال التعليمة البرمجية ليتم تشغيله مع واجهة برمجة تطبيقات التضمين الحالية:

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'

يبدو أن آلة التضمين الخاصة بنا في حالة سيئة جدًا ...

خصص هذا لنفسي للنظر إليه.

ممتاز شكرا.

لسوء الحظ ، أعتقد أن هذا يمثل قيودًا على بايثون نفسها. يبدو أن الكود المترجم ديناميكيًا لا يمكنه تحديد الإغلاق ، وهو ما نقوم به هنا بشكل أساسي باستخدام تعبير المولد. فيما يلي حالة اختبار بسيطة:

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 كلاً من المتغيرات المحلية والعالمية حيث يتم تضمينها في شكل globals. ومع ذلك ، نظرًا لعدم وجود ضوضاء حول هذا الأمر على مدار العامين الماضيين ، لا أعتقد أنه يمثل أولوية عالية ، لذلك أعيد وضع العلامات وفقًا لذلك ، وآمل أن أصل إلى هذا في وقت ما بعد الإصدار 1.0.

هل يجوز التصويت بالتأييد: +1:؟ هذا يؤثر علي أيضًا. يمكنني إضافة حالة الاستخدام الخاصة بي إذا طلبت.

سأكون على ما يرام بنسبة 100٪ إذا كان إصلاح هذا يعمل فقط على Python 3.

أواجه نفس المشكلة أيضًا في Python 3. شكرًا على إعادة الفتح.

نفس المشكلة هنا: +1: كلاهما في بايثون 2 و 3.

لدي هذه المشكلة في بايثون 2 و 3. إنها تؤثر علي بشكل يومي.

لقد بحثت في هذه المشكلة قليلاً ، وهي بالتأكيد قابلة للإصلاح (على الرغم من أن الحفاظ على الإصلاحات لكل من Python 2 و 3 قد يكون فوضويًا).

سيكون حل ChainMap أسهل لتضمينه في IPython المناسب. ومع ذلك ، هناك مشكلة طفيفة في أن EVAL / exec تتطلب أن تكون الكرة الأرضية dict . يمكن أن يعمل إنشاء class MyChainMap(ChainMap, dict): pass حل هذه المشكلة.

لقد كتبت أيضًا إصلاح Python 3.5+ استنادًا إلى استراتيجية مختلفة لمحاكاة خلايا الإغلاق وإجبار مترجم python على إصدار الرمز الثنائي الصحيح للعمل معهم. الملف ذي الصلة موجود هنا ، جزء من العرض التوضيحي الخاص بي. يعمل عن طريق استبدال get_ipython().run_ast_nodes .

بقدر ما أستطيع أن أقول ، يختلف النهجان فقط في تعاملهما مع عمليات الإغلاق. عندما يتم تضمين xdbg في نطاق مغلق عند بعض المتغيرات ، يمكنه الوصول إلى هذه المتغيرات بشكل صحيح عن طريق المرجع وتغييرها. بالإضافة إلى ذلك ، إذا تم إنشاء أي وظائف في المترجم التفاعلي ، فسيتم إغلاقها عند أي متغيرات محلية يحتاجونها مع السماح بجمع بقية النطاق المحلي.

تعمل إصدارات IPython 6.0 والإصدارات الأحدث فقط في Python 3 ، لذلك إذا أرادnikitakit أو أي شخص آخر فتح طلب سحب مع حالة اختبار وإصلاح لذلك ، فسيكون ذلك موضع ترحيب.

لقد مر عام على تعليقي الأخير هنا ، وخلال هذا الوقت تغير فهمي للقضية قليلاً.

يظل الفحص التفاعلي للنطاق المحلي ميزة مهمة بالنسبة لي ، ولكن هناك بالفعل عددًا من المشكلات المترابطة فيما يتعلق بالمتغيرات المحلية وآلية التضمين. على سبيل المثال ، لا يعمل تعديل المتغيرات المحلية داخل غلاف مضمن:

>>> 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 وحقيقة أن تمرير السكان المحليين () و globals () صراحةً إلى exec يصلح استنساخtakluyver للخلل ، يبدو أن هذا _يجب_ أن يكون من الممكن إصلاحه في 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 التقييمات