web-dev-qa-db-fra.com

comment obtenir la valeur de retour d'un thread en python?

Comment obtenir la valeur 'foo' qui est renvoyée par la cible du thread? 

from threading import Thread

def foo(bar):
    print('hello {}'.format(bar))
    return 'foo'

thread = Thread(target=foo, args=('world!',))
thread.start()
return_value = thread.join()

La "manière évidente de le faire", illustrée ci-dessus, ne fonctionne pas: thread.join() a retourné None.

231
wim

FWIW, le module multiprocessing a une interface Nice pour cela en utilisant la classe Pool. Et si vous souhaitez vous en tenir à des threads plutôt qu'à des processus, vous pouvez simplement utiliser la classe multiprocessing.pool.ThreadPool comme solution de remplacement.

def foo(bar, baz):
  print 'hello {0}'.format(bar)
  return 'foo' + baz

from multiprocessing.pool import ThreadPool
pool = ThreadPool(processes=1)

async_result = pool.apply_async(foo, ('world', 'foo')) # Tuple of args for foo

# do some other stuff in the main process

return_val = async_result.get()  # get the return value from your function.
225
Jake Biesinger

Une façon que j'ai vue consiste à passer un objet mutable, tel qu'une liste ou un dictionnaire, au constructeur du thread, avec un index ou un autre identifiant. Le thread peut ensuite stocker ses résultats dans son emplacement dédié dans cet objet. Par exemple:

def foo(bar, result, index):
    print 'hello {0}'.format(bar)
    result[index] = "foo"

from threading import Thread

threads = [None] * 10
results = [None] * 10

for i in range(len(threads)):
    threads[i] = Thread(target=foo, args=('world!', results, i))
    threads[i].start()

# do some other stuff

for i in range(len(threads)):
    threads[i].join()

print " ".join(results)  # what sound does a metasyntactic locomotive make?

Si vous voulez vraiment que join() renvoie la valeur de retour de la fonction appelée, vous pouvez le faire avec une sous-classe Thread comme celle-ci:

from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)
    return "foo"

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs, Verbose)
        self._return = None
    def run(self):
        if self._Thread__target is not None:
            self._return = self._Thread__target(*self._Thread__args,
                                                **self._Thread__kwargs)
    def join(self):
        Thread.join(self)
        return self._return

twrv = ThreadWithReturnValue(target=foo, args=('world!',))

twrv.start()
print twrv.join()   # prints foo

Cela devient un peu poilu à cause de certains noms malchanceux, et il accède à des structures de données "privées" qui sont spécifiques à la mise en œuvre de Thread ... mais ça fonctionne.

Pour python3

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None
    def run(self):
        print(type(self._target))
        if self._target is not None:
            self._return = self._target(*self._args,
                                                **self._kwargs)
    def join(self, *args):
        Thread.join(self, *args)
        return self._return
163
kindall

La réponse de Jake est bonne, mais si vous ne voulez pas utiliser de pool de threads (vous ne savez pas combien de threads vous aurez besoin, mais créez-les au besoin), alors un bon moyen de transmettre des informations entre les threads est la fonction intégrée. Queue.Queue class, car il offre la sécurité des threads.

J'ai créé le décorateur suivant pour le faire agir de la même manière que le threadpool:

def threaded(f, daemon=False):
    import Queue

    def wrapped_f(q, *args, **kwargs):
        '''this function calls the decorated function and puts the 
        result in a queue'''
        ret = f(*args, **kwargs)
        q.put(ret)

    def wrap(*args, **kwargs):
        '''this is the function returned from the decorator. It fires off
        wrapped_f in a new thread and returns the thread object with
        the result queue attached'''

        q = Queue.Queue()

        t = threading.Thread(target=wrapped_f, args=(q,)+args, kwargs=kwargs)
        t.daemon = daemon
        t.start()
        t.result_queue = q        
        return t

    return wrap

Ensuite, vous l'utilisez comme:

@threaded
def long_task(x):
    import time
    x = x + 5
    time.sleep(5)
    return x

