मैं पायथन ऑब्जेक्ट को सही तरीके से कैसे साफ करूं?


461
class Package:
    def __init__(self):
        self.files = []

    # ...

    def __del__(self):
        for file in self.files:
            os.unlink(file)

__del__(self)ऊपर एक विशेषता अपवाद के साथ विफल रहता है। मैं समझता हूं कि जब आमंत्रित किया जाता है तो पायथन "वैश्विक चर" (इस संदर्भ में सदस्य डेटा?) के अस्तित्व की गारंटी नहीं देता__del__() है। अगर ऐसा है और यह अपवाद का कारण है, तो मैं कैसे सुनिश्चित करूं कि वस्तु ठीक से नष्ट हो जाए?


3
आपके द्वारा लिंक किए गए, पढ़ने वाले वैश्विक वैरिएबल्स यहां लागू नहीं होते हैं, जब तक कि आप प्रोग्राम के बाहर निकलने के बारे में बात नहीं कर रहे हैं, जिसके दौरान मैं आपके द्वारा जोड़े गए लिंक के अनुसार अनुमान लगाता हूं कि ओएस मॉड्यूल पहले से ही चला गया है। अन्यथा, मुझे नहीं लगता कि यह __del __ () विधि में सदस्य चर पर लागू होता है।
केविन एंडरसन

3
मेरे कार्यक्रम से बाहर निकलने से पहले अपवाद को फेंक दिया गया है। एट्रीब्यूट अपवाद मुझे मिलता है पायथन कह रहा है कि यह स्वयं को पहचान नहीं करता है। पैकेज की विशेषता के रूप में। मुझे यह गलत लग रहा है, लेकिन अगर "ग्लोबल्स" से उनका मतलब यह नहीं है कि तरीकों के लिए वैश्विक रूप से वैरिएबल (लेकिन संभवतः स्थानीय से वर्ग) तो मुझे नहीं पता कि यह अपवाद क्या कारण है। Google संकेत देता है कि __del __ (स्व) कहे जाने से पहले सदस्य डेटा को साफ करने का अधिकार रखता है।
विल्हेमटेल

1
पोस्ट के अनुसार कोड मेरे लिए (पायथन 2.5 के साथ) काम करता है। क्या आप वास्तविक कोड को विफल कर रहे हैं - या एक सरलीकृत (बेहतर संस्करण जो अभी भी त्रुटि का कारण बनता है?)
सिल्वरफ़िश

@ विल्हेमटेल क्या आप अधिक ठोस उदाहरण दे सकते हैं? मेरे सभी परीक्षणों में, डेल विध्वंसक पूरी तरह से काम करता है।
अज्ञात

