Spiegazione di '__enter__' e '__exit__' di Python


363

Ho visto questo nel codice di qualcuno. Cosa significa?

    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

19
Una buona spiegazione qui: effbot.org/zone/python-with-statement.htm
Manur

7
@StevenVascellaro Modificare il codice di una domanda è generalmente una cattiva idea, specialmente quando ci sono errori nel codice. Questa domanda è stata posta pensando a Py2 e non c'è motivo di aggiornarla a Py3.
jpaugh

Risposte:


420

L'uso di questi metodi magici ( __enter__, __exit__) consente di implementare oggetti che possono essere facilmente utilizzati con l' withistruzione.

L'idea è che semplifica la creazione di codice che richiede l'esecuzione di un codice "cleandown" (pensalo come un try-finallyblocco). Qualche altra spiegazione qui .

Un esempio utile potrebbe essere un oggetto di connessione al database (che quindi chiude automaticamente la connessione una volta che l'istruzione 'with' corrispondente esce dall'ambito):

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()
        ...

Come spiegato sopra, usa questo oggetto con l' withistruzione (potrebbe essere necessario farlo from __future__ import with_statementall'inizio del file se sei su Python 2.5).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - Anche l'istruzione 'with' ha una buona scrittura.


20
Probabilmente, __enter__dovrebbe tornare selfsempre come allora solo altri metodi della classe possono essere chiamati sul contesto.
ViFI,

3
@ViFI Ci sono 4 esempi di def __enter__(self)PEP 343 e nessuno lo fa return self: python.org/dev/peps/pep-0343 . Perchè la pensi così?
Dolore il

4
@Grief: per 2 motivi, a mio avviso, 1) non sarò in grado di chiamare altri metodi su selfoggetti come spiegato qui: stackoverflow.com/questions/38281853/... 2) self.XYZ è solo una parte di sé e dell'oggetto tornare a gestire solo ciò che mi sembra inappropriato dal punto di vista della manutenzione. Preferirei restituire handle per completare l'oggetto e quindi fornire API pubbliche solo a quei componenti selfoggetto, che voglio esporre all'utente come in with open(abc.txt, 'r') as fin: content = fin.read()
ViFI

4
Gli oggetti file ritornano selfda qui __enter__, fwith open(...) as f
ecco

2
Una sottigliezza che ho dovuto capire: se l'oggetto richiede l'inizializzazione dei parametri, questi dovrebbero essere su init , non su se stessi .
dfrankow,

70

Se sai cosa sono i gestori di contesto, non hai bisogno di altro per capire __enter__e __exit__metodi magici. Vediamo un esempio molto semplice.

In questo esempio, apro myfile.txt con l'aiuto della funzione open . Il blocco try / finally garantisce che anche se si verifica un'eccezione imprevista myfile.txt verrà chiuso.

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

Ora sto aprendo lo stesso file con l' istruzione:

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

Se guardi il codice, non ho chiuso il file e non c'è nessun blocco try / finally . Perché con statement si chiude automaticamente myfile.txt . Puoi anche verificarlo chiamando l' print(fp.closed)attributo - che restituisce True.

Questo perché gli oggetti file (fp nel mio esempio) restituiti dalla funzione open hanno due metodi integrati __enter__e __exit__. È anche noto come gestore del contesto. __enter__Il metodo viene chiamato all'inizio con il blocco e il __exit__ metodo viene chiamato alla fine. Nota: with statement funziona solo con oggetti che supportano il protocollo di mamangement del contesto, cioè che hanno __enter__e __exit__metodi. Una classe che implementa entrambi i metodi è nota come classe gestore di contesto.

Ora definiamo la nostra classe di gestore del contesto .

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

Spero ora che tu abbia una conoscenza di base di entrambi __enter__e __exit__dei metodi magici.


53

Ho trovato stranamente difficile individuare i documenti __enter__e i __exit__metodi di Python tramite Google, quindi per aiutare gli altri ecco il 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
(il dettaglio è lo stesso per entrambe le versioni)

object.__enter__(self)
Immettere il contesto di runtime relativo a questo oggetto. L' withistruzione assocerà il valore restituito di questo metodo alla destinazione o alle destinazioni specificate nella clausola as dell'istruzione, se presente.

