Ipython: IPythonShellEmbed erkennt lokale Variablen nicht

Erstellt am 19. Juli 2010  ·  18Kommentare  ·  Quelle: ipython/ipython

Problem

Eingebettete IPython-Shells können lokale Variablen aus den Augen verlieren.

Testfall

Minimaler Testfall:

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)

Um den Fehler zu sehen, führen Sie zuerst den Code in Python (oder IPython) aus und beenden Sie die erzeugte Shell. die letzte print-Anweisung zeigt korrekt '15' an. Führen Sie den Code erneut aus, aber geben Sie diesmal sum(foo.values[x] for x in foo.indices) in die erzeugte Shell ein, und wir erhalten den Fehler

" NameError: globaler Name 'foo' ist nicht definiert".

bug

Hilfreichster Kommentar

Ich habe mich ein wenig mit diesem Problem befasst und es ist definitiv behebbar (obwohl die Wartung von Fixes für Python 2 und 3 chaotisch sein kann).

Die ChainMap-Lösung wäre am einfachsten in IPython einzubinden. Es gibt jedoch einen kleinen Haken, dass eval/exec erfordern, dass globales dict . Das Erstellen eines class MyChainMap(ChainMap, dict): pass kann dies umgehen.

Ich habe auch einen Python 3.5+-Fix geschrieben, der auf einer anderen Strategie basiert, bei der Closure-Zellen simuliert und der Python-Compiler gezwungen wird, den richtigen Bytecode auszugeben, um mit ihnen zu arbeiten. Die entsprechende Datei ist hier , Teil meiner xdbg-Demo . Es funktioniert, indem es get_ipython().run_ast_nodes .

Soweit ich das beurteilen kann, unterscheiden sich die beiden Ansätze nur im Umgang mit Verschlüssen. Wenn xdbg in einen Bereich eingebettet ist, der einige Variablen geschlossen hat, kann es korrekt auf diese Variablen durch Verweis zugreifen und sie mutieren. Wenn Funktionen im interaktiven Interpreter erstellt werden, werden sie außerdem über alle benötigten lokalen Variablen geschlossen, während der Rest des lokalen Gültigkeitsbereichs auf Datenmüll gesammelt wird.

Alle 18 Kommentare

Hmm, wo hast du 'foo' definiert?

Wenn Sie call foo nur außerhalb der Funktionsleiste ausführen, sollte foo nicht existieren (und offensichtlich auch nicht).

Wenn Sie stattdessen "sum(f.values[x] for x in f.indices)" ausführen, erhalten Sie wieder 15 ...

Ganz richtig. Ich beziehe mich jedoch auf die Verwendung von foo _inside_ der erzeugten IPython-Shell, die wiederum innerhalb der Funktionsdefinition erzeugt wird, wobei foo eine lokale Variable ist.

Ist dies wahrscheinlich das gleiche Problem wie #62?

Nein, @takluyver : Dies ist ein separates Problem und tatsächlich ein echter Fehler in unserem Einbettungscode. Ich hatte gehofft, dass Ihre jüngste Arbeit mit Namespaces das Problem behoben hätte, aber das war nicht der Fall. Als Referenz hier der Beispielcode, der mit der aktuellen Einbettungs-API ausgeführt werden soll:

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)

Dann schlägt dies im erzeugten, eingebetteten IPython fehl:

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

Und es funktioniert auch nicht, wenn wir explizit an den Einbettungsaufruf user_ns=locals() , aber in diesem Fall bekommen wir zusätzlich einen Absturz beim Beenden:

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'

Es sieht so aus, als ob unsere Einbettmaschinen in einem ziemlich schlechten Zustand sind ...

Habe mir dies zum Anschauen zugewiesen.

Großartig, danke.

Leider denke ich, dass dies eine Einschränkung von Python selbst ist. Es scheint, dass dynamisch kompilierter Code keine Closure definieren kann, was im Wesentlichen das ist, was wir hier mit einem Generatorausdruck tun. Hier ist ein minimaler Testfall:

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

f()

Was gibt:

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

Beachten Sie, dass Sie in IPython weiterhin lokale Variablen sehen können - im angegebenen Beispiel funktioniert einfaches print foo . Aber Sie können keinen neuen Bereich darüber schließen.

Ich denke, es ist möglich, dies mit collections.ChainMap aus Python 3.3 zum Laufen zu bringen, sodass IPython sowohl lokale als auch globale Variablen sieht, wo es als globale Variablen eingebettet ist. Aufgrund des Mangels an Lärm darüber in den letzten zwei Jahren glaube ich jedoch nicht, dass dies eine hohe Priorität hat, also verschlage ich entsprechend neu und komme hoffentlich irgendwann nach 1.0 dazu.

Ist eine Aufwertung akzeptabel, :+1: ? Das betrifft auch mich. Auf Wunsch kann ich meinen Anwendungsfall hinzufügen.

Ich wäre zu 100% in Ordnung, wenn der Fix dafür nur mit Python 3 funktioniert.

Ich habe auch das gleiche Problem unter Python 3. Danke fürs erneute Öffnen.

