Menjelaskan '__enter__' dan '__exit__' Python

363

Saya melihat ini dalam kode seseorang. Apa artinya?

    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
sumber
19
Penjelasan yang bagus di sini: effbot.org/zone/python-with-statement.htm
Manur
7
@StevenVascellaro Mengedit kode pertanyaan umumnya adalah ide yang buruk, terutama ketika ada kesalahan dalam kode. Pertanyaan ini diajukan dengan mempertimbangkan Py2, dan tidak ada alasan untuk memperbaruinya ke Py3.
jpaugh

Jawaban:

420

Menggunakan metode ajaib ini ( __enter__, __exit__) memungkinkan Anda untuk mengimplementasikan objek yang dapat digunakan dengan mudah dengan withpernyataan.

Idenya adalah membuatnya mudah untuk membangun kode yang memerlukan beberapa kode 'cleandown' dieksekusi (anggap sebagai try-finallyblok). Penjelasan lebih lanjut di sini .

Contoh yang berguna bisa berupa objek koneksi basis data (yang kemudian secara otomatis menutup koneksi setelah pernyataan 'with' keluar dari lingkup):

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

Seperti dijelaskan di atas, gunakan objek ini dengan withpernyataan (Anda mungkin perlu melakukan from __future__ import with_statementdi bagian atas file jika Anda menggunakan Python 2.5).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - 'dengan' pernyataan ' memiliki langgan yang bagus juga.

ChristopheD
sumber
20
Mungkin, __enter__harus kembali selfselalu karena hanya metode lain dari kelas yang dapat dipanggil pada konteksnya.
ViFI
3
@ViFI Ada 4 contoh def __enter__(self)dalam PEP 343 dan tidak ada yang melakukannya return self: python.org/dev/peps/pep-0343 . Mengapa menurut Anda begitu?
Duka
4
@Grief: Untuk 2 alasan, Menurut saya, 1) saya tidak akan dapat memanggil metode lain pada selfobjek seperti yang dijelaskan di sini: stackoverflow.com/questions/38281853/… 2) self.XYZ hanyalah bagian dari objek mandiri dan kembali menangani hanya untuk hal yang tampaknya tidak pantas bagi saya dari sudut pandang pemeliharaan. Saya lebih suka untuk kembali menangani untuk menyelesaikan objek dan kemudian menyediakan API publik untuk hanya selfobjek komponen tersebut , yang ingin saya paparkan kepada pengguna seperti di with open(abc.txt, 'r') as fin: content = fin.read()
ViFI
4
Objek file kembali selfdari __enter__, yaitu bagaimana Anda dapat memproses file sebagai fdi dalamwith open(...) as f
holdenweb
2
Satu kehalusan yang harus saya pahami: jika objek membutuhkan parameter untuk diinisialisasi, itu harus init , bukan self .
dfrankow
70

Jika Anda tahu apa itu manajer konteks , maka Anda tidak perlu apa-apa lagi untuk memahami __enter__dan __exit__metode sulap. Mari kita lihat contoh yang sangat sederhana.

Dalam contoh ini saya membuka myfile.txt dengan bantuan fungsi terbuka . The try / akhirnya memblokir memastikan bahwa bahkan jika pengecualian tak terduga terjadi myfile.txt akan ditutup.

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

Sekarang saya membuka file yang sama dengan pernyataan:

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

Jika Anda melihat kode, saya tidak menutup file & tidak ada blok coba / akhirnya . Karena dengan pernyataan otomatis menutup myfile.txt . Anda bahkan dapat memeriksanya dengan memanggil print(fp.closed)atribut - yang mengembalikan True.

Ini karena objek file (fp dalam contoh saya) yang dikembalikan oleh fungsi terbuka memiliki dua metode bawaan __enter__dan __exit__. Ia juga dikenal sebagai manajer konteks. __enter__Metode dipanggil di awal dengan blok dan __exit__ metode disebut di akhir. Catatan: dengan pernyataan hanya bekerja dengan objek yang mendukung konteks konteks protokol yaitu mereka miliki __enter__dan __exit__metode. Kelas yang menerapkan kedua metode ini dikenal sebagai kelas konteks manajer.

Sekarang mari kita mendefinisikan kelas manajer konteks kita sendiri .

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

Saya harap sekarang Anda memiliki pemahaman dasar tentang keduanya __enter__dan __exit__metode sihir.

N Randhawa
sumber
53

Anehnya, sulit untuk menemukan dokumen python untuk __enter__dan __exit__metode dengan Googling, jadi untuk membantu orang lain di sini adalah tautannya:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(detailnya sama untuk kedua versi)

object.__enter__(self)
Masukkan konteks runtime yang terkait dengan objek ini. The withpernyataan akan mengikat nilai metode ini kembali ke target (s) ditentukan dalam sebagai klausul pernyataan, jika ada.

