पायथन की '__enter__' और '__exit__' की व्याख्या करना


361

मैंने किसी के कोड में यह देखा। इसका क्या मतलब है?

    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
यहाँ एक अच्छी व्याख्या: effbot.org/zone/python-with-statement.htm
मनूर

7
@StevenVascellaro एक प्रश्न के कोड का संपादन आमतौर पर एक बुरा विचार है, खासकर जब कोड में त्रुटियां होती हैं। यह प्रश्न Py2 को ध्यान में रखते हुए पूछा गया था, और Py3 में इसे अपडेट करने का कोई कारण नहीं है।
jpaugh

जवाबों:


420

इन जादूई विधियों ( __enter__, __exit__) का उपयोग करने से आप उन वस्तुओं को लागू कर सकते हैं, जिन्हें withकथन के साथ आसानी से उपयोग किया जा सकता है ।

विचार यह है कि यह कोड बनाना आसान बनाता है, जिसे निष्पादित किए जाने वाले कुछ 'क्लींडाउन' कोड की आवश्यकता होती है (इसे एक try-finallyब्लॉक के रूप में सोचो )। कुछ और स्पष्टीकरण यहाँ

एक उपयोगी उदाहरण एक डेटाबेस कनेक्शन ऑब्जेक्ट हो सकता है (जो तब संबंधित 'स्टेटमेंट' के दायरे से बाहर जाने पर स्वचालित रूप से कनेक्शन बंद कर देता है):

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

जैसा कि ऊपर बताया गया है, इस ऑब्जेक्ट को withस्टेटमेंट के साथ उपयोग करें ( from __future__ import with_statementयदि आप पायथन 2.5 पर हैं तो आपको फ़ाइल के शीर्ष पर करने की आवश्यकता हो सकती है )।

with DatabaseConnection() as mydbconn:
    # do stuff

PEP343 - 'स्टेटमेंट' के साथ एक अच्छा राइटअप भी है।


20
शायद, हमेशा __enter__वापस आना चाहिए selfक्योंकि तब ही संदर्भ में कक्षा के अन्य तरीकों को बुलाया जा सकता है।
वीआईएफआई

3
@ViFI def __enter__(self)PEP 343 में 4 उदाहरण हैं और कोई भी ऐसा नहीं करता है return self: python.org/dev/peps/pep-0343 । आप ऐसा क्यों सोचते हैं?
शोक

4
@ दुख: 2 कारणों से, मेरी राय में, 1) मैं selfऑब्जेक्ट पर अन्य तरीकों को कॉल नहीं कर पाऊंगा, जैसा कि यहां बताया गया है: stackoverflow.com/questions/38281853/… 2) self.XYZ आत्म वस्तु का सिर्फ एक हिस्सा है) केवल वही लौटना जो रखरखाव के दृष्टिकोण से मेरे लिए अनुपयुक्त लगता है। मैं केवल ऑब्जेक्ट को पूरा करने के लिए हैंडल को वापस करना पसंद करूंगा और फिर केवल उन्हीं घटकों selfऑब्जेक्ट को सार्वजनिक API प्रदान करूंगा, जिन्हें मैं उपयोगकर्ता के सामने उजागर करना चाहता हूं जैसे with open(abc.txt, 'r') as fin: content = fin.read()
ViFI

4
फ़ाइल ऑब्जेक्ट्स से लौटते selfहैं __enter__, जो है कि आप कैसे फाइल को fअंदर ले जा सकते हैंwith open(...) as f
होल्डनवेब

2
एक सूक्ष्मता मुझे समझनी थी: यदि ऑब्जेक्ट को शुरू करने के लिए मापदंडों की आवश्यकता होती है, तो वे स्वयं पर नहीं, इनिट पर होना चाहिए ।
dfrankow

70

यदि आप जानते हैं कि संदर्भ प्रबंधक क्या हैं तो आपको समझने __enter__और __exit__जादू के तरीकों के लिए अधिक कुछ नहीं चाहिए । एक बहुत ही सरल उदाहरण देखते हैं।