7
यदि कोई जानना चाहता है: इस लेख में बताया गया है कि __del__इसका उपयोग समकक्ष के रूप में क्यों नहीं किया जाना चाहिए __init__। (अर्थात, यह एक "विध्वंसक" नहीं है, इस अर्थ में कि __init__एक निर्माता है।
फ्रैंकलिन

जवाबों:


619

मैं उन withसंसाधनों को प्रबंधित करने के लिए पायथन के बयान का उपयोग करने की सलाह दूंगा जिन्हें साफ करने की आवश्यकता है। एक स्पष्ट close()कथन का उपयोग करने में समस्या यह है कि आपको ऐसे लोगों के बारे में चिंता करना होगा जो इसे कॉल करने के लिए भूल जाते हैं या finallyअपवाद होने पर संसाधन रिसाव को रोकने के लिए इसे ब्लॉक में रखना भूल जाते हैं ।

withकथन का उपयोग करने के लिए , निम्न विधियों के साथ एक वर्ग बनाएं:

  def __enter__(self)
  def __exit__(self, exc_type, exc_value, traceback)

ऊपर आपके उदाहरण में, आप उपयोग करेंगे

class Package:
    def __init__(self):
        self.files = []

    def __enter__(self):
        return self

    # ...

    def __exit__(self, exc_type, exc_value, traceback):
        for file in self.files:
            os.unlink(file)

फिर, जब कोई आपकी कक्षा का उपयोग करना चाहता था, तो वे निम्नलिखित कार्य करेंगे:

with Package() as package_obj:
    # use package_obj

चर package_obj एक प्रकार का पैकेज होगा (यह __enter__विधि द्वारा दिया गया मान है )। इसकी __exit__विधि को स्वचालित रूप से कहा जाएगा, भले ही कोई अपवाद हो या न हो।

आप भी इस दृष्टिकोण को एक कदम आगे ले जा सकते हैं। ऊपर दिए गए उदाहरण में, कोई भी withक्लॉज का उपयोग किए बिना अपने कंस्ट्रक्टर का उपयोग करके पैकेज को तुरंत लिख सकता है । आप ऐसा नहीं चाहते हैं। आप इसे ठीक कर सकते हैं एक PackageResource वर्ग जो विधियों __enter__और __exit__विधियों को परिभाषित करता है । फिर, पैकेज वर्ग को __enter__विधि के अंदर सख्ती से परिभाषित किया जाएगा और वापस आ जाएगा। इस तरह, कॉलर कभी भी withबयान का उपयोग किए बिना पैकेज वर्ग को तुरंत नहीं दे सकता है :

class PackageResource:
    def __enter__(self):
        class Package:
            ...
        self.package_obj = Package()
        return self.package_obj

    def __exit__(self, exc_type, exc_value, traceback):
        self.package_obj.cleanup()

आप इसका उपयोग इस प्रकार करेंगे:

with PackageResource() as package_obj:
    # use package_obj

35
तकनीकी रूप से, कोई भी व्यक्ति पैकेजResource () .__ को __ () दर्ज कर सकता है और स्पष्ट रूप से एक ऐसा पैकेज बना सकता है, जिसे कभी भी अंतिम रूप नहीं दिया जाएगा ... लेकिन उन्हें कोड को तोड़ने का प्रयास करना होगा। शायद कुछ चिंता की बात नहीं है।
डेविड जेड

3
वैसे, यदि आप Python 2.5 का उपयोग कर रहे हैं, तो आपको भविष्य के आयात के साथ with_statement करने की आवश्यकता होगी ताकि कथन का उपयोग कर सकें।
क्लिंट मिलर

2
मुझे एक लेख मिला, जो यह दिखाने में मदद करता है कि __del __ () जिस तरह से काम करता है और एक संदर्भ प्रबंधक समाधान का उपयोग करने का श्रेय देता है: andy-pearce.com/blog/posts/2013/Apr/python-dhructor-drawbacks
eikonomega

2
यदि आप पैरामीटर पास करना चाहते हैं तो उस अच्छे और साफ निर्माण का उपयोग कैसे करें? मैं ऐसा करने में सक्षम होना with Resource(param1, param2) as r: # ...
चाहूंगा

4
@ snooze92 आप संसाधन को एक __init__ विधि दे सकते हैं जो स्वयं में * args और ** kwargs संग्रहीत करता है, और फिर उन्हें प्रवेश विधि में आंतरिक कक्षा में भेजता है। स्टेटमेंट के साथ प्रयोग करते समय, __init__ को __enter__ से पहले बुलाया जाता है
ब्रायन

48

मानक तरीका उपयोग करना है atexit.register:

# package.py
import atexit
import os

class Package:
    def __init__(self):
        self.files = []
        atexit.register(self.cleanup)

    def cleanup(self):
        print("Running cleanup...")
        for file in self.files:
            print("Unlinking file: {}".format(file))
            # os.unlink(file)

लेकिन आपको यह ध्यान रखना चाहिए कि यह Packageपायथन समाप्त होने तक सभी बनाए गए उदाहरणों को जारी रखेगा ।

पैकेज के रूप में सहेजे गए कोड का उपयोग करके डेमो :

$ python
>>> from package import *
>>> p = Package()
>>> q = Package()
>>> q.files = ['a', 'b', 'c']
>>> quit()
Running cleanup...
Unlinking file: a
Unlinking file: b
Unlinking file: c
Running cleanup...

2
Atexit.register दृष्टिकोण के बारे में अच्छी बात यह है कि आपको इस बारे में चिंता करने की ज़रूरत नहीं है कि कक्षा का उपयोगकर्ता क्या करता है (क्या उन्होंने उपयोग withकिया था? क्या उन्होंने स्पष्ट रूप से कॉल किया है __enter__?) नकारात्मक पक्ष यह है कि यदि आपको अजगर से पहले होने के लिए सफाई की आवश्यकता है? बाहर निकलता है, यह काम नहीं करेगा। मेरे मामले में, मुझे परवाह नहीं है कि यह तब होता है जब ऑब्जेक्ट दायरे से बाहर हो जाता है या जब तक कि अजगर बाहर नहीं निकलता है। :)
१।

क्या मैं प्रवेश और निकास का उपयोग कर सकता हूं और जोड़ भी सकता हूं atexit.register(self.__exit__)?
मिरादियो

@myradio मैं नहीं देखता कि यह कैसे उपयोगी होगा? क्या आप सभी क्लीनअप लॉजिक को अंदर नहीं कर सकते हैं __exit__, और एक प्रसंग का उपयोग कर सकते हैं? इसके अलावा, __exit__अतिरिक्त तर्क (यानी __exit__(self, type, value, traceback)) लेता है , इसलिए आपको उन लोगों के लिए आरोप लगाने की आवश्यकता होगी। किसी भी तरह से, ऐसा लगता है कि आपको एसओ पर एक अलग प्रश्न पोस्ट करना चाहिए, क्योंकि आपका उपयोग-मामला असामान्य प्रतीत होता है?
ओस्ट्रोकैच

33

क्लिंट के जवाब में एक परिशिष्ट के रूप में , आप PackageResourceका उपयोग करके सरल कर सकते हैं contextlib.contextmanager:

@contextlib.contextmanager
def packageResource():
    class Package:
        ...
    package = Package()
    yield package
    package.cleanup()

वैकल्पिक रूप से, हालांकि शायद पायथन के रूप में नहीं, आप ओवरराइड कर सकते हैं Package.__new__:

class Package(object):
    def __new__(cls, *args, **kwargs):
        @contextlib.contextmanager
        def packageResource():
            # adapt arguments if superclass takes some!
            package = super(Package, cls).__new__(cls)
            package.__init__(*args, **kwargs)
            yield package
            package.cleanup()

    def __init__(self, *args, **kwargs):
        ...

और बस का उपयोग करें with Package(...) as package

चीजों को कम करने के लिए, अपने सफाई कार्य closeऔर उपयोग को नाम दें contextlib.closing, जिस स्थिति में आप या तो अनधिकृत Packageवर्ग का उपयोग कर सकते हैं या सरलता से with contextlib.closing(Package(...))उसका ओवरराइड कर सकते हैं__new__

class Package(object):
    def __new__(cls, *args, **kwargs):
        package = super(Package, cls).__new__(cls)
        package.__init__(*args, **kwargs)
        return contextlib.closing(package)

और यह कंस्ट्रक्टर विरासत में मिला है, इसलिए आप बस विरासत में ले सकते हैं, जैसे

class SubPackage(Package):
    def close(self):
        pass

1
यह कमाल का है। मैं विशेष रूप से अंतिम उदाहरण पसंद करता हूं। यह दुर्भाग्यपूर्ण है कि हम Package.__new__()विधि की चार-लाइन बॉयलरप्लेट से बच नहीं सकते हैं , हालांकि। या शायद हम कर सकते हैं। हम या तो एक वर्ग सज्जाकार या मेटाक्लस को परिभाषित कर सकते हैं जो हमारे लिए बॉयलरप्लेट का निर्माण कर रहे हैं। पायथोनिक विचार के लिए भोजन।
सेसिल करी

@CecilCurry धन्यवाद, और अच्छी बात है। इनसे विरासत में मिली किसी भी कक्षा Packageको भी ऐसा करना चाहिए (हालाँकि मैंने अभी तक इसका परीक्षण नहीं किया है), इसलिए किसी भी प्रकार के मेटाक्लस की आवश्यकता नहीं होनी चाहिए। हालांकि मैं है अतीत में metaclasses उपयोग करने के लिए कुछ बहुत उत्सुक तरीके ... पाया
टोबियास Kienzler

