8

I'm attempting to wrap a poorly written Python module (that I have no control of) in a class. The issue is that if I don't explicitly call that module's close function then the python process hangs on exit, so I've attempted to wrap the module with a class that has a del method, however the del method does not seem to be called on exceptions.

Example:

class Test(object):
    def __init__(self):
        # Initialize the problematic module here
        print "Initializing"

    def __del__(self):
        # Close the problematic module here
        print "Closing"

t = Test()
# This raises an exception
moo()

In this case del is not called and python hangs. I need somehow to force Python to call del immediately whenever the object goes out of scope (like C++ does). Please note that I have no control over the problematic module (i.e. cannot fix the bug that causes this in the first place) and also no control over whoever uses the wrapper class (can't force them to use "with" so I can't use exit either).

Is there any decent way to solve this?

Thanks!

Dan
  • 261
  • 2
  • 8
  • 3
    Don't use __del__. This is not C++ or a language built for destructors. The __del__ method really should be gone in Python 3.x, though I'm sure someone will find a use case that makes sense. If you need to use __del__, be aware of the basic limitations per http://docs.python.org/reference/datamodel.html – harshil9968 Nov 24 '16 at 12:40
  • 2
    Wrap it with a context manager? – jonrsharpe Nov 24 '16 at 12:40
  • As I said, I have no control over whoever uses the class. I can't force them to use "with". – Dan Nov 24 '16 at 13:01
  • @harshil9968 no reason not to call del. Its behavior is deterministic. You just need to be aware of how it works. But, in the context of this question, it won't work as expected :) – ivarec Feb 14 '19 at 22:37
  • [Python __del__ does not work as destructor?](https://stackoverflow.com/a/41516416) – aschipfl Jan 12 '23 at 16:59

3 Answers3

5

If you want some resource to be released on an exception, think about __enter__ + __exit__ paradigm.

class Test(object):
    def __enter__(self):
        pass

    def __exit__(self):
        pass  # Release your resources here

with Test() as t:
    moo()

When the execution goes into the 'with' block, the method __enter__() of 't' is called, and then it leaves the block due to either normal flow, or an exception, the method __exit__() of 't' is called.

Dmitry T.
  • 673
  • 7
  • 7
  • Please note my last part of the comment - I have no control on whoever uses the wrapper class or how it will be used, i.e. I can't force them to use "with". Any solution must be implemented only inside the wrapper class code. – Dan Nov 24 '16 at 13:00
  • OK, but you have control over the code that uses the class? You can use \_\_enter\_\_ / \_\_exit\_\_ in that client code. I put the Test class just to illustrate the idea. There are also other ways of implementing enter/exit things, e.g. using @contextlib.contextmanger. – Dmitry T. Nov 25 '16 at 08:20
  • Hi, I don't have any control over the code that uses the class. – Dan Nov 27 '16 at 13:51
  • 3
    `__exit__` has longer signature, not only `self` – Azat Ibrakov Aug 09 '18 at 18:19
1

A possible solution is to use sys.excepthook, which allows you to introduce custom login to the global exception handler. You can add some code there to close your module's leftovers.

ivarec
  • 2,542
  • 2
  • 34
  • 57
1

Please find details below :

class Test:
    def __init__(self):
        print('cons')
    
    def __del__(self):
        print('des')

If we add object of class Test in some global dict and clear that dict once your functionality is completed. It will call destructor

ctx = {}
def a():
    ctx['ao'] = Test()
    raise 1/0

a()

# cons
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
#   File "<stdin>", line 3, in a
# ZeroDivisionError: integer division or modulo by zero

If we release old content of ctx and assign new value, destructor is called

ctx  = {}
# des
Sanjay Bhosale
  • 685
  • 2
  • 8
  • 18