Erklären von Pythons '__enter__' und '__exit__'

362

Ich habe das in jemandes Code gesehen. Was bedeutet das?

    def __enter__(self):
        return self

    def __exit__(self, type, value, tb):
        self.stream.close()

from __future__ import with_statement#for python2.5 

class a(object):
    def __enter__(self):
        print 'sss'
        return 'sss111'
    def __exit__(self ,type, value, traceback):
        print 'ok'
        return False

with a() as s:
    print s


print s
zjm1126
quelle
19
Eine gute Erklärung hier: effbot.org/zone/python-with-statement.htm
Manur
7
@StevenVascellaro Das Bearbeiten des Codes einer Frage ist im Allgemeinen eine schlechte Idee, insbesondere wenn der Code fehlerhaft ist. Diese Frage wurde im Hinblick auf Py2 gestellt, und es gibt keinen Grund, sie auf Py3 zu aktualisieren.
Jpaugh

Antworten:

420

Mit diesen magischen Methoden ( __enter__, __exit__) können Sie Objekte implementieren, die einfach mit der withAnweisung verwendet werden können .

Die Idee ist, dass es einfach ist, Code zu erstellen, für den ein Bereinigungscode ausgeführt werden muss (stellen Sie sich diesen als try-finallyBlock vor). Noch eine Erklärung hier .

Ein nützliches Beispiel könnte ein Datenbankverbindungsobjekt sein (das die Verbindung dann automatisch schließt, sobald die entsprechende 'with'-Anweisung den Gültigkeitsbereich verlässt):

class DatabaseConnection(object):

    def __enter__(self):
        # make a database connection and return it
        ...
        return self.dbconn

    def __exit__(self, exc_type, exc_val, exc_tb):
        # make sure the dbconnection gets closed
        self.dbconn.close()
        ...

Verwenden Sie dieses Objekt wie oben erläutert mit der withAnweisung (möglicherweise müssen Sie dies from __future__ import with_statementoben in der Datei tun, wenn Sie Python 2.5 verwenden).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - Die 'with'-Anweisung' hat auch eine schöne Beschreibung.

ChristopheD
quelle
20
Wahrscheinlich __enter__sollte zurückkehren selfimmer dann nur andere Methoden der Klasse kann auf den Kontext aufgerufen werden.
ViFI
3
@ViFI Es gibt 4 Beispiele def __enter__(self)in PEP 343 und niemand tut dies return self: python.org/dev/peps/pep-0343 . Warum denkst du das?
Trauer
4
@Grief: Aus zwei Gründen kann ich meiner Meinung nach 1) keine anderen Methoden für selfObjekte aufrufen, wie hier erläutert: stackoverflow.com/questions/38281853/… 2) self.XYZ ist nur ein Teil von self object und Die Rückgabe des Griffs nur auf genau das scheint mir aus Sicht der Wartung unangemessen. Ich würde es vorziehen, das Handle zurückzugeben, um das Objekt zu vervollständigen, und dann öffentliche APIs nur für das Komponentenobjekt bereitzustellen self, das ich dem Benutzer wie in with open(abc.txt, 'r') as fin: content = fin.read()
ViFI
4
Dateiobjekte kehren selfvon zurück __enter__, weshalb Sie die Datei wie finnen verarbeiten könnenwith open(...) as f
holdenweb
2
Eine Subtilität musste ich verstehen: Wenn das Objekt Parameter zum Initialisieren benötigt, sollten diese auf init und nicht auf self sein .
dfrankow
70

Wenn Sie wissen, was Kontextmanager sind, brauchen Sie nichts mehr zu verstehen __enter__und __exit__magische Methoden. Sehen wir uns ein sehr einfaches Beispiel an.

In diesem Beispiel öffne ich myfile.txt mit Hilfe der Open- Funktion. Der try / finally- Block stellt sicher, dass myfile.txt auch dann geschlossen wird, wenn eine unerwartete Ausnahme auftritt .

fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
    for line in fp:
        print(line)
finally:
    fp.close()

Jetzt öffne ich die gleiche Datei mit der folgenden Anweisung:

with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
    for line in fp:
        print(line) 

Wenn Sie sich den Code ansehen, habe ich die Datei nicht geschlossen und es gibt keinen try / finally- Block. Denn mit Anweisung wird myfile.txt automatisch geschlossen . Sie können es sogar überprüfen, indem Sie das print(fp.closed)Attribut aufrufen , das zurückgibt True.

Dies liegt daran, dass die von der Funktion open zurückgegebenen Dateiobjekte (in meinem Beispiel fp) über zwei integrierte Methoden __enter__und verfügen __exit__. Es wird auch als Kontextmanager bezeichnet. __enter__Die Methode wird am Anfang von mit Block __exit__ aufgerufen und die Methode wird am Ende aufgerufen. Hinweis: with- Anweisung funktioniert nur mit Objekten, die das Kontext-Mamangement-Protokoll unterstützen, dh sie haben __enter__und __exit__Methoden. Eine Klasse, die beide Methoden implementiert, wird als Kontextmanagerklasse bezeichnet.