# does not block, returns Thread object
y = long_task(10)
print y

# this blocks, waiting for the result
result = y.result_queue.get()
print result

La fonction décorée crée un nouveau thread à chaque appel et renvoie un objet Thread contenant la file d'attente qui recevra le résultat.

METTRE À JOUR

Cela fait un bon bout de temps que je n'ai pas posté cette réponse, mais elle a toujours des vues, alors j'ai pensé la mettre à jour pour refléter ma façon de procéder dans les nouvelles versions de Python:

Python 3.2 ajouté dans le module concurrent.futures qui fournit une interface de haut niveau pour les tâches parallèles. Il fournit ThreadPoolExecutor et ProcessPoolExecutor, de sorte que vous pouvez utiliser un pool de processus ou de processus avec le même api.

L'un des avantages de cette API est que la soumission d'une tâche à une Executor renvoie un objet Future , qui se termine par la valeur de retour de l'appelable que vous soumettez.

Cela rend inutile d'attacher un objet queue, ce qui simplifie un peu le décorateur:

_DEFAULT_POOL = ThreadPoolExecutor()

def threadpool(f, executor=None):
    @wraps(f)
    def wrap(*args, **kwargs):
        return (executor or _DEFAULT_POOL).submit(f, *args, **kwargs)

    return wrap

Ceci utilisera un exécuteur module} _ par défaut, threadpool, par défaut.

L'utilisation est très similaire à avant:

@threadpool
def long_task(x):
    import time
    x = x + 5
    time.sleep(5)
    return x

# does not block, returns Future object
y = long_task(10)
print y

# this blocks, waiting for the result
result = y.result()
print result

Si vous utilisez Python 3.4+, l’utilisation de cette méthode (et des objets Future en général) permet de transformer le futur retour en un asyncio.Future avec asyncio.wrap_future . Cela facilite le travail avec les coroutines:

result = await asyncio.wrap_future(long_task(10))

Si vous n'avez pas besoin d'accéder à l'objet concurrent.Future sous-jacent, vous pouvez inclure le wrap dans le décorateur:

_DEFAULT_POOL = ThreadPoolExecutor()

def threadpool(f, executor=None):
    @wraps(f)
    def wrap(*args, **kwargs):
        return asyncio.wrap_future((executor or _DEFAULT_POOL).submit(f, *args, **kwargs))

    return wrap

Ensuite, chaque fois que vous avez besoin de pousser un processeur intensif ou de bloquer le code hors du fil de la boucle d’événements, vous pouvez le placer dans une fonction décorée:

@threadpool
def some_long_calculation():
    ...

# this will suspend while the function is executed on a threadpool
result = await some_long_calculation()
64
bj0

Une autre solution qui ne nécessite pas de changer votre code existant:

import Queue
from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)
    return 'foo'

que = Queue.Queue()

t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, 'world!'))
t.start()
t.join()
result = que.get()
print result

Il peut également être facilement ajusté à un environnement multithread:

import Queue
from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)
    return 'foo'

que = Queue.Queue()
threads_list = list()

t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, 'world!'))
t.start()
threads_list.append(t)

# Add more threads here
...
threads_list.append(t2)
...
threads_list.append(t3)
...

# Join all the threads
for t in threads_list:
    t.join()

# Check thread's return value
while not que.empty():
    result = que.get()
    print result
24
Arik

Parris/kindall's answerjoin/return réponse transférée vers Python 3:

from threading import Thread

def foo(bar):
    print('hello {0}'.format(bar))
    return "foo"

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
        Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)

        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self):
        Thread.join(self)
        return self._return


twrv = ThreadWithReturnValue(target=foo, args=('world!',))

twrv.start()
print(twrv.join())   # prints foo

Notez que la classe Thread est implémentée différemment dans Python 3.

15
GuySoft

J'ai volé la réponse de kindall et l'ai nettoyée juste un petit peu.

La partie clé est d'ajouter * args et ** kwargs à join () afin de gérer le délai d'attente

