简体   繁体   中英

Accessing `self` from thread target

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 (eg 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.

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.

You can use a mutable object (like a list ) as an argument to your thread and populate it after creation, but before running:

def my_task(l):
    t = l[0]
    print(t.name)


mutable_list = []
thread = threading.Thread(target=my_task, args=(mutable_list,), kwargs={})
mutable_list.append(thread)
thread.start()
thread.join()

You get:

Thread-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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM