2013-03-15 5 views
7

यह सामान्य ज्ञान अजगर __del__ तरीके के रूप में इस विधि कहा जाता हो जाता है इसकी गारंटी नहीं है, महत्वपूर्ण बातों को साफ करने के लिए नहीं किया जाना चाहिए के रूप में। विकल्प कई संदर्भों में वर्णित एक संदर्भ प्रबंधक का उपयोग है।पायथन में __del__ के उपयोग से बचने के लिए संदर्भ प्रबंधक का उपयोग कैसे करें?

लेकिन मैं काफी है कि कैसे एक संदर्भ प्रबंधक का उपयोग करने के लिए एक वर्ग के पुनर्लेखन के लिए समझ में नहीं आता। विस्तृत करने के लिए, मेरे पास एक सरल (गैर-कामकाजी) उदाहरण है जिसमें एक रैपर वर्ग खुलता है और डिवाइस को बंद करता है, और किसी भी मामले में डिवाइस को बंद कर देगा, कक्षा का उदाहरण इसके दायरे से बाहर हो जाता है (अपवाद आदि)।

class MyWrapper(object): 
    def __init__(self, device): 
     self.device = device 

    def open(self): 
     self.device.open() 

    def close(self): 
     self.device.close() 

    def __del__(self): 
     self.close() 

इस वर्ग एक और वर्ग myclass.py द्वारा किया जाता है:

पहली फ़ाइल mydevice.py खोलने के लिए और एक डिवाइस बंद करने के लिए एक मानक आवरण वर्ग है

import mydevice 


class MyClass(object): 

    def __init__(self, device): 

     # calls open in mydevice 
     self.mydevice = mydevice.MyWrapper(device) 
     self.mydevice.open() 

    def processing(self, value): 
     if not value: 
      self.mydevice.close() 
     else: 
      something_else() 

मेरा प्रश्न: जब मैं लागू __enter__ और __exit__ तरीकों के साथ mydevice.py में संदर्भ प्रबंधक, कैसे इस वर्ग myclass.py में संभाला जा सकता है? मुझे

def __init__(self, device): 
    with mydevice.MyWrapper(device): 
     ??? 

जैसे कुछ करने की आवश्यकता है, लेकिन फिर इसे कैसे संभालें? शायद मैंने कुछ महत्वपूर्ण नजरअंदाज कर दिया? या क्या मैं केवल एक फ़ंक्शन के भीतर एक संदर्भ प्रबंधक का उपयोग कर सकता हूं, न कि क्लास स्कोप के अंदर एक चर के रूप में?

उत्तर

4

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

जानते हुए भी कि वास्तव में क्या डिवाइस है और यह कैसे काम करता है, यह पास अधिक कहना मुश्किल है, लेकिन मूल विचार है कि यदि संभव हो, यह बेहतर है के लिए एक ही डिवाइस सही खोलने जब आप इसे उपयोग करने की आवश्यकता है, और फिर बिना

तुरंत बाद में। तो अपने processing क्या अधिक की तरह कुछ करने के लिए, को बदलने की जरूरत हो सकती है:

def processing(self, value): 
    with self.device: 
     if value: 
      something_else() 

तो self.device एक उचित रूप से लिखा संदर्भ प्रबंधक है, यह __enter__ में उपकरण खोलने के लिए और __exit__ में इसे बंद कर देना चाहिए। यह सुनिश्चित करता है कि डिवाइस with ब्लॉक के अंत में बंद हो जाएगा।

बेशक

, संसाधनों के कुछ प्रकार के लिए, यह संभव नहीं यह करने के लिए (उदाहरण के लिए, क्योंकि खोलने और डिवाइस को बंद करने के लिए महत्वपूर्ण राज्य खो देता है, या है एक धीमी गति से आपरेशन) है। यदि यह आपका मामला है, तो आप __del__ का उपयोग करके फंस गए हैं और इसके नुकसान के साथ रह रहे हैं। मूल समस्या डिवाइस छोड़ने का कोई सरल तरीका है कि वहाँ है "ओपन एंडेड" लेकिन अभी भी गारंटी तो यह और भी कुछ असामान्य कार्यक्रम विफलता की स्थिति में बंद कर दिया जाएगा।

+0

पहले जैसा ही टिप्पणी है: मुझे पहले से पता नहीं है कि डिवाइस के साथ क्या होता है, लेकिन इसे खोला जाएगा और कई अन्य तरीकों, कक्षाओं आदि में उपयोग किया जाएगा। क्या यह मामला मुझे अभी भी '__del__' का उपयोग करना चाहिए? – Alex

+0

मुझे लगता है कि '__del __()' इस खुले अंत व्यवहार के लिए आपका एकमात्र खुला है। ध्यान रखें कि '__del __()' केवल तभी बुलाया जाता है जब कचरा कलेक्टर को आपकी कक्षा से संबंधित संदर्भ चक्र मिल जाए। आप समय-समय पर ['gc.garbage'] (http://docs.python.org/2/library/gc.html#gc.garbage) की जांच करके और संदर्भ चक्रों को तोड़कर अपने कोड में मैन्युअल रूप से इसका पता लगा सकते हैं और हल कर सकते हैं समस्या पैदा कर रहे हैं। – Cartroo

0

मैं काफी यकीन है कि तुम क्या कह रहे हैं नहीं कर रहा हूँ। एक संदर्भ प्रबंधक उदाहरण एक वर्ग के सदस्य हो सकते हैं - आप कर सकते हैं के रूप में कई with खंड में उसका फिर से उपयोग के रूप में आप की तरह और __enter__() और __exit__() तरीकों हर बार बुलाया जाएगा।

तो, एक बार जब आप MyWrapper पर उन विधियों को जोड़ देंगे, तो आप इसे ऊपर दिए गए MyClass में बना सकते हैं।

def my_method(self): 
    with self.mydevice: 
     # Do stuff here 

कि उदाहरण आप निर्माता में बनाया पर __enter__() और __exit__() तरीकों कॉल करेंगे: और फिर आप की तरह कुछ करना चाहते हैं।

हालांकि, with खंड केवल एक समारोह अवधि कर सकते हैं - अगर आप निर्माता में with खंड का उपयोग तो यह निर्माता बाहर निकलने से पहले __exit__() कॉल करेंगे। यदि आप ऐसा करना चाहते हैं, तो __del__() का उपयोग करने का एकमात्र तरीका है, जिसकी आपकी समस्याएं पहले से ही बताई गई हैं। जब आप with का उपयोग करते हैं तो आप डिवाइस को खोल और बंद कर सकते हैं, लेकिन मुझे नहीं पता कि यह आपकी आवश्यकताओं को पूरा करता है या नहीं।

+0

आपका समाधान केवल एक समारोह के भीतर काम करता है! क्या होगा यदि मैं विधि 'my_method()' में कुछ खोलना चाहता हूं, तो किसी अन्य फ़ंक्शन में कुछ और करें? आपका सुझाव काम नहीं करेगा। – Alex

+0

जैसा कि मैंने अपने उत्तर के अंत में बताया है, 'साथ' खंड केवल उस फ़ंक्शन के भीतर ही काम करेगा जिसमें इसका उपयोग किया गया है - यह वही तरीका है जिसे परिभाषित किया गया है। आप कक्षा परिभाषा में 'साथ' का उपयोग कर सकते हैं लेकिन यह केवल तभी लागू होगा जब परिभाषा संसाधित की जा रही हो। यदि आप कक्षा के पूरे जीवनकाल में एक ही प्रभाव चाहते हैं तो आपको '__del __() 'का उपयोग करना होगा, भले ही यह समस्याओं से ग्रस्त हो। बेहतर समाधान शायद प्रत्येक फंक्शन में डिवाइस को खोलने और बंद करने के लिए है। क्षमा करें अगर मैंने यह स्पष्ट नहीं किया है। – Cartroo

14

मैं __enter__ और __exit__ लागू करने वाली कक्षा लिखने के बजाय contextlib.contextmanager क्लास का उपयोग करने का सुझाव देता हूं। यहाँ यह कैसे काम करेगा:

class MyWrapper(object): 
    def __init__(self, device): 
     self.device = device 

    def open(self): 
     self.device.open() 

    def close(self): 
     self.device.close() 

    # I assume your device has a blink command 
    def blink(self): 
     # do something useful with self.device 
     self.device.send_command(CMD_BLINK, 100) 

    # there is no __del__ method, as long as you conscientiously use the wrapper 

import contextlib 

@contextlib.contextmanager 
def open_device(device): 
    wrapper_object = MyWrapper(device) 
    wrapper_object.open() 
    try: 
     yield wrapper_object 
    finally: 
     wrapper_object.close() 
    return 

with open_device(device) as wrapper_object: 
    # do something useful with wrapper_object 
    wrapper_object.blink() 

लाइन के साथ संकेत पर एक एक डेकोरेटर कहा जाता है शुरू होता है कि। यह अगली पंक्ति पर फ़ंक्शन घोषणा को संशोधित करता है।

जब with कथन का सामना किया गया है, open_device() फ़ंक्शन yield कथन तक निष्पादित होगा। yield कथन में मान वैरिएबल में वापस किया गया है जो वैकल्पिक as खंड का लक्ष्य है, इस मामले में, wrapper_object। आप उसके बाद उस सामान्य पायथन ऑब्जेक्ट की तरह उस मान का उपयोग कर सकते हैं। जब ब्लॉक द्वारा ब्लॉक से बाहर निकलता है तो पथ – फेंकने वाले अपवाद – open_device फ़ंक्शन का शेष निकाय निष्पादित करेगा।

मुझे यकीन नहीं है कि (ए) आपकी रैपर क्लास निम्न-स्तरीय एपीआई में कार्यक्षमता जोड़ रही है, या (बी) यदि यह केवल कुछ है जिसमें आप शामिल हैं तो आपके पास एक संदर्भ प्रबंधक हो सकता है। यदि (बी), तो आप शायद इसके साथ पूरी तरह से वितरण कर सकते हैं, क्योंकि contextlib आपके लिए इसका ख्याल रखता है। यहां बताया गया है कि आपका कोड कैसा दिख सकता है:

import contextlib 

@contextlib.contextmanager 
def open_device(device): 
    device.open() 
    try: 
     yield device 
    finally: 
     device.close() 
    return 

with open_device(device) as device: 
    # do something useful with device 
    device.send_command(CMD_BLINK, 100) 

संदर्भ प्रबंधक का 99% संदर्भlib.contextmanager के साथ किया जा सकता है। यह एक बेहद उपयोगी एपीआई क्लास है (और जिस तरह से इसे कार्यान्वित किया गया है वह निम्न स्तर की पायथन प्लंबिंग का एक रचनात्मक उपयोग है, यदि आप ऐसी चीजों की परवाह करते हैं)।

+0

क्या 'def open_device' में वापसी की आवश्यकता है? –

संबंधित मुद्दे