class threadWithReturn(Thread):
    def __init__(self, *args, **kwargs):
        super(threadWithReturn, self).__init__(*args, **kwargs)

        self._return = None

    def run(self):
        if self._Thread__target is not None:
            self._return = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)

    def join(self, *args, **kwargs):
        super(threadWithReturn, self).join(*args, **kwargs)

        return self._return

RÉPONSE MISE À JOUR CI-DESSOUS

C’est ma réponse la plus populaire et la plus votée, j’ai donc décidé de mettre à jour le code qui s’exécutera à la fois sur py2 et py3.

De plus, je vois beaucoup de réponses à cette question qui montrent un manque de compréhension concernant Thread.join (). Certains échouent complètement à gérer la timeout arg. Mais il y a aussi un cas d'école dont vous devez être conscient en ce qui concerne les cas où vous avez (1) une fonction cible pouvant renvoyer None et (2) vous transmettez également l'argument timeout arg à join (). Veuillez voir "TEST 4" pour comprendre ce cas de coin.

Classe ThreadWithReturn qui fonctionne avec py2 et py3:

import sys
from threading import Thread
from builtins import super    # https://stackoverflow.com/a/30159479

if sys.version_info >= (3, 0):
    _thread_target_key = '_target'
    _thread_args_key = '_args'
    _thread_kwargs_key = '_kwargs'
else:
    _thread_target_key = '_Thread__target'
    _thread_args_key = '_Thread__args'
    _thread_kwargs_key = '_Thread__kwargs'

class ThreadWithReturn(Thread):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._return = None

    def run(self):
        target = getattr(self, _thread_target_key)
        if not target is None:
            self._return = target(*getattr(self, _thread_args_key), **getattr(self, _thread_kwargs_key))

    def join(self, *args, **kwargs):
        super().join(*args, **kwargs)
        return self._return

Quelques exemples de tests sont présentés ci-dessous:

import time, random

# TEST TARGET FUNCTION
def giveMe(arg, seconds=None):
    if not seconds is None:
        time.sleep(seconds)
    return arg

# TEST 1
my_thread = ThreadWithReturn(target=giveMe, args=('stringy',))
my_thread.start()
returned = my_thread.join()
# (returned == 'stringy')

# TEST 2
my_thread = ThreadWithReturn(target=giveMe, args=(None,))
my_thread.start()
returned = my_thread.join()
# (returned is None)

# TEST 3
my_thread = ThreadWithReturn(target=giveMe, args=('stringy',), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=2)
# (returned is None) # because join() timed out before giveMe() finished

# TEST 4
my_thread = ThreadWithReturn(target=giveMe, args=(None,), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=random.randint(1, 10))

Pouvez-vous identifier le coin cas que nous pourrions éventuellement rencontrer avec TEST 4?

Le problème est que nous nous attendons à ce que donMe () renvoie Aucun (voir TEST 2), mais nous nous attendons également à ce que join () renvoie Aucun si le délai est dépassé.

returned is None signifie soit:

(1) c'est ce que GiveMe () a retourné, ou

(2) join () expiré

Cet exemple est trivial puisque nous savons que giveMe () retournera toujours None. Mais dans le cas concret (où la cible peut légitimement renvoyer Aucun ou autre chose), nous voudrions vérifier explicitement ce qui s'est passé.

Vous trouverez ci-dessous comment aborder ce coin de cas:

# TEST 4
my_thread = ThreadWithReturn(target=giveMe, args=(None,), kwargs={'seconds': 5})
my_thread.start()
returned = my_thread.join(timeout=random.randint(1, 10))

if my_thread.isAlive():
    # returned is None because join() timed out
    # this also means that giveMe() is still running in the background
    pass
    # handle this based on your app's logic
else:
    # join() is finished, and so is giveMe()
    # BUT we could also be in a race condition, so we need to update returned, just in case
    returned = my_thread.join()
14
user2426679

Utiliser la file d'attente:

import threading, queue