इस उदाहरण में मैं खुले फ़ंक्शन की मदद से myfile.txt खोल रहा हूं । ट्राई / अंत में यह सुनिश्चित है कि एक अप्रत्याशित अपवाद तब होता है, भले ही ब्लॉक myfile.txt बंद कर दिया जाएगा।

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

अब मैं के साथ एक ही फ़ाइल खोलने रहा हूँ के साथ बयान:

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

यदि आप कोड को देखते हैं, तो मैंने फ़ाइल को बंद नहीं किया और कोई कोशिश नहीं / अंत में ब्लॉक है। क्योंकि कथन के साथ स्वचालित रूप से myfile.txt बंद हो जाता है । आप इसे print(fp.closed)विशेषता कहकर भी जांच सकते हैं - जो रिटर्न देता है True

इसका कारण यह है कि फ़ाइल फ़ंक्शन (मेरे उदाहरण में fp) खुले फ़ंक्शन द्वारा लौटाए गए दो अंतर्निहित तरीके हैं __enter__और __exit__। इसे संदर्भ प्रबंधक के रूप में भी जाना जाता है। __enter__विधि के शुरू में कहा जाता है के साथ ब्लॉक और __exit__ विधि अंत में कहा जाता है। नोट: कथन के साथ केवल उन वस्तुओं के साथ काम करता है जो संदर्भ mamangement प्रोटोकॉल का समर्थन करते हैं अर्थात उनके पास __enter__और __exit__विधियाँ हैं। एक वर्ग जो दोनों विधियों को लागू करता है उसे संदर्भ प्रबंधक वर्ग के रूप में जाना जाता है।

अब अपने स्वयं के संदर्भ प्रबंधक वर्ग को परिभाषित करने देता है ।

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

मुझे उम्मीद है कि अब आपको दोनों __enter__और __exit__जादू के तरीकों की बुनियादी समझ होगी ।


53

Googling द्वारा पायथन डॉक्स के लिए __enter__और __exit__तरीकों का पता लगाना मुझे अजीब लगा , इसलिए यहां दूसरों की मदद करना लिंक है:

https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-state-context-managers
(विस्तार दोनों संस्करणों के लिए समान है)

object.__enter__(self)
इस ऑब्जेक्ट से संबंधित रनटाइम संदर्भ दर्ज करें। withबयान लक्ष्य (लक्ष्यों) के लिए इस विधि द्वारा दिया गया मान बयान के रूप में खंड में निर्दिष्ट है, यदि कोई बाध्य होगा।

object.__exit__(self, exc_type, exc_value, traceback)
इस ऑब्जेक्ट से संबंधित रनटाइम संदर्भ से बाहर निकलें। पैरामीटर अपवाद का वर्णन करते हैं जिसके कारण संदर्भ से बाहर निकल गया। यदि संदर्भ को अपवाद के बिना बाहर किया गया था, तो सभी तीन तर्क होंगे None

यदि एक अपवाद की आपूर्ति की जाती है, और विधि अपवाद को दबाने की इच्छा रखती है (यानी, इसे प्रचारित होने से रोकती है), तो इसे एक वास्तविक मूल्य वापस करना चाहिए। अन्यथा, इस विधि से बाहर निकलने पर अपवाद को सामान्य रूप से संसाधित किया जाएगा।

ध्यान दें कि __exit__()विधियों को पारित-अपवाद को अलग नहीं करना चाहिए; यह कॉलर की जिम्मेदारी है।

मैं __exit__विधि तर्कों के स्पष्ट विवरण की उम्मीद कर रहा था । यह कमी है, लेकिन हम उन्हें कम कर सकते हैं ...

संभवतः exc_typeअपवाद का वर्ग है।

यह कहता है कि आपको उत्तीर्ण अपवाद को फिर से नहीं उठाना चाहिए। इससे हमें पता चलता है कि तर्कों में से एक वास्तविक अपवाद उदाहरण हो सकता है ... या हो सकता है कि आप इसे अपने आप को टाइप और मान से इंस्टेंट करने वाले हों?

हम इस लेख को देखकर जवाब दे सकते हैं:
http://effbot.org/zone/python-with-statement.htm

