web-dev-qa-db-fra.com

Expliquer '__enter__' et '__exit__' de Python

J'ai vu cela dans le code de quelqu'un. Qu'est-ce que ça veut dire?

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

L'utilisation de ces méthodes magiques (__enter__, __exit__) vous permet d'implémenter des objets qui peuvent être facilement utilisés avec l'instruction with.

L'idée est que cela facilite la construction de code nécessitant l'exécution d'un code de "nettoyage" (considérez-le comme un bloc try-finally.). Quelques explications supplémentaires ici .

Un exemple utile pourrait être un objet de connexion à une base de données (qui ferme ensuite automatiquement la connexion lorsque l'instruction 'with' correspondante sort de la portée):

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

Comme expliqué ci-dessus, utilisez cet objet avec l'instruction with (vous devrez peut-être utiliser from __future__ import with_statement en haut du fichier si vous êtes sur Python 2.5).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - Le 'with' statement ' a aussi une belle écriture.

350
ChristopheD

Si vous savez ce que gestionnaires de contexte vous n'avez alors plus besoin de comprendre les méthodes magiques __enter__ et __exit__. Voyons un exemple très simple.

Dans cet exemple, j'ouvre myfile.txt avec l'aide de la fonction open. Le bloc try/finally garantit que même si une exception inattendue se produit, myfile.txt sera fermé.

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

Maintenant, j'ouvre le même fichier avec l'instruction with:

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

Si vous regardez le code, je n'ai pas fermé le fichier et il n'y a pas de bloc try/finally. Parce que avec, l'instruction se ferme automatiquement myfile.txt. Vous pouvez même le vérifier en appelant l'attribut print(fp.closed), qui renvoie True.

En effet, les objets fichier (fp dans mon exemple) renvoyés par la fonction open ont deux méthodes intégrées __enter__ et __exit__. Il est également appelé gestionnaire de contexte. La méthode __enter__ est appelée au début du bloc avec et la méthode __exit__ est appelée à la fin. Remarque: L'instruction with ne fonctionne qu'avec les objets prenant en charge le protocole de configuration de contexte, c'est-à-dire qu'ils utilisent les méthodes __enter__ et __exit__. Une classe qui implémente les deux méthodes est appelée classe de gestionnaire de contexte.

Définissons maintenant notre propre classe gestionnaire de contexte.

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

J'espère que vous avez maintenant une compréhension de base des deux méthodes magiques __enter__ et __exit__.

52
N Randhawa

J'ai trouvé étrangement difficile de localiser les méthodes python pour les méthodes __enter__ et __exit__ par Google, alors pour aider les autres, voici le lien:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(le détail est le même pour les deux versions)

object.__enter__(self)
Entrez le contexte d’exécution associé à cet objet. L’instruction with lie la valeur de retour de cette méthode à la ou aux cibles spécifiées dans la clause as de l’instruction, le cas échéant.

object.__exit__(self, exc_type, exc_value, traceback)
Quitte le contexte d’exécution associé à cet objet. Les paramètres décrivent l'exception à l'origine de la fermeture du contexte. Si le contexte a été quitté sans exception, les trois arguments seront None.

Si une exception est fournie et que la méthode souhaite la supprimer (c'est-à-dire l'empêcher de se propager), elle devrait renvoyer une valeur vraie. Sinon, l'exception sera traitée normalement à la sortie de cette méthode.

Notez que les méthodes __exit__() ne doivent pas relancer l'exception transmise; C’est la responsabilité de l’appelant.

J'espérais une description claire des arguments de la méthode __exit__. Cela manque mais on peut en déduire ...

Vraisemblablement exc_type est la classe de l'exception.

Il dit que vous ne devriez pas relancer l'exception contournée. Cela nous suggère qu'un des arguments pourrait être une instance d'Exception réelle ... ou peut-être êtes-vous censé l'instancier vous-même à partir du type et de la valeur?

Nous pouvons répondre en regardant cet article:
http://effbot.org/zone/python-with-statement.htm

Par exemple, la méthode __exit__ suivante englobe toutes les erreurs TypeError, mais laisse passer toutes les autres exceptions:

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

... si clairement value est une instance d'Exception.

Et vraisemblablement traceback est un objet Python traceback .

40
Anentropic

Outre les réponses ci-dessus pour illustrer l'ordre d'appel, un exemple d'exécution simple

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

Produit la sortie:

__init__
__enter__
body
__exit__
__del__

Rappel: en utilisant la syntaxe with myclass() as mc, la variable mc récupère la valeur renvoyée par __enter__(), dans le cas ci-dessus None! Pour une telle utilisation, il est nécessaire de définir une valeur de retour, telle que:

def __enter__(self): 
    print('__enter__')
    return self
28
Yuri Feldman

essayez d'ajouter mes réponses (ma pensée d'apprendre):

__enter__ et [__exit__] sont des méthodes invoquées à l'entrée et à la sortie du corps de " l'instruction with " ( PEP 34 ) et la mise en oeuvre des deux est appelée gestionnaire de contexte.

l'instruction with a pour but de masquer le contrôle de flux de la clause try finally et de rendre le code impénétrable.

la syntaxe de l'instruction with est la suivante:

with EXPR as VAR:
    BLOCK

qui se traduisent par (comme mentionné dans 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)

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

et maintenant, essayez manuellement (en suivant la syntaxe de traduction):

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

le résultat du côté serveur comme avant

désolé pour mon mauvais anglais et mes explications peu claires, merci ....

3
Wira Bhakti