程式扎記: [ Python 常見問題 ] How do I correctly clean up a Python object?

標籤

2016年11月29日 星期二

[ Python 常見問題 ] How do I correctly clean up a Python object?

Source From Here
Question
Consider code below:
  1. class MyClass:  
  2.     def __init__(self):  
  3.         self.files = []  
  4.   
  5.     # ...  
  6.   
  7.     def __del__(self):  
  8.         for file in self.files:  
  9.             os.unlink(file)  
__del__(self) above fails with an AttributeError exception. I understand Python doesn't guarantee the existence of "global variables" (member data in this context?) when __del__() is invoked. If that is the case and this is the reason for the exception, how do I make sure the object destructs properly?

How-To
I'd recommend using Python's with statement for managing resources that need to be cleaned up. The problem with using an explicit close() statement is that you have to worry about people forgetting to call it at all or forgetting to place it in a finally block to prevent a resource leak when an exception occurs.

To use the with statement, create a class with the following methods:
  1. def __enter__(self)  
  2. def __exit__(self, exc_type, exc_value, traceback)  
In your example above, you'd use:
  1. import os  
  2.   
  3. class MyClass:  
  4.     def __init__(self):  
  5.         print "Initialize %s" % (self.__class__.__name__)  
  6.         self.files = []  
  7.   
  8.     def __enter__(self):  
  9.         print "Enter %s" % (self.__class__.__name__)  
  10.         return self  
  11.   
  12.     # ...  
  13.   
  14.     def __exit__(self, exc_type, exc_value, traceback):  
  15.         print "Exit %s" % (self.__class__.__name__)  
  16.         for file in self.files:  
  17.             os.unlink(file)  
Then, when someone wanted to use your class, they'd do the following:
  1. print "Outside with"  
  2. with MyClass() as myObj:  
  3.     # Use myObj  
  4.     print "Inside with"  
  5.     pass  
  6. print "Exit with"  
The variable myObj will be an instance of type MyClass (it's the value returned by the __enter__ method). Its __exit__ method will automatically be called, regardless of whether or not an exception occurs. Below is the output:
# python DemoWith.py
Outside with
Initialize MyClass
Enter MyClass
Inside with
Exit MyClass
Exit with

You could even take this approach a step further. In the example above, someone could still instantiate MyClass using its constructor without using the with clause. You don't want that to happen. You can fix this by creating a MyClassResource class that defines the __enter__ and __exit__ methods. Then, the Package class would be defined strictly inside the __enter__ method and returned. That way, the caller never could instantiate the MyClass class without using a with statement:
  1. class MyClassResource:  
  2.     def __enter__(self):  
  3.         class MyAnotherClass:  
  4.             def __init__(self):  
  5.                 pass  
  6.             def cleanup(self):  
  7.                 pass  
  8.         print "Enter %s" % (self.__class__.__name__)  
  9.         self.myObj = MyAnotherClass()  
  10.         return self.myObj  
  11.   
  12.     # ...  
  13.   
  14.     def __exit__(self, exc_type, exc_value, traceback):  
  15.         print "Exit %s" % (self.__class__.__name__)  
  16.         self.myObj.cleanup()  
You'd use this as follows:
  1. # Below will throw Exception NameError  
  2. #myAnoObj = MyAnotherClass()  
  3.   
  4. with MyClassResource() as myObj:  
  5.     # Use myObj which is instance of MyAnotherClass  
  6.     print "myObj is %s" % (myObj.__class__.__name__)  
The output looks like:
Enter MyClassResource
myObj is MyAnotherClass
Exit MyClassResource


沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!