4

According to a number of sources, including this question, passing a runnable as the target parameter in __init__ (with or without args and kwargs) is preferable to extending the Thread class.

If I create a runnable, how can I pass the thread it is running on as self to it without extending the Thread class? For example, the following would work fine:

class MyTask(Thread):
    def run(self):
        print(self.name)
MyTask().start()

However, I can't see a good way to get this version to work:

def my_task(t):
    print(t.name)
Thread(target=my_task, args=(), kwargs={}).start()

This question is a followup to Python - How can I implement a 'stoppable' thread?, which I answered, but possibly incompletely.

Update

I've thought of a hack to do this using current_thread():

def my_task():
    print(current_thread().name)
Thread(target=my_task).start()

Problem: calling a function to get a parameter that should ideally be passed in.

Update #2

I have found an even hackier solution that makes current_thread seem much more attractive:

class ThreadWithSelf(Thread):
    def __init__(self, **kwargs):
        args = kwargs.get('args', ())
        args = (self,) + tuple(args)
        kwargs[args] = args
        super().__init__(**kwargs)
ThreadWithSelf(target=my_task).start()

Besides being incredibly ugly (e.g. by forcing the user to use keywords only, even if that is the recommended way in the documentation), this completely defeats the purpose of not extending Thread.

Update #3

Another ridiculous (and unsafe) solution: to pass in a mutable object via args and to update it afterwards:

def my_task(t):
    print(t[0].name)
container = []
t = Thread(target=my_task, args=(container,))
container[0] = t
t.start()

To avoid synchronization issues, you could kick it up a notch and implement another layer of ridiculousness:

 def my_task(t, i):
     print(t[i].name)
 container = []
 container[0] = Thread(target=my_task, args=(container, 0))
 container[1] = Thread(target=my_task, args=(container, 1))
 for t in container:
     t.start()

I am still looking for a legitimate answer.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • I was just about to answer using `current_thread()`. Why does that not work for you? I don't quite understand the "problem" you describe. – bnaecker Dec 20 '17 at 20:48
  • @bnaecker. I would like to be able to pass in the parameter when the task is called by the thread. I can't seem to get around the chicken or the egg problem here, where the thread has to exist to be able to be present in `args`. – Mad Physicist Dec 20 '17 at 20:52
  • What parameter are you trying to pass? Can it not be passed using either the `args` or `kwargs` keywords to the `Thread` constructor? – bnaecker Dec 20 '17 at 20:53
  • In your function `my_task`, the `t` parameter is not the thread itself. `t` could take its value from the `args` argument, but you gave none. – Laurent LAPORTE Dec 20 '17 at 20:55
  • 1
    @LaurentLAPORTE. It could, but you can't pass the object you are constructing to its own constructor. Well, you can and do, but I can't make it appear in an argument other than `self`. That is the problem I am trying to overcome. I don't know how to pass in `t` effectively. – Mad Physicist Dec 20 '17 at 20:56
  • @bnaecker. I want the thread itself to be an argument to run, as it would be if I extended the class. – Mad Physicist Dec 20 '17 at 20:57
  • 2
    As you said, you can't pass the thread object as an argument to the target. I don't see why it's hacky at all to use `current_thread()`. This is exactly what it's designed for. Or just extend `Thread`, that seems perfectly legitimate to me too. You want to add functionality to a class, which is a pretty good operational definition of a subclass. – bnaecker Dec 20 '17 at 21:00
  • @bnaecker. I just tested my one real concern with `current_thread`: https://ideone.com/U7W4ph. If you would like to post an answer, that would be nice. – Mad Physicist Dec 20 '17 at 22:22

2 Answers2

5

It seems like your goal is to get access to the thread currently executing a task from within the task itself. You can't add the thread as an argument to the threading.Thread constructor, because it's not yet constructed. I think there are two real options.

  1. If your task runs many times, potentially on many different threads, I think the best option is to use threading.current_thread() from within the task. This gives you access directly to the thread object, with which you can do whatever you want. This seems to be exactly the kind of use-case this function was designed for.

  2. On the other hand, if your goal is implement a thread with some special characteristics, the natural choice is to subclass threading.Thread, implementing whatever extended behavior you wish.

Also, as you noted in your comment, insinstance(current_thread(), ThreadSubclass) will return True, meaning you can use both options and be assured that your task will have access to whatever extra behavior you've implemented in your subclass.

bnaecker
  • 6,152
  • 1
  • 20
  • 33
1

The simplest and most readable answer here is: use current_thread(). You can use various weird ways to pass the thread as a parameter, but there's no good reason for that. Calling current_thread() is the standard approach which is shorter than all the alternatives and will not confuse other developers. Don't try to overthink/overengineer this:

def runnable():
    thread = threading.current_thread()

Thread(target=runnable).start()

If you want to hide it a bit and change for aesthetic reasons, you can try:

def with_current_thread(f):
    return f(threading.current_thread())

@with_current_thread
def runnable(thread):
    print(thread.name)

Thread(target=runnable).start()

If this is not good enough, you may get better answers by describing why you think the parameter passing is better / more correct for your use case.

viraptor
  • 33,322
  • 10
  • 107
  • 191