24

Ich lese the source code of the incoming asyncio package. Beachten Sie, dass am Ende der Methode eine self = None-Anweisung steht. Was tut es?Was macht self = None?

def _run(self): 
    try: 
     self._callback(*self._args) 
    except Exception as exc: 
     msg = 'Exception in callback {}{!r}'.format(self._callback, 
                self._args) 
     self._loop.call_exception_handler({ 
      'message': msg, 
      'exception': exc, 
      'handle': self, 
     }) 
    self = None # Needed to break cycles when an exception occurs. 

dachte ich, es würde die Instanz löschen, aber der folgende Test schlägt nicht so:

class K: 
    def haha(self): 
     self = None 

a = K() 
a.haha() 
print(a) # a is still an instance 
+6

Vielleicht sollte die Frage sein "warum kann die Einstellung" self "auf" None "Zyklen brechen? Welche Zyklen?" – satoru

Antwort

24

Es löscht einfach den lokalen Bezug auf self, um sicherzustellen, dass, wenn eine Ausnahme die Referenz auftritt übergeben an self._loop.call_exception_handler() ist die einzige verbleibende Referenz und kein Zyklus wurde erstellt.

Dies wird hier immer noch benötigt, da der lokale Namespace durch die Ausnahme-Traceback referenziert wird; Es wird nicht gelöscht werden, wenn die Funktion verlässt, da es einen Verweis auf die Einheimischen immer noch gibt. Diese

ist dokumentiert im sys.exc_info() function documentation mit einer Warnung:

Warnung: Zuweisen der Zurückverfolgungs Rückgabewert auf eine lokale Variable in einer Funktion, die eine Ausnahme ist der Umgang wird eine kreisförmige Referenz verursachen. Dadurch wird verhindert, dass alles, auf das von einer lokalen Variablen in der gleichen Funktion oder vom Traceback verwiesen wird, als Garbage Collection behandelt wird. Da die meisten Funktionen keinen Zugriff auf das Traceback benötigen, ist die beste Lösung, etwas wie exctype, value = sys.exc_info()[:2] zu verwenden, um nur den Ausnahmetyp und -wert zu extrahieren. Wenn Sie die Rückverfolgung benötigen, müssen Sie sie nach der Verwendung löschen (am besten mit einer try ... finally-Anweisung) oder in einer Funktion aufrufen, die selbst keine Ausnahme behandelt.

Da tulip Handler eine grundlegenden Rahmen Klasse bildet der Code die Zurückverfolgungskreisreferenzgriffe durch self aus dem lokalen Namespace statt zu entfernen, da es nicht garantieren kann, dass die _callback oder call_exception_handler Funktionen werden ihre Referenzen aufklären.

In CPython werden Objekte zerstört, wenn ihre Referenzzahl auf 0 fällt, aber eine zyklische Referenz (eine Reihe von Objekten, die sich selbst in einem Zyklus referenzieren) wird nie ihre Referenzzahl auf 0 fallen lassen. Der Garbage Collector versucht zu brechen Solche Zyklen können aber nicht immer schnell genug sein. Das explizite Löschen von Referenzen vermeidet das Erstellen von Zyklen.

Zum Beispiel, wenn es eine __del__ Methode gibt, bricht der Garbage Collector keinen Zyklus ab, da er nicht weiß, in welcher Reihenfolge ein Zyklus in diesem Fall sicher unterbrochen wird.

Auch wenn es keine __del__ Methode gibt (die eine Framework-Klasse niemals annehmen sollte, ist dies nicht der Fall), ist es am besten, sich nicht auf den Garbage Collector zu verlassen, der die Zyklen schließlich löscht.

+1

Können Sie ein Beispiel für eine solche zyklische Referenz geben? – msvalkon

+0

Ich verstehe es immer noch nicht ... Ich meine, wenn die Anweisung 'self = None' getroffen wird, dann wird natürlich das Ende der Methode getroffen, in diesem Fall wird die lokale Referenz sowieso aus dem Geltungsbereich fallen Warum brauchen wir 'self = None'? –

+0

@DerekChiang: Nein, weil es eine Live-Traceback gibt, wenn eine Ausnahme auftritt und den lokalen Namespace am Leben hält. –

1

Beachten Sie, dass diese Zeile in revision 496 von Guido eingeführt wird.

Bei dieser Revision ist die Funktion, die _run entsprach ist run:

def run(self): 
    try: 
     self._callback(*self._args) 
    except Exception: 
     tulip_log.exception('Exception in callback %s %r', 
          self._callback, self._args) 
    self = None # Needed to break cycles when an exception occurs. 

tulip_log nur ein normaler Logger ist: logging.getLogger("tulip").

Unter der Haube Logger.exception speichert das Ergebnis von sys.exc_info() in LogRecord, aber die Platten Objekt bleibt nicht nach dem exception Anruf.

Um sicherzustellen, dass logging.exception nicht Referenzzyklus verursacht, ich folgendes Experiment tat:

import time 

import logging 

class T: 
    def __del__(self): 
     print('T.__del__ called') 

    def test(self): 
     try: 
      1/0 
     except Exception: 
      logging.exception("Testing") 


def run(): 
    t = T() 
    t.test() 
    # t is supposed to be garbaged collected 


run() 

time.sleep(10) # to emulate a long running process 

Dies ist das Ergebnis:

$ python test.py 
ERROR:root:Testing 
Traceback (most recent call last): 
    File "test.py", line 11, in test 
    1/0 
ZeroDivisionError: integer division or modulo by zero 
T.__del__ called 

Das Objekt t Müll wie erwartet gesammelt ist.

Also ich glaube nicht, dass self = None Zuweisung hier notwendig ist.

+0

Das gleiche Commit fügt 'self = None' an mehreren Stellen hinzu, da dies für ein Framework sinnvoll ist, nicht weil die Codebasis * in Isolation * einen Zirkelverweis aufweist. –

+0

@MartijnPieters Können Sie mir bitte ein Beispiel zeigen, wenn Zirkelverweise generiert werden? Wahrscheinlich mit '_callback'? – satoru

+0

Ich habe die Tulip/Ascio-Bibliothek noch nicht studiert; Ich kenne die Absichten des Entwurfs nicht, und auch nicht, wenn es sich bei dem von Ihnen untersuchten Commit um ein Work in Progress handelt (z. B. noch nicht die komplette Bibliothek). Und die "Logging" -Bibliothek ermöglicht es Ihnen, benutzerdefinierte Handler und Formatierer zu registrieren; gehe nicht einfach zur Standard-Codebase. Leider habe ich momentan nicht die Zeit, einen Sample-Case-ATM zu bauen (dieser Kommentar stammt von einem Smartphone über eine 3G-Verbindung). –