object.__exit__(self, exc_type, exc_value, traceback)
Esci dal contesto di runtime relativo a questo oggetto. I parametri descrivono l'eccezione che ha causato l'uscita dal contesto. Se il contesto è uscito senza eccezioni, tutti e tre gli argomenti lo saranno None.

Se viene fornita un'eccezione e il metodo desidera sopprimere l'eccezione (ovvero impedirne la propagazione), dovrebbe restituire un valore vero. In caso contrario, l'eccezione verrà elaborata normalmente all'uscita da questo metodo.

Si noti che i __exit__()metodi non devono rilanciare l'eccezione passata; questa è la responsabilità del chiamante.

Speravo in una chiara descrizione degli __exit__argomenti del metodo. Questo è carente ma possiamo dedurli ...

Presumibilmente exc_typeè la classe dell'eccezione.

Dice che non è necessario aumentare nuovamente l'eccezione passata. Questo ci suggerisce che uno degli argomenti potrebbe essere una vera istanza di eccezione ... o forse dovresti istanziarlo tu stesso dal tipo e dal valore?

Possiamo rispondere guardando questo articolo:
http://effbot.org/zone/python-with-statement.htm

Ad esempio, il seguente __exit__metodo ingoia qualsiasi TypeError, ma lascia passare tutte le altre eccezioni:

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

... così chiaramente valueè un'istanza di eccezione.

E presumibilmente tracebackè un oggetto traceback di Python .


2
Essere d'accordo. Questo URL è così difficile da trovare.
Shihao Xu,

potrebbe essere importante notare questo bit sepolto all'interno del riferimento PEP notando l'uso arg: python.org/dev/peps/pep-0343/#generator-decorator
Tcll

43

Oltre alle risposte di cui sopra per esemplificare l'ordine di invocazione, un semplice esempio di esecuzione

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

Produce l'output:

__init__
__enter__
body
__exit__
__del__

Un promemoria: quando si utilizza la sintassi with myclass() as mc, la variabile mc ottiene il valore restituito da __enter__(), nel caso sopra None! Per tale uso, è necessario definire un valore di ritorno, come ad esempio:

def __enter__(self): 
    print('__enter__')
    return self

3
E anche se la sequenza delle definizioni viene cambiata, l'ordine di esecuzione rimane lo stesso!
Sean,

1
Questo è stato molto utile. Grazie.
Reez0

5

prova ad aggiungere le mie risposte (il mio pensiero di apprendimento):

__enter__ed [__exit__]entrambi sono metodi che vengono invocati all'entrata e all'uscita dal corpo dell'istruzione " the with " ( PEP 343 ) e l'implementazione di entrambi è chiamata gestore del contesto.

l'istruzione with intende nascondere il controllo del flusso della clausola try finally e rendere il codice imperscrutabile.

la sintassi dell'istruzione with è:

with EXPR as VAR:
    BLOCK

che traducono in (come menzionato in PEP 343):

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)

prova un po 'di codice:

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

e ora prova manualmente (seguendo la sintassi di traduzione):

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

il risultato del lato server è lo stesso di prima

scusate il mio cattivo inglese e le mie spiegazioni poco chiare, grazie ....


1

Questo si chiama gestore del contesto e voglio solo aggiungere che esistono approcci simili per altri linguaggi di programmazione. Il loro confronto potrebbe essere utile per comprendere il gestore del contesto in Python. Fondamentalmente, un gestore di contesto viene utilizzato quando abbiamo a che fare con alcune risorse (file, rete, database) che devono essere inizializzate e, a un certo punto, abbattute (eliminate). In Java 7 e versioni successive abbiamo una gestione automatica delle risorse che assume la forma di:

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

Si noti che Session deve implementare AutoClosableo una delle sue (molte) interfacce secondarie.

In C # , utilizziamo le istruzioni per la gestione delle risorse che assume la forma di:

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

In quale Sessiondovrebbe implementare IDisposable.

In python , la classe che usiamo dovrebbe implementare __enter__e __exit__. Quindi prende la forma di:

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

E come altri hanno sottolineato, puoi sempre usare l'istruzione try / finally in tutte le lingue per implementare lo stesso meccanismo. Questo è solo zucchero sintattico.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.