Opposite of __init__ in thread class?
Asked Answered
H

4

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
Harrus answered 14/4, 2017 at 14:25 Comment(2)
Why don't you just use the try: ... finally: ... construct?Doublespace
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.Radtke
E
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!
Embraceor answered 14/4, 2017 at 14:56 Comment(6)
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.Oriana
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.Oriana
@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".Embraceor
@Dolda2000: __exit__() is called when an exception occurs and controls how it will be handled depending on its return value—read the documentation.Embraceor
Yes, but as I said, __exit__ is only called if __enter__ has returned normally.Oriana
@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.Embraceor
R
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()
Rattan answered 14/4, 2017 at 15:11 Comment(1)
You need to define __enter__ as well if you want spam to be assigned a useful value.Froh
O
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.

Oriana answered 14/4, 2017 at 15:35 Comment(0)
O
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

Oconnor answered 30/3, 2023 at 18:18 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.