Definieren wir nun unsere eigene Kontextmanager- Klasse.

 class Log:
    def __init__(self,filename):
        self.filename=filename
        self.fp=None    
    def logging(self,text):
        self.fp.write(text+'\n')
    def __enter__(self):
        print("__enter__")
        self.fp=open(self.filename,"a+")
        return self    
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("__exit__")
        self.fp.close()

with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile:
    print("Main")
    logfile.logging("Test1")
    logfile.logging("Test2")

Ich hoffe, Sie haben jetzt ein grundlegendes Verständnis für beide __enter__und __exit__magische Methoden.

N Randhawa
quelle
53

Ich fand es seltsam schwierig, die Python-Dokumente __enter__und __exit__-Methoden von Googling zu finden. Um anderen hier zu helfen, ist der Link:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(Detail ist für beide Versionen gleich)

object.__enter__(self)
Geben Sie den Laufzeitkontext für dieses Objekt ein. Die withAnweisung bindet den Rückgabewert dieser Methode an die in der as-Klausel der Anweisung angegebenen Ziele, falls vorhanden.

object.__exit__(self, exc_type, exc_value, traceback)
Beenden Sie den Laufzeitkontext für dieses Objekt. Die Parameter beschreiben die Ausnahme, durch die der Kontext beendet wurde. Wenn der Kontext ohne Ausnahme beendet wurde, sind alle drei Argumente None.

Wenn eine Ausnahme angegeben wird und die Methode die Ausnahme unterdrücken möchte (dh verhindern möchte, dass sie weitergegeben wird), sollte sie einen wahren Wert zurückgeben. Andernfalls wird die Ausnahme beim Beenden dieser Methode normal verarbeitet.

Beachten Sie, dass __exit__()Methoden die übergebene Ausnahme nicht erneut auslösen sollten. Dies liegt in der Verantwortung des Anrufers.

Ich hatte auf eine klare Beschreibung der __exit__Methodenargumente gehofft . Dies fehlt, aber wir können sie ableiten ...

Vermutlich exc_typeist die Klasse der Ausnahme.

Es heißt, Sie sollten die übergebene Ausnahme nicht erneut auslösen. Dies legt uns nahe, dass eines der Argumente eine tatsächliche Ausnahmeinstanz sein könnte ... oder Sie sollten es selbst anhand des Typs und des Werts instanziieren?

Wir können antworten, indem wir uns diesen Artikel ansehen :
http://effbot.org/zone/python-with-statement.htm

Die folgende __exit__Methode verschluckt beispielsweise einen TypeError, lässt jedoch alle anderen Ausnahmen durch:

def __exit__(self, type, value, traceback):
    return isinstance(value, TypeError)

... so klar valueist eine Exception-Instanz.

Und vermutlich tracebackhandelt es sich um ein Python- Traceback- Objekt.

Anentropisch
quelle
2
Zustimmen. Diese URL ist so schwer zu finden.
Shihao Xu
Es könnte wichtig sein, dieses vergrabene Bit in der PEP-Referenz zu vermerken und die Verwendung von arg zu vermerken
Tcll
43

Zusätzlich zu den obigen Antworten zur Veranschaulichung der Aufrufreihenfolge ein einfaches Ausführungsbeispiel

class myclass:
    def __init__(self):
        print("__init__")

    def __enter__(self): 
        print("__enter__")

    def __exit__(self, type, value, traceback):
        print("__exit__")

    def __del__(self):
        print("__del__")

with myclass(): 
    print("body")

Erzeugt die Ausgabe:

__init__
__enter__
body
__exit__
__del__

Zur Erinnerung: Bei Verwendung der Syntax with myclass() as mcerhält die Variable mc __enter__()im obigen Fall den von zurückgegebenen Wert None! Für eine solche Verwendung muss der Rückgabewert definiert werden, z.

def __enter__(self): 
    print('__enter__')
    return self
Yuri Feldman
quelle
3
Und selbst wenn die Reihenfolge der Definitionen geändert wird, bleibt die Ausführungsreihenfolge gleich!
Sean
1
Das war sehr hilfreich. Vielen Dank.
Reez0
5

versuche meine Antworten hinzuzufügen (mein Gedanke an Lernen):

__enter__und [__exit__]beide sind Methoden, die beim Ein- und Aussteigen aus dem Hauptteil der " with-Anweisung " ( PEP 343 ) aufgerufen werden, und die Implementierung von beiden wird als Kontextmanager bezeichnet.