उदाहरण के लिए, निम्न __exit__विधि किसी भी प्रकार को निगल लेती है, लेकिन अन्य सभी अपवादों को इसके माध्यम से पूरा करती है:

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

... तो स्पष्ट रूप valueसे एक अपवाद उदाहरण है।

और संभवतः tracebackएक पायथन ट्रेसबैक ऑब्जेक्ट है।


2
इस बात से सहमत। यह यूआरएल इतना मुश्किल है।
शिहाओ जू

पीईपी संदर्भ के भीतर इस दफन बिट को नोट करने के लिए महत्वपूर्ण हो सकता है कि arg उपयोग को ध्यान में रखते हुए: python.org/dev/peps/pep-0343/#generator-decorator
Tcll

43

उपर्युक्त उत्तरों के अलावा, आह्वान आदेश को सरल बनाने के लिए, एक सरल उदाहरण है

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

उत्पादन का उत्पादन:

__init__
__enter__
body
__exit__
__del__

एक अनुस्मारक: वाक्यविन्यास का उपयोग करते समय with myclass() as mc, चर mc __enter__()को उपरोक्त मामले में, द्वारा लौटाया गया मान मिलता है None! ऐसे उपयोग के लिए, वापसी मूल्य को परिभाषित करने की आवश्यकता है, जैसे:

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

3
और अगर परिभाषाओं का क्रम बदल दिया जाता है, तो भी निष्पादन क्रम समान रहता है!
सीन

1
यह बहुत मददगार था। धन्यवाद।
Reez0

5

मेरे उत्तर जोड़ने का प्रयास करें (सीखने का मेरा विचार):

__enter__और [__exit__]दोनों ऐसे तरीके हैं जो " स्टेटमेंट " ( PEP 343 ) के बॉडी से प्रवेश करने और बाहर निकलने के लिए लगाए जाते हैं और दोनों के कार्यान्वयन को संदर्भ प्रबंधक कहा जाता है।

इस कथन के साथ अंत में प्रयास के प्रवाह नियंत्रण को छिपाने का इरादा है और कोड को अयोग्य बना सकता है।

बयान के साथ वाक्य रचना है:

with EXPR as VAR:
    BLOCK

जो अनुवाद (पीईपी 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)

कुछ कोड आज़माएं:

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

और अब मैन्युअल रूप से प्रयास करें (अनुवाद वाक्य रचना का अनुसरण करें):

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

सर्वर साइड का परिणाम पहले जैसा है

मेरी खराब अंग्रेजी और मेरी अस्पष्ट व्याख्याओं के लिए खेद है, धन्यवाद ...।


1

इसे संदर्भ प्रबंधक कहा जाता है और मैं केवल यह जोड़ना चाहता हूं कि अन्य प्रोग्रामिंग भाषाओं के लिए समान दृष्टिकोण मौजूद हैं। उनकी तुलना अजगर में संदर्भ प्रबंधक को समझने में मददगार हो सकती है। मूल रूप से, एक संदर्भ प्रबंधक का उपयोग तब किया जाता है जब हम कुछ संसाधनों (फ़ाइल, नेटवर्क, डेटाबेस) के साथ काम कर रहे होते हैं जिन्हें आरंभ में और कुछ बिंदु पर, आंसू गिराए जाने (निपटाने) की आवश्यकता होती है। में जावा 7 और ऊपर हम स्वत: संसाधन प्रबंधन कि का रूप ले लेता है:

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

ध्यान दें कि सत्र AutoClosableको इसके (कई) उप-इंटरफेसों को लागू करने या करने की आवश्यकता है ।

में सी # , हम संसाधनों है कि का रूप ले लेता के प्रबंधन के लिए बयानों का उपयोग कर दिया है:

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

जिसमें Sessionअमल करना चाहिए IDisposable

में अजगर , वर्ग है कि हम का उपयोग को लागू करना चाहिए __enter__और __exit__। तो यह का रूप लेता है:

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

और जैसा कि अन्य ने बताया है, आप एक ही तंत्र को लागू करने के लिए सभी भाषाओं में हमेशा कोशिश / अंत में बयान का उपयोग कर सकते हैं। यह सिंटैक्टिक शुगर है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.