@CecilCurry दरअसल, कंस्ट्रक्टर को विरासत में मिला है, इसलिए आप अपनी कक्षा के माता-पिता के रूप में Package(या बेहतर Closingरूप से नामित नाम ) का उपयोग कर सकते हैं object। लेकिन मुझसे यह मत पूछिए कि इसके साथ कितनी विरासत
निहित है

17

मुझे नहीं लगता है कि उदाहरण के सदस्यों को __del__बुलाया जाने से पहले हटाया जाना संभव है। मेरा अनुमान यह होगा कि आपके विशेष गुण का कारण कहीं और है (हो सकता है कि आप गलती से स्वयं को हटा दें। अन्यत्र)।

हालांकि, जैसा कि अन्य ने बताया है, आपको उपयोग करने से बचना चाहिए __del__। इसका मुख्य कारण यह है कि उदाहरणों के साथ __del__कचरा एकत्र नहीं किया जाएगा (वे केवल तभी मुक्त किए जाएंगे जब उनकी वापसी 0 तक पहुंच जाएगी)। इसलिए, यदि आपके उदाहरण परिपत्र संदर्भों में शामिल हैं, तो वे तब तक मेमोरी में रहेंगे, जब तक एप्लिकेशन रन करता है। (मैं इस सब के बारे में गलत हो सकता हूं, मुझे फिर से जीसी डॉक्स पढ़ना होगा, लेकिन मुझे यकीन है कि यह इस तरह काम करता है)।


5
साथ ऑब्जेक्ट्स __del__कचरा एकत्र किया जा सकता है, तो साथ अन्य वस्तुओं से उनके संदर्भ गिनती __del__शून्य है और वे नहीं पहुंचा जा सकता है। इसका मतलब है कि यदि आपके पास वस्तुओं के बीच एक संदर्भ चक्र है __del__, तो उनमें से कोई भी एकत्र नहीं किया जाएगा। हालांकि, किसी अन्य मामले को उम्मीद के मुताबिक हल किया जाना चाहिए।
कॉलिन

"पायथन 3.4 के साथ शुरू, __del __ () तरीके अब संदर्भ चक्रों को कचरा एकत्र करने से नहीं रोकते हैं, और मॉड्यूल ग्लोबल्स को अब दुभाषिया बंद के दौरान किसी से भी मजबूर नहीं किया जाता है। इसलिए इस कोड को सीपीथॉन के बिना किसी भी मुद्दे के बिना काम करना चाहिए।" - docs.python.org/3.6/library/…
टॉमस

14

एक बेहतर विकल्प है कि आप कम-से-कम का उपयोग करें__Del __ () विधियों के साथ फ़ाइनलीज़र ऑब्जेक्ट्स और कंपैरिज़िंग फ़ाइनलर्स के उदाहरण देखें ।


1
आज इसका इस्तेमाल किया और यह अन्य समाधानों की तुलना में बेहतर ढंग से काम करता है। मेरे पास मल्टीप्रोसेसिंग-आधारित संचारक वर्ग है जो एक सीरियल पोर्ट खोलता है और फिर मेरे पास stop()पोर्ट्स और join()प्रक्रियाओं को बंद करने का एक तरीका है। हालांकि, यदि प्रोग्राम अनपेक्षित रूप stop()से बाहर निकलता है, तो इसे नहीं कहा जाता है - मैंने इसे अंतिम रूप से हल किया है। लेकिन किसी भी मामले में मैं _finalizer.detach()इसे रोकने के लिए दो बार (मैन्युअल रूप से और बाद में फ़ाइनलीज़र द्वारा) कॉल करने की विधि को रोक देता हूँ।
बोजन पी।

3
IMO, यह वास्तव में सबसे अच्छा जवाब है। यह बाहर निकलने पर सफाई की संभावना के साथ कचरा संग्रह पर सफाई की संभावना को जोड़ती है। चेतावनी यह है कि अजगर 2.7 कमजोर नहीं है। finalize।
'

12

मुझे लगता है कि समस्या हो सकती है __init__ अगर दिखाए गए से अधिक कोड हो?

__del__ जब भी बुलाया जाएगा __init__ उसे ठीक से निष्पादित नहीं किया गया हो या अपवाद न फेंका गया हो।

स्रोत


