'Pythonic way to close connection-like objects in __del__

I'm working on a connection-like object which implements a context manager. Writing something like this is strongly encouraged:

with MyConnection() as con:
    # do stuff

Of course one can do this as well:

con = MyConnection()
# do stuff
con.close()

But failing to close the connection is rather problematic. So closing in the __del__() seems like a good idea:

def __del__(self):
    self.close()

This looks quite nice, but sometimes leads to errors:

Exception ignored in: [...]
Traceback (most recent call last):
  File "...", line xxx, in __del__()
TypeError: 'NoneType' object is not callable

It appears as if sometimes the close method is already destroyed, when __del__() is called.

So I'm looking for a nice way to encourage python to close the connection properly on destruction. If possible I would like to avoid code duplication in close() and __del__()



Solution 1:[1]

There is no guarantee when __del__ will actually run. Since you are using a with statement, use the __exit__ method instead. __exit__ will be called as soon as the with statement is finished, no matter how the statement completes (normally, with an exception, etc).

Solution 2:[2]

You could try calling close in __del__, and ignore any exceptions:

del __del__(self):
    try:
        self.close()
    except TypeError:
        pass

Solution 3:[3]

It is true you cannot force your users to use good programming techniques, and if they refuse to do so you cannot be responsible for them.

There is no guarantee of when __del__ will be called -- in some Pythons it is immediate, in others it may not happen until interpreter shutdown. So, while not a very good option, using atexit may be viable -- just be sure that the function you register is smart enough to check if the resource has already been closed/destroyed.

Solution 4:[4]

weakref.finalize() lets you perform an action when an object is garbage collected, or the program exits. It's available from Python 3.4 onwards.

When you create your object you can make a call to finalize(), providing it a callback that cleans up the resources your object holds.

The Python docs provide several examples of its use:

>>> import weakref
>>> class Object:
...     pass
...
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")  
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!

And this example of a class representing a temporary directory whose contents are deleted when:

  • its remove() method is called
  • it gets garbage collected
  • program exits

Whichever happens first.

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()
        self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)

    def remove(self):
        self._finalizer()

    @property
    def removed(self):
        return not self._finalizer.alive

Solution 5:[5]

You may define a close() method and call it on is_open condition both in __exit__ and __del__ methods as follows:

class MyContext:
    def __init__(self, *args):
        self.is_open = False
        self.args = args
        self.open(*self.args)
    def __enter__(self):
        return self
    def __del__(self):
        self.close()
    def __exit__(self, *args):
        self.close()
    def open(self, *args):
        if not self.is_open:
            self.is_open = True
            print("opening: ", args)
        return self
    def close(self):
        if self.is_open:
            self.is_open = False
            print("closing: ", self.args)

Here is a usage example WITHOUT a context manager:

def init_way():
    c = MyContext("path", "openparam")

init_way()

Possible output:

opening:  ('path', 'openparam')
closing:  ('path', 'openparam')

And another example: using the same class as a context manager this time:

def context_way():
    with MyContext("path", "openparam") as c:
        print("in with ...")

context_way()

Possible output:

opening:  ('path', 'openparam')
in with ...
closing:  ('path', 'openparam')

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 chepner
Solution 2 Ethan Furman
Solution 3 Ethan Furman
Solution 4 Hal
Solution 5