def calc_square(num, out_queue1):
  l = []
  for x in num:
    l.append(x*x)
  out_queue1.put(l)


arr = [1,2,3,4,5,6,7,8,9,10]
out_queue1=queue.Queue()
t1=threading.Thread(target=calc_square, args=(arr,out_queue1))
t1.start()
t1.join()
print (out_queue1.get())
7
user341143

Ma solution au problème consiste à envelopper la fonction et le fil dans une classe. Ne nécessite pas l'utilisation de pools, de files d'attente ou de variables de type c. C'est aussi non bloquant. Vous vérifiez le statut à la place. Voir un exemple d'utilisation en fin de code.

import threading

class ThreadWorker():
    '''
    The basic idea is given a function create an object.
    The object can then run the function in a thread.
    It provides a wrapper to start it,check its status,and get data out the function.
    '''
    def __init__(self,func):
        self.thread = None
        self.data = None
        self.func = self.save_data(func)

    def save_data(self,func):
        '''modify function to save its returned data'''
        def new_func(*args, **kwargs):
            self.data=func(*args, **kwargs)

        return new_func

    def start(self,params):
        self.data = None
        if self.thread is not None:
            if self.thread.isAlive():
                return 'running' #could raise exception here

        #unless thread exists and is alive start or restart it
        self.thread = threading.Thread(target=self.func,args=params)
        self.thread.start()
        return 'started'

    def status(self):
        if self.thread is None:
            return 'not_started'
        else:
            if self.thread.isAlive():
                return 'running'
            else:
                return 'finished'

    def get_results(self):
        if self.thread is None:
            return 'not_started' #could return exception
        else:
            if self.thread.isAlive():
                return 'running'
            else:
                return self.data

def add(x,y):
    return x +y

add_worker = ThreadWorker(add)
print add_worker.start((1,2,))
print add_worker.status()
print add_worker.get_results()
6
Peter Lonjers

Vous pouvez définir un mutable au-dessus de la portée de la fonction thread et y ajouter le résultat. (J'ai aussi modifié le code pour qu'il soit compatible python3)

returns = {}
def foo(bar):
    print('hello {0}'.format(bar))
    returns[bar] = 'foo'

from threading import Thread
t = Thread(target=foo, args=('world!',))
t.start()
t.join()
print(returns)

Ceci retourne {'world!': 'foo'}

Si vous utilisez la fonction entrée comme clé de votre dict de résultats, chaque entrée unique est garantie pour donner une entrée dans les résultats 

2
Thijs D

Compte tenu de@imancommenter@JakeBiesingeranswer je l'ai recomposé pour avoir un nombre de threads différent:

from multiprocessing.pool import ThreadPool

def foo(bar, baz):
    print 'hello {0}'.format(bar)
    return 'foo' + baz

numOfThreads = 3 
results = []

pool = ThreadPool(numOfThreads)

for i in range(0, numOfThreads):
    results.append(pool.apply_async(foo, ('world', 'foo'))) # Tuple of args for foo)

# do some other stuff in the main process
# ...
# ...

results = [r.get() for r in results]
print results

pool.close()
pool.join()

À votre santé,

Gars.

2
Guy Avraham

Vous pouvez utiliser Pool en tant que pool de processus de travail, comme ci-dessous:

from multiprocessing import Pool


def f1(x, y):
    return x*y


if __== '__main__':
    with Pool(processes=10) as pool:
        result = pool.apply(f1, (2, 3))
        print(result)
2
Tung Nguyen

J'utilise cette enveloppe qui convertit aisément n'importe quelle fonction pour exécuter une Thread - en prenant en compte sa valeur de retour ou son exception. Cela n'ajoute pas Queue frais généraux. 

def threading_func(f):
    """Decorator for running a function in a thread and handling its return
    value or exception"""
    def start(*args, **kw):
        def run():
            try:
                th.ret = f(*args, **kw)
            except:
                th.exc = sys.exc_info()
        def get(timeout=None):
            th.join(timeout)
            if th.exc:
                raise th.exc[0], th.exc[1], th.exc[2] # py2
                ##raise th.exc[1] #py3                
            return th.ret
        th = threading.Thread(None, run)
        th.exc = None
        th.get = get
        th.start()
        return th
    return start

Exemples d'utilisation

def f(x):
    return 2.5 * x
th = threading_func(f)(4)
print("still running?:", th.is_alive())
print("result:", th.get(timeout=1.0))

@threading_func
def th_mul(a, b):
    return a * b
th = th_mul("text", 2.5)

try:
    print(th.get())
except TypeError:
    print("exception thrown ok.")

Notes sur le module threading

Une valeur de retour confortable et la gestion des exceptions d'une fonction thread sont un besoin "pythonique" fréquent et devraient en fait déjà être offerts par le module threading - éventuellement directement dans la classe standard Thread. ThreadPool a trop de frais généraux pour des tâches simples - 3 tâches de gestion, beaucoup de bureaucratie. Malheureusement, la disposition de Thread a été copiée à partir de Java - vous le voyez par exemple. du 1 er (!) constructeur toujours utilisable, paramètre group.

1
kxr

Comme mentionné, le pool de multitraitement est beaucoup plus lent que le threading de base. L'utilisation de files d'attente comme proposé dans certaines réponses constitue une alternative très efficace. Je l'ai utilisé avec des dictionnaires afin de pouvoir exécuter beaucoup de petits threads et récupérer plusieurs réponses en les combinant avec des dictionnaires:

#!/usr/bin/env python3

import threading
# use Queue for python2
import queue
import random

LETTERS = 'abcdefghijklmnopqrstuvwxyz'
LETTERS = [ x for x in LETTERS ]

NUMBERS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

def randoms(k, q):
    result = dict()
    result['letter'] = random.choice(LETTERS)
    result['number'] = random.choice(NUMBERS)
    q.put({k: result})

threads = list()
q = queue.Queue()
results = dict()

for name in ('alpha', 'oscar', 'yankee',):
    threads.append( threading.Thread(target=randoms, args=(name, q)) )
    threads[-1].start()
_ = [ t.join() for t in threads ]
while not q.empty():
    results.update(q.get())

print(results)
1
Yves Dorfsman

join retournera toujours None, je pense que vous devriez sous-classe Thread pour gérer les codes de retour, etc.

1
BrainStorm

Une solution habituelle consiste à envelopper votre fonction foo avec un décorateur comme

result = queue.Queue()

def task_wrapper(*args):
    result.put(target(*args))

Alors tout le code peut ressembler à ça

result = queue.Queue()

def task_wrapper(*args):
    result.put(target(*args))

threads = [threading.Thread(target=task_wrapper, args=args) for args in args_list]

for t in threads:
    t.start()
    while(True):
        if(len(threading.enumerate()) < max_num):
            break
for t in threads:
    t.join()
return result

Remarque

Un problème important est que les valeurs de retour peuvent être unorderred . (En fait, le return value n'est pas nécessairement enregistré dans la queue, car vous pouvez choisir arbitraire thread-safe data structure)

0
Response777

Définissez votre cible pour
1) prendre un argument q
2) remplacer toutes les instructions return foo par q.put(foo); return

donc une fonction

def func(a):
    ans = a * a
    return ans

deviendrait

def func(a, q):
    ans = a * a
    q.put(ans)
    return

et alors vous procéderiez comme tel

from Queue import Queue
from threading import Thread

ans_q = Queue()
arg_tups = [(i, ans_q) for i in xrange(10)]

threads = [Thread(target=func, args=arg_tup) for arg_tup in arg_tups]
_ = [t.start() for t in threads]
_ = [t.join() for t in threads]
results = [q.get() for _ in xrange(len(threads))]

Et vous pouvez utiliser les décorateurs/wrappers de fonctions pour le rendre ainsi vous pouvez utiliser vos fonctions existantes en tant que target sans les modifier, mais suivez ce schéma de base.

0
tscizzle

Pourquoi ne pas simplement utiliser la variable globale?

