views:

1301

answers:

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

    # ...

    def __del__(self):
        for file in self.files:
            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?

A: 

Just wrap your destructor with a try/except statement and it will not throw an exception if your globals are already disposed of.

Edit

Try this:

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

It will stuff the file list in the del function that is guaranteed to exist at the time of call. The weakref proxy is to prevent Python, or yourself from deleting the self.files variable somehow (if it is deleted, then it will not affect the original file list). If it is not the case that this is being deleted even though there are more references to the variable, then you can remove the proxy encapsulation.

Unknown
The problem is that if the member data is gone it's too late for me. I need that data. See my code above: I need the filenames to know which files to remove. I simplified my code though, there are other data I need to clean up myself (i.e. the interpreter won't know how to clean).
wilhelmtell
+2  A: 

It seems that the idiomatic way to do this is to provide a close() method (or similar), and call it explicitely.

Bastien Léonard
This is the approach I used before, but I ran into other problems with it. With exceptions thrown all over the place by other libraries, I need Python's help in cleaning up the mess in the case of an error. Specifically, I need Python to call the destructor for me, because otherwise the code becomes quickly unmanageable, and I will surely forget an exit-point where a call to .close() should be.
wilhelmtell
+12  A: 

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 or forgetting to call it in a finally block so that resources are leaked when an exception occurs.

To use the with statement, create a class with the following methods:

  def __enter__(self)
  def __exit__(self, type, value, traceback)

In your example above, you'd use

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

    def __enter__(self):
        return self

    # ...

    def __exit__(self, type, value, traceback)
        for file in self.files:
            os.unlink(file)

Then, when someone wanted to use your class, they'd do the following:

with Package() as package_obj:
    # use package_obj

The variable package_obj will be an instance of type Package (it's the value returned by the enter method). It's exit method will automatically be called, regardless of whether or not an exception occurs.

You could even take this approach a step further. In the example above, someone could still instantiate Package using its constructor without using the with clause. You don't want that to happen. You can fix this by creating a PackageResource 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 Package class without using a with statement:

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

    def __exit__(self, type, value, traceback):
        self.package_obj.cleanup()

You'd use this as follows:

with PackageResource() as package_obj:
    # use package_obj
Clint Miller
Technically speaking, one could call PackageResource().__enter__() explicitly and thus create a Package that would never be finalized... but they'd really have to be trying to break the code. Probably not something to worry about.
David Zaslavsky
By the way, if you're using Python 2.5, you'll need to do from __future__ import with_statementto be able to use the with statement.
Clint Miller
A: 

I don't think that it's possible for instance members to be removed before __del__ is called. My guess would be that the reason for your particular AttributeError is somewhere else (maybe you mistakenly remove self.file elsewhere).

However, as the others pointed out, you should avoid using __del__. The main reason for this is that instances with __del__ will not be garbage collected (they will only be freed when their refcount reaches 0). Therefore, if your instances are involved in circular references, they will live in memory for as long as the application run. (I may be mistaken about all this though, I'd have to read the gc docs again, but I'm rather sure it works like this).

Virgil Dupras