2
बहुत संभावना लगता है। उपयोग करते समय इस समस्या से बचने का सबसे अच्छा तरीका यह है कि __del__कक्षा-स्तर पर सभी सदस्यों को स्पष्ट रूप से घोषित किया जाए, यह सुनिश्चित करते हुए कि वे हमेशा मौजूद रहते हैं, भले ही वे __init__असफल हों। दिए गए उदाहरण में, files = ()काम करेगा, हालांकि ज्यादातर आप सिर्फ असाइन करेंगे None; या तो मामले में, आपको अभी भी वास्तविक मूल्य असाइन करने की आवश्यकता है __init__
सोरेन लोर्बोर्ग

11

यहाँ एक न्यूनतम काम करने वाला कंकाल है:

class SkeletonFixture:

    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        pass

    def method(self):
        pass


with SkeletonFixture() as fixture:
    fixture.method()

महत्वपूर्ण: स्व वापस लौटें


यदि आप मेरे जैसे हैं, और क्लिंट मिलर के सही उत्तर के return selfभाग की अनदेखी करते हैं , तो आप इस बकवास को देख रहे होंगे:

Traceback (most recent call last):
  File "tests/simplestpossible.py", line 17, in <module>                                                                                                                                                          
    fixture.method()                                                                                                                                                                                              
AttributeError: 'NoneType' object has no attribute 'method'

आशा है कि यह अगले व्यक्ति की मदद करता है।


8

अपने विध्वंसक को केवल एक प्रयास के अलावा / बयान के अलावा लपेटें और यह एक अपवाद नहीं फेंकेगा यदि आपके ग्लोबल्स पहले से ही निपट चुके हैं।

संपादित करें

इसे इस्तेमाल करे:

from weakref import proxy

class MyList(list): pass

class Package:
    def __init__(self):
        self.__del__.im_func.files = MyList([1,2,3,4])
        self.files = proxy(self.__del__.im_func.files)

    def __del__(self):
        print self.__del__.im_func.files

यह फ़ाइल सूची को डेल फ़ंक्शन में रखेगा जो कॉल के समय मौजूद होने की गारंटी है। कमजोर क्रोम प्रॉक्सी पायथन को रोकने के लिए है, या स्वयं को किसी भी तरह से स्वयं चर को हटाने से रोकता है (यदि इसे हटा दिया गया है, तो यह मूल फ़ाइल सूची को प्रभावित नहीं करेगा)। यदि यह मामला नहीं है कि चर के अधिक संदर्भ होने के बावजूद इसे हटाया जा रहा है, तो आप प्रॉक्सी इनकैप्सुलेशन को हटा सकते हैं।


2
समस्या यह है कि यदि सदस्य डेटा चला गया है तो मेरे लिए बहुत देर हो चुकी है। मुझे वह डेटा चाहिए। ऊपर मेरा कोड देखें: मुझे यह पता करने के लिए फाइलनेम की आवश्यकता है कि कौन सी फाइल को निकालना है। मैंने अपना कोड सरल कर दिया, हालांकि, अन्य डेटा हैं जिन्हें मुझे खुद को साफ करने की आवश्यकता है (यानी दुभाषिया को पता नहीं होगा कि कैसे साफ करना है)।
विल्हेमटेल

4

ऐसा लगता है कि ऐसा करने का मुहावरेदार तरीका एक close()विधि (या समान) प्रदान करना है, और इसे विस्फोटक रूप से कॉल करना है।


20
यह वह दृष्टिकोण है जिसका मैंने पहले उपयोग किया था, लेकिन मैं इसके साथ अन्य समस्याओं में भाग गया। अन्य पुस्तकालयों द्वारा सभी जगह फेंके गए अपवादों के साथ, मुझे एक त्रुटि के मामले में गंदगी को साफ करने में पायथन की मदद की आवश्यकता है। विशेष रूप से, मुझे मेरे लिए विध्वंसक को कॉल करने के लिए पायथन की आवश्यकता है, क्योंकि अन्यथा कोड जल्दी से असहनीय हो जाता है, और मैं निश्चित रूप से एक निकास बिंदु भूल जाएगा जहां एक कॉल .close () होना चाहिए।
विल्हेमटेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.