Gleiches Problem hier :+1: sowohl in Python 2 als auch in 3.

Ich habe dieses Problem sowohl in Python 2 als auch in 3. Es betrifft mich täglich.

Ich habe mich ein wenig mit diesem Problem befasst und es ist definitiv behebbar (obwohl die Wartung von Fixes für Python 2 und 3 chaotisch sein kann).

Die ChainMap-Lösung wäre am einfachsten in IPython einzubinden. Es gibt jedoch einen kleinen Haken, dass eval/exec erfordern, dass globales dict . Das Erstellen eines class MyChainMap(ChainMap, dict): pass kann dies umgehen.

Ich habe auch einen Python 3.5+-Fix geschrieben, der auf einer anderen Strategie basiert, bei der Closure-Zellen simuliert und der Python-Compiler gezwungen wird, den richtigen Bytecode auszugeben, um mit ihnen zu arbeiten. Die entsprechende Datei ist hier , Teil meiner xdbg-Demo . Es funktioniert, indem es get_ipython().run_ast_nodes .

Soweit ich das beurteilen kann, unterscheiden sich die beiden Ansätze nur im Umgang mit Verschlüssen. Wenn xdbg in einen Bereich eingebettet ist, der einige Variablen geschlossen hat, kann es korrekt auf diese Variablen durch Verweis zugreifen und sie mutieren. Wenn Funktionen im interaktiven Interpreter erstellt werden, werden sie außerdem über alle benötigten lokalen Variablen geschlossen, während der Rest des lokalen Gültigkeitsbereichs auf Datenmüll gesammelt wird.

IPython 6.0 und spätere Versionen funktionieren nur in Python 3, wenn @nikitakit oder jemand anderes also eine Pulll-Anfrage mit einem Testfall und einem Fix dafür öffnen möchte, wäre das willkommen.

Seit meinem letzten Kommentar hier ist ein Jahr vergangen, und in dieser Zeit hat sich mein Verständnis des Themas ein wenig geändert.

Die interaktive Überprüfung des lokalen Geltungsbereichs bleibt für mich ein wichtiges Merkmal, aber es gibt tatsächlich eine Reihe miteinander verbundener Probleme in Bezug auf lokale Variablen und die Einbettungsmaschinerie. Das Ändern lokaler Variablen innerhalb einer eingebetteten Shell funktioniert beispielsweise nicht:

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

Ein ChainMap -basierter Ansatz würde in dieser Situation nicht helfen.

Eine andere Frage ist, was passiert, wenn innerhalb einer eingebetteten Shell definierte Closures in den globalen Geltungsbereich gelangen. Ziehen Sie in Betracht, denselben Code wie oben auszuführen, aber etwas in der Art von IPython.get_my_x = lambda: x in die eingebettete IPython-Shell einzugeben. Eine ChainMap -basierte Lösung vermeidet in dieser Situation ein NameError , auf Kosten der potentiellen Einführung zweier gleichzeitiger Kopien von x , die unabhängig voneinander existieren (eine davon ist die ChainMap , und das andere die lokale Variable/Schließungszelle, die vom Python-Interpreter verwendet wird).

Angesichts der Komplexität der Situation habe ich beschlossen, meine Bemühungen auf einen umfassenderen Ansatz für das Problem zu konzentrieren (der auch besser zu meiner eigenen Verwendung von IPython passt). Dies hat zur Entwicklung von xdbg geführt , bei dem es sich im Wesentlichen um einen Debugger handelt, der in IPython integriert wird. Die Kernidee besteht darin, die IPython-Shell zu erweitern, indem Debugger-Befehle über Magie angeboten werden (zB %break zum Setzen von Breakpoints). Die Tatsache, dass Breakpoints extern gesetzt werden, anstatt die Funktion embed direkt aufzurufen, hat eine Implementierung ermöglicht, die viele dieser Probleme mit lokalen Variablen angeht.

Ich plane derzeit nicht, einen eng gezielten Bugfix für den Kern von IPython anzufordern. Ich bin jedoch sehr daran interessiert, was IPython-Benutzer und -Entwickler über die Verwendung einer vom Debugger inspirierten Schnittstelle für die lokale Codeinspektion denken. IPython (und jetzt auch Jupyter) hatten einen großen Einfluss auf die Möglichkeit, interaktives Codieren in Python durchzuführen, aber es gibt noch viele Verbesserungen in der Interaktion mit starker Kapselung mithilfe von Klassen/Funktionen/Modulen.

Ich bin ein paar Mal von diesem Fehler verbrannt worden. Z.B

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

Ich bin mir nicht sicher, was getan werden kann, aber ich füge nur meine Stimme zum Chor hinzu.

Basierend auf https://bugs.python.org/issue13557 und der Tatsache, dass die explizite Übergabe von locals() und globals() an eine Exec die Reproduktion des Fehlers durch @takluyver behebt, scheint es, dass dies _sollte_ möglich sein sollte, es in . zu beheben IPython durch Übergabe der korrekten lokalen und globalen Namespaces.

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()
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen