9

I understand that __init__() is called automatically when you create a class like newThread = MyThread(property) and run() is triggered by newthread.start(). What I am looking for is something that is called automatically before a thread terminates, so I don't have to explicitly call self.cleanUp() before each return statement.

class MyThread(Thread): 
  
    def __init__(self, property): 
        Thread.__init__(self)
        self.property = property

    def cleanUp(self):
        # Clean up here
 
    def run(self):
        # Do some stuff
        self.cleanUp() # Current work around
        return
martineau
  • 119,623
  • 25
  • 170
  • 301
  • 1
    Why don't you just use the `try: ... finally: ...` construct? – Leon Apr 14 '17 at 14:30
  • 4
    While `__del__()` is considered the opposite, there can be issues with when it is called. I would look at context managers, for use in the `with` clause. – AChampion Apr 14 '17 at 14:40

4 Answers4

9

One way to do this is by making the Thread subclass also a context manager. This will effectively make __exit__() the special method you want triggered.

The following shows what I'm proposing. Note: I renamed the property argument you were passing the constructor because property is the name of a Python built-in.

from threading import Thread
import time

TEST_THREAD_EXCEPTION = False  # change as desired

class MyThread(Thread):

    def __init__(self, attribute):
        Thread.__init__(self)
        self.attribute = attribute

    def cleanup(self):
        # Clean up here
        print('  cleaning up after thread')

    def run(self):
        if TEST_THREAD_EXCEPTION:
            raise RuntimeError('OOPS!')  # force exception
        print('  other thread now running...')
        time.sleep(2)  # Do something...

    def __enter__(self):
        try:
            self.run()
        except Exception as exc:
            print('Error: {} exception raised by thread'.format(exc))
            raise  # reraise the exception
        return self

    def __exit__(self, *args):
        self.cleanup()

print('main thread begins execution')
with MyThread('hello') as thread:
    print('doing other things in main thread while other thread is running')
print('main thread continuing...')

Output:

main thread begins execution
  other thread now running...
doing other things in main thread while other thread is running
  cleaning up after thread
main thread continuing on...

If you change TEST_THREAD_EXCEPTION to True, cleanup() won't be called since the thread didn't run successfully—although you could change that if you wished, but may also need to ensure that it doesn't get called twice. Here's what the code above does in that case:

main thread begins execution
Error: OOPS! exception raised by thread
Traceback (most recent call last):
  File "opposite_init.py", line 37, in <module>
    with MyThread('hello') as thread:
  File "opposite_init.py", line 27, in __enter__
    self.run()
  File "opposite_init.py", line 21, in run
    raise RuntimeError('OOPS!')  # force exception
RuntimeError: OOPS!
martineau
  • 119,623
  • 25
  • 170
  • 301
  • As far as I can tell, your example doesn't actually run anything in a separate thread -- it just calls MyThread's `run` method from the main thread. Is this intentional? It doesn't seem to be what OP is asking for. – Dolda2000 Apr 14 '17 at 15:20
  • 3
    Furthermore, `__exit__` is only called if `__enter__` actually returns normally. If the `run` method would throw an exception instead, then `__exit__` wouldn't be called. – Dolda2000 Apr 14 '17 at 15:22
  • @Dolda2000: It's intentional in the sense that it's just an example (and all the OP provided in their code)—which I understood to be "something that is called automatically before a thread terminates". – martineau Apr 14 '17 at 15:25
  • @Dolda2000: `__exit__()` _is_ called when an exception occurs and controls how it will be handled depending on its return value—read the documentation. – martineau Apr 14 '17 at 15:28
  • 3
    Yes, but as I said, `__exit__` is only called if `__enter__` has returned normally. – Dolda2000 Apr 14 '17 at 15:29
  • @Dolda2000: OK, I understand what you mean now, however it's unclear whether the clean-up should be done unconditionally it that case or not. Regardless, I've modified my answer to show how that scenario can be handled. – martineau Apr 14 '17 at 21:27
3

As stated in the Python mailing list, __del__ shouldn't be considered the opposite, but you can use the with syntax, which is a context manager

you cannot be sure that an object's destructor (__del__() ) will ever be called. If you want to make sure that a particular object gets processed, one approach is the with- syntax.

Or you can also look into the try...finally clause, in which the finally statement will always get run.

class MyThread(Thread): 

    def __init__(self, property): 
        Thread.__init__(self)
        self.property = property

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print('starting cleanup')
        # Clean up here

    def run(self):
        # Do some stuff
        return

# not now you can call it like this:
with MyThread("spam") as spam:
    print("The thread is running")
    # you can also do stuff here

You can use the try...finally clause like so:

class MyThread(Thread): 

    def __init__(self, property): 
        Thread.__init__(self)
        self.property = property

    def cleanUp(self):
        # Clean up here
        print('starting cleanup')

    def run(self):
        # Do some stuff
        return

try:
    spam = MyThread('spam')
    print('The thread is running')
finally:
    spam.cleanUp()
Taku
  • 31,927
  • 11
  • 74
  • 85
2

If the problem you're trying to solve is that you don't want to add code to each of your run() methods to call your cleanup function, then I'd suggest making a custom subclass of Thread which does that for you. Something like this, perhaps:

class CleanupThread(Thread):
    def cleanup(self):
        # Override this method in your subclasses to do cleanup
        pass

    def run2(self):
        # Override this method in your subclasses instead of run()
        pass

    def run(self):
        # Do *not* override this in your subclasses. Override run2() instead.
        try:
            self.run2()
        finally:
            self.cleanup()

Of course, you're free to rename run2 to something that makes sense for you.

Python does not offer a built-in equivalent of this, if that's what you're looking for.

Dolda2000
  • 25,216
  • 4
  • 51
  • 92
0

Assuming that EntryExit is a class with the __entry__(...) and __exit__(..) and a function(...) declared, the following will not work:

with EntryExit() as e_e:
    # this fails because e_e is None
    print(e_e.function())

but this works:

with (e_e := EntryExit()):
    print(e_e.function())

tested for 3.12.0a6

Harm Salomons
  • 127
  • 2
  • 2