Giải thích '__enter__' của Python và '__exit__'


363

Tôi đã thấy điều này trong mã của ai đó. Nó có nghĩa là gì?

    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
Một lời giải thích tốt ở đây: effbot.org/zone/python-with-statement.htmlm
Manur

7
@StevenVascellaro Chỉnh sửa mã của một câu hỏi thường là một ý tưởng tồi, đặc biệt là khi có lỗi trong mã. Câu hỏi này đã được hỏi với Py2 và không có lý do gì để cập nhật nó lên Py3.
jpaugh

Câu trả lời:


420

Sử dụng các phương thức ma thuật này ( __enter__, __exit__) cho phép bạn thực hiện các đối tượng có thể được sử dụng dễ dàng với withcâu lệnh.

Ý tưởng là nó giúp bạn dễ dàng xây dựng mã cần một số mã 'cleandown' được thực thi (nghĩ về nó như một try-finallykhối). Một số giải thích thêm ở đây .

Một ví dụ hữu ích có thể là một đối tượng kết nối cơ sở dữ liệu (sau đó tự động đóng kết nối một khi câu lệnh 'with'-tương ứng nằm ngoài phạm vi):

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

Như đã giải thích ở trên, hãy sử dụng đối tượng này với withcâu lệnh (bạn có thể cần phải làm from __future__ import with_statementở đầu tệp nếu bạn đang ở trên Python 2.5).

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - 'với' tuyên bố ' cũng có một bài viết hay.


20
Có lẽ, __enter__nên trả về selfluôn vì khi đó chỉ các phương thức khác của lớp có thể được gọi trong ngữ cảnh.
ViFI

3
@ViFI Có 4 ví dụ về def __enter__(self)PEP 343 và không ai làm return self: python.org/dev/peps/pep-0343 . Tại sao bạn nghĩ vậy?
Đau buồn

4
@Grief: Vì 2 lý do, theo tôi, 1) tôi sẽ không thể gọi các phương thức khác trên selfđối tượng như được giải thích ở đây: stackoverflow.com/questions/38281853/ Lỗi 2) self.XYZ chỉ là một phần của đối tượng tự và trả lại xử lý chỉ cho rằng điều đó có vẻ không phù hợp với tôi từ quan điểm bảo trì. Tôi thà trả lại xử lý cho đối tượng hoàn chỉnh và sau đó cung cấp API công khai cho chỉ những selfđối tượng thành phần mà tôi muốn hiển thị cho người dùng như trong with open(abc.txt, 'r') as fin: content = fin.read()
ViFI

4
Các đối tượng tệp trở về selftừ __enter__đó, đó là lý do tại sao bạn có thể xử lý tệp như fbên trongwith open(...) as f
Holdenweb

2
Một điều tinh tế tôi đã phải hiểu: nếu đối tượng yêu cầu các tham số để khởi tạo, thì những đối tượng đó phải là trên init chứ không phải tự .
dfrankow

70

Nếu bạn biết người quản lý bối cảnh là gì thì bạn không cần gì thêm để hiểu __enter____exit__phương pháp kỳ diệu. Hãy xem một ví dụ rất đơn giản.

Trong ví dụ này tôi đang mở myfile.txt với sự trợ giúp của hàm mở . Khối try / cuối cùng đảm bảo rằng ngay cả khi có ngoại lệ không mong muốn xảy ra, myfile.txt sẽ bị đóng.

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

Bây giờ tôi đang mở cùng một tệp với câu lệnh:

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

Nếu bạn nhìn vào mã, tôi đã không đóng tệp và không có khối thử / cuối cùng . Bởi vì với câu lệnh sẽ tự động đóng myfile.txt . Bạn thậm chí có thể kiểm tra nó bằng cách gọi print(fp.closed)thuộc tính - trả về True.

Điều này là do các đối tượng tệp (fp trong ví dụ của tôi) được trả về bởi hàm mở có hai phương thức dựng sẵn __enter____exit__. Nó còn được gọi là quản lý bối cảnh. __enter__phương thức được gọi khi bắt đầu với khối và __exit__ phương thức được gọi ở cuối. Lưu ý: với câu lệnh chỉ hoạt động với các đối tượng hỗ trợ giao thức mamangement bối cảnh tức là chúng có __enter____exit__các phương thức. Một lớp thực hiện cả hai phương thức được gọi là lớp quản lý bối cảnh.

Bây giờ hãy xác định lớp trình quản lý bối cảnh của riêng chúng ta .

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

Tôi hy vọng bây giờ bạn có hiểu biết cơ bản về cả hai __enter____exit__phương pháp ma thuật.


53

Tôi thấy rất khó để xác định vị trí các tài liệu __enter____exit__phương thức python cho Googling, vì vậy để giúp đỡ những người khác ở đây là liên kết:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-manager
https://docs.python.org/3/reference/datamodel.html#with-statement-context-manager
(chi tiết giống nhau cho cả hai phiên bản)