object.__exit__(self, exc_type, exc_value, traceback)
Keluar dari konteks runtime yang terkait dengan objek ini. Parameter menggambarkan pengecualian yang menyebabkan konteks keluar. Jika konteksnya keluar tanpa terkecuali, ketiga argumen akan menjadi None.

Jika pengecualian diberikan, dan metode ingin menekan pengecualian (yaitu, mencegahnya agar tidak diperbanyak), ia harus mengembalikan nilai sebenarnya. Jika tidak, pengecualian akan diproses secara normal saat keluar dari metode ini.

Perhatikan bahwa __exit__()metode tidak boleh mengembalikan pengecualian yang diteruskan; ini adalah tanggung jawab penelepon.

Saya berharap penjelasan yang jelas tentang __exit__argumen metode. Ini kurang tetapi kita dapat menyimpulkan mereka ...

Agaknya exc_typeadalah kelas pengecualian.

Dikatakan Anda tidak harus menaikkan kembali pengecualian yang diterima. Ini menyarankan kepada kita bahwa salah satu argumen mungkin merupakan contoh Pengecualian yang sebenarnya ... atau mungkin Anda harus membuat instance sendiri dari jenis dan nilai?

Kami dapat menjawab dengan melihat artikel ini:
http://effbot.org/zone/python-with-statement.htm

Misalnya, __exit__metode berikut ini menelan TypeError apa pun, tetapi membiarkan semua pengecualian lainnya melalui:

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

... begitu jelas valueadalah contoh Pengecualian.

Dan mungkin tracebackadalah objek traceback Python .

Anentropik
sumber
2
Setuju. Url ini sangat sulit ditemukan.
Shihao Xu
mungkin penting untuk mencatat bit yang terkubur ini dalam referensi PEP dengan memperhatikan penggunaan arg: python.org/dev/peps/pep-0343/#generator-decorator
Tcll
43

Selain jawaban di atas untuk mencontohkan pesanan doa, contoh menjalankan sederhana

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

Menghasilkan output:

__init__
__enter__
body
__exit__
__del__

Pengingat: saat menggunakan sintaks with myclass() as mc, variabel mc mendapatkan nilai yang dikembalikan oleh __enter__(), dalam kasus di atas None! Untuk penggunaan seperti itu, perlu mendefinisikan nilai kembali, seperti:

def __enter__(self): 
    print('__enter__')
    return self
Yuri Feldman
sumber
3
Dan bahkan jika urutan definisi diaktifkan, urutan eksekusi tetap sama!
Sean
1
Ini sangat membantu. Terima kasih.
Reez0
5

coba tambahkan jawaban saya (pemikiran saya untuk belajar):

__enter__dan [__exit__]keduanya adalah metode yang dipanggil saat masuk dan keluar dari badan " the with statement " ( PEP 343 ) dan implementasi keduanya disebut manajer konteks.

pernyataan with bermaksud menyembunyikan kontrol aliran coba akhirnya klausa dan membuat kode tidak dapat dipahami.

sintaks dari pernyataan with adalah:

with EXPR as VAR:
    BLOCK

yang diterjemahkan ke (sebagaimana disebutkan dalam 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)

coba beberapa kode:

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

dan sekarang coba secara manual (ikuti sintaks terjemahan):

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

hasil sisi server sama seperti sebelumnya

maaf untuk bahasa inggris saya yang buruk dan penjelasan saya yang tidak jelas, terima kasih ....

Wira Bhakti
sumber
1

Ini disebut manajer konteks dan saya hanya ingin menambahkan bahwa pendekatan serupa ada untuk bahasa pemrograman lain. Membandingkannya bisa membantu dalam memahami manajer konteks dengan python. Pada dasarnya, manajer konteks digunakan ketika kita berhadapan dengan beberapa sumber daya (file, jaringan, database) yang perlu diinisialisasi dan pada titik tertentu, runtuh (dibuang). Di Java 7 dan di atas kami memiliki manajemen sumber daya otomatis yang mengambil bentuk:

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

Perhatikan bahwa Sesi perlu mengimplementasikan AutoClosableatau salah satu (banyak) sub-antarmuka-nya.

Di C # , kami telah menggunakan pernyataan untuk mengelola sumber daya yang berbentuk:

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

Di mana Sessionharus menerapkan IDisposable.

Dalam python , kelas yang kita gunakan harus mengimplementasikan __enter__dan __exit__. Jadi itu mengambil bentuk:

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

Dan seperti yang ditunjukkan orang lain, Anda selalu dapat menggunakan pernyataan try / akhirnya dalam semua bahasa untuk menerapkan mekanisme yang sama. Ini hanya gula sintaksis.

Rohola Zandie
sumber