import threading


class myThread(threading.Thread):
    def __init__(self, ind, lock):
        threading.Thread.__init__(self)
        self.ind = ind
        self.lock = lock

    def run(self):
        global results
        with self.lock:
            results.append(self.ind)



results = []
lock = threading.Lock()
threads = [myThread(x, lock) for x in range(1, 4)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(results)
0
Alexey

Si seul True ou False doit être validé à partir de l'appel d'une fonction, une solution plus simple que je trouve est la mise à jour d'une liste globale. 

import threading

lists = {"A":"True", "B":"True"}

def myfunc(name: str, mylist):
    for i in mylist:
        if i == 31:
            lists[name] = "False"
            return False
        else:
            print("name {} : {}".format(name, i))

t1 = threading.Thread(target=myfunc, args=("A", [1, 2, 3, 4, 5, 6], ))
t2 = threading.Thread(target=myfunc, args=("B", [11, 21, 31, 41, 51, 61], ))
t1.start()
t2.start()
t1.join()
t2.join()

for value in lists.values():
    if value == False:
        # Something is suspicious 
        # Take necessary action 

Cela est plus utile lorsque vous voulez savoir si l’un des threads a renvoyé un faux statut pour prendre les mesures nécessaires. 

0
Sravya

L'idée de GuySoft est excellente, mais je pense que l'objet ne doit pas nécessairement hériter de Thread et que start () pourrait être supprimé de l'interface:

from threading import Thread
import queue
class ThreadWithReturnValue(object):
    def __init__(self, target=None, args=(), **kwargs):
        self._que = queue.Queue()
        self._t = Thread(target=lambda q,arg1,kwargs1: q.put(target(*arg1, **kwargs1)) ,
                args=(self._que, args, kwargs), )
        self._t.start()

    def join(self):
        self._t.join()
        return self._que.get()


def foo(bar):
    print('hello {0}'.format(bar))
    return "foo"

twrv = ThreadWithReturnValue(target=foo, args=('world!',))

print(twrv.join())   # prints foo
0
pandy.song

La réponse de Kindall en Python3

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, *, daemon=None):
        Thread.__init__(self, group, target, name, args, kwargs, daemon)
        self._return = None 

    def run(self):
        try:
            if self._target:
                self._return = self._target(*self._args, **self._kwargs)
        finally:
            del self._target, self._args, self._kwargs 

    def join(self,timeout=None):
        Thread.join(self,timeout)
        return self._return
0
SmartManoj

Un moyen très simple de faire cela pour des mannequins comme moi:

import queue
import threading

# creating queue instance
q = queue.Queue()

# creating threading class
class AnyThread():
    def __init__ (self):
        threading.Thread.__init__(self)

    def run(self):
        # in this class and function we will put our test target function
        test()

t = AnyThread()

# having our test target function
def test():
    # do something in this function:
    result = 3 + 2
    # and put result to a queue instance
    q.put(result)

for i in range(3): #calling our threading fucntion 3 times (just for example)
    t.run()
    output = q.get() # here we get output from queue instance
    print(output)

>>> 5
>>> 5
>>> 5

la chose principale ici - est le module queue. Nous créons une instance queue.Queue() et l'incluons dans notre fonction. Nous le nourrissons avec notre résultat que nous dépassons plus tard.

Veuillez voir un autre exemple avec les arguments passés à notre fonction de test:

import queue
import threading

# creating queue instance
q = queue.Queue()

# creating threading class
class AnyThread():
    def __init__ (self):
        threading.Thread.__init__(self)

    def run(self, a, b):
        # in this class and function we will put our execution test function
        test(a, b)

t = AnyThread()

# having our test target function
def test(a, b):
    # do something in this function:
    result = a + b
    # and put result to a queue instance
    q.put(result)

for i in range(3): #calling our threading fucntion 3 times (just for example)
    t.run(3+i, 2+i)
    output = q.get() # here we get output from queue instance
    print(output)

>>> 5
>>> 7
>>> 9
0
Vyachez