Die with-Anweisung soll die Flusskontrolle der try finally-Klausel verbergen und den Code unergründlich machen.

Die Syntax der with-Anweisung lautet:

with EXPR as VAR:
    BLOCK

die übersetzt werden (wie in PEP 343 erwähnt):

mgr = (EXPR)
exit = type(mgr).__exit__  # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
    try:
        VAR = value  # Only if "as VAR" is present
        BLOCK
    except:
        # The exceptional case is handled here
        exc = False
        if not exit(mgr, *sys.exc_info()):
            raise
        # The exception is swallowed if exit() returns true
finally:
    # The normal and non-local-goto cases are handled here
    if exc:
        exit(mgr, None, None, None)

Versuchen Sie einen Code:

>>> import logging
>>> import socket
>>> import sys

#server socket on another terminal / python interpreter
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.listen(5)
>>> s.bind((socket.gethostname(), 999))
>>> while True:
>>>    (clientsocket, addr) = s.accept()
>>>    print('get connection from %r' % addr[0])
>>>    msg = clientsocket.recv(1024)
>>>    print('received %r' % msg)
>>>    clientsocket.send(b'connected')
>>>    continue

#the client side
>>> class MyConnectionManager:
>>>     def __init__(self, sock, addrs):
>>>         logging.basicConfig(level=logging.DEBUG, format='%(asctime)s \
>>>         : %(levelname)s --> %(message)s')
>>>         logging.info('Initiating My connection')
>>>         self.sock = sock
>>>         self.addrs = addrs
>>>     def __enter__(self):
>>>         try:
>>>             self.sock.connect(addrs)
>>>             logging.info('connection success')
>>>             return self.sock
>>>         except:
>>>             logging.warning('Connection refused')
>>>             raise
>>>     def __exit__(self, type, value, tb):
>>>             logging.info('CM suppress exception')
>>>             return False
>>> addrs = (socket.gethostname())
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> with MyConnectionManager(s, addrs) as CM:
>>>     try:
>>>         CM.send(b'establishing connection')
>>>         msg = CM.recv(1024)
>>>         print(msg)
>>>     except:
>>>         raise
#will result (client side) :
2018-12-18 14:44:05,863         : INFO --> Initiating My connection
2018-12-18 14:44:05,863         : INFO --> connection success
b'connected'
2018-12-18 14:44:05,864         : INFO --> CM suppress exception

#result of server side
get connection from '127.0.0.1'
received b'establishing connection'

und versuchen Sie es jetzt manuell (folgende Übersetzungssyntax):

>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #make new socket object
>>> mgr = MyConnection(s, addrs)
2018-12-18 14:53:19,331         : INFO --> Initiating My connection
>>> ext = mgr.__exit__
>>> value = mgr.__enter__()
2018-12-18 14:55:55,491         : INFO --> connection success
>>> exc = True
>>> try:
>>>     try:
>>>         VAR = value
>>>         VAR.send(b'establishing connection')
>>>         msg = VAR.recv(1024)
>>>         print(msg)
>>>     except:
>>>         exc = False
>>>         if not ext(*sys.exc_info()):
>>>             raise
>>> finally:
>>>     if exc:
>>>         ext(None, None, None)
#the result:
b'connected'
2018-12-18 15:01:54,208         : INFO --> CM suppress exception

das Ergebnis der Serverseite wie zuvor

Entschuldigung für mein schlechtes Englisch und meine unklaren Erklärungen, danke ....

Wira Bhakti
quelle
1

Dies wird als Kontextmanager bezeichnet, und ich möchte nur hinzufügen, dass ähnliche Ansätze für andere Programmiersprachen existieren. Ein Vergleich kann hilfreich sein, um den Kontextmanager in Python zu verstehen. Grundsätzlich wird ein Kontextmanager verwendet, wenn es sich um einige Ressourcen (Datei, Netzwerk, Datenbank) handelt, die initialisiert und irgendwann heruntergefahren (entsorgt) werden müssen. In Java 7 und höher haben wir eine automatische Ressourcenverwaltung in Form von:

//Java code
try (Session session = new Session())
{
  // do stuff
}

Beachten Sie, dass die Sitzung eine AutoClosableoder mehrere ihrer (vielen) Unterschnittstellen implementieren muss.

In C # verwenden wir Anweisungen zum Verwalten von Ressourcen in Form von:

//C# code
using(Session session = new Session())
{
  ... do stuff.
}

In dem Sessionsollte implementieren IDisposable.

In Python sollte die von uns verwendete Klasse __enter__und implementieren __exit__. Es hat also die Form:

#Python code
with Session() as session:
    #do stuff

Und wie andere betonten, können Sie die try / finally-Anweisung immer in allen Sprachen verwenden, um denselben Mechanismus zu implementieren. Dies ist nur syntaktischer Zucker.

Rohola Zandie
quelle