object.__enter__(self)
Nhập bối cảnh thời gian chạy liên quan đến đối tượng này. Câu withlệnh sẽ liên kết giá trị trả về của phương thức này với (các) mục tiêu được chỉ định trong mệnh đề as của câu lệnh, nếu có.

object.__exit__(self, exc_type, exc_value, traceback)
Thoát khỏi bối cảnh thời gian chạy liên quan đến đối tượng này. Các tham số mô tả ngoại lệ khiến bối cảnh bị thoát. Nếu bối cảnh đã được thoát mà không có ngoại lệ, cả ba đối số sẽ là None.

Nếu một ngoại lệ được cung cấp và phương thức muốn loại bỏ ngoại lệ đó (nghĩa là ngăn không cho nó được truyền bá), thì nó sẽ trả về một giá trị thực. Mặt khác, ngoại lệ sẽ được xử lý bình thường khi thoát khỏi phương thức này.

Lưu ý rằng __exit__()các phương thức không nên lấy lại ngoại lệ đã qua; đây là trách nhiệm của người gọi

Tôi đã hy vọng cho một mô tả rõ ràng về các __exit__đối số phương thức. Điều này là thiếu nhưng chúng ta có thể suy luận chúng ...

Có lẽ exc_typelà lớp của ngoại lệ.

Nó nói rằng bạn không nên nâng lại ngoại lệ đã qua. Điều này gợi ý cho chúng tôi rằng một trong các đối số có thể là một trường hợp Ngoại lệ thực tế ... hoặc có thể bạn phải tự khởi tạo nó từ loại và giá trị?

Chúng tôi có thể trả lời bằng cách xem bài viết này:
http://effbot.org/zone/python-with-statement.htmlm

Ví dụ: __exit__phương thức sau đây nuốt bất kỳ TypeError nào, nhưng cho phép tất cả các ngoại lệ khác thông qua:

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

... rõ ràng valuelà một ví dụ ngoại lệ.

Và có lẽ tracebacklà một Python traceback đối tượng.


2
Đồng ý. Url này rất khó tìm.
Shihao Xu

có thể rất quan trọng để lưu ý bit chôn này trong tham chiếu PEP lưu ý sử dụng arg: python.org/dev/peps/pep-0343/#generator-decorator
Tcll 22/07/19

43

Ngoài các câu trả lời ở trên để minh họa thứ tự gọi, một ví dụ chạy đơn giản

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

Sản xuất đầu ra:

__init__
__enter__
body
__exit__
__del__

Một lời nhắc nhở: khi sử dụng cú pháp with myclass() as mc, biến mc sẽ nhận được giá trị được trả về bởi __enter__(), trong trường hợp trên None! Để sử dụng như vậy, cần xác định giá trị trả về, chẳng hạn như:

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

3
Và ngay cả khi chuỗi các định nghĩa được chuyển đổi, thứ tự thực hiện vẫn giữ nguyên!
Sean

1
Điều này rất hữu ích. Cảm ơn bạn.
Reez0

5

hãy thử thêm câu trả lời của tôi (suy nghĩ của tôi về việc học):

__enter__[__exit__]cả hai đều là các phương thức được gọi khi vào và thoát khỏi phần thân của " câu lệnh with " ( PEP 343 ) và việc thực hiện cả hai được gọi là trình quản lý bối cảnh.

câu lệnh with có ý định ẩn điều khiển luồng của mệnh đề try cuối cùng và làm cho mã không thể hiểu được.

cú pháp của câu lệnh with là:

with EXPR as VAR:
    BLOCK

dịch sang (như đã đề cập trong 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)

thử một số mã:

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

và bây giờ hãy thử thủ công (theo cú pháp dịch sau):

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

kết quả của phía máy chủ giống như trước

xin lỗi vì tiếng anh không tốt và những lời giải thích không rõ ràng của tôi, cảm ơn bạn ....


1

Đây được gọi là trình quản lý bối cảnh và tôi chỉ muốn thêm rằng các cách tiếp cận tương tự tồn tại cho các ngôn ngữ lập trình khác. So sánh chúng có thể hữu ích trong việc hiểu người quản lý bối cảnh trong python. Về cơ bản, một trình quản lý bối cảnh được sử dụng khi chúng ta đang xử lý một số tài nguyên (tệp, mạng, cơ sở dữ liệu) cần được khởi tạo và tại một số điểm, bị phá vỡ (xử lý). Trong Java 7 trở lên, chúng tôi có quản lý tài nguyên tự động có dạng:

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

Lưu ý rằng Phiên cần triển khai AutoClosablehoặc một trong (nhiều) giao diện con của nó.

Trong C # , chúng tôi đã sử dụng các câu lệnh để quản lý tài nguyên có dạng:

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

Trong đó Sessionnên thực hiện IDisposable.

Trong python , lớp mà chúng ta sử dụng nên thực hiện __enter____exit__. Vì vậy, nó có hình thức:

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

Và như những người khác đã chỉ ra, bạn luôn có thể sử dụng câu lệnh try / cuối cùng trong tất cả các ngôn ngữ để thực hiện cùng một cơ chế. Đây chỉ là cú pháp đường.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.