web-dev-qa-db-fra.com

fonction d'arrière-plan dans Python

J'ai un script Python qui affiche parfois des images à l'utilisateur. Les images peuvent parfois être assez grandes et elles sont réutilisées souvent. Les afficher n'est pas critique, mais affiche le le message qui leur est associé est. J'ai une fonction qui télécharge l'image nécessaire et l'enregistre localement. En ce moment, elle est exécutée en ligne avec le code qui affiche un message à l'utilisateur, mais cela peut parfois prendre plus de 10 secondes pour les non-locaux Existe-t-il un moyen d'appeler cette fonction lorsque cela est nécessaire, mais de l'exécuter en arrière-plan pendant que le code continue de s'exécuter? J'utiliserais simplement une image par défaut jusqu'à ce que la bonne devienne disponible.

68
Dan Hlavenka

Faites quelque chose comme ça:

def function_that_downloads(my_args):
    # do some long download here

puis en ligne, faites quelque chose comme ceci:

import threading
def my_inline_function(some_args):
    # do some stuff
    download_thread = threading.Thread(target=function_that_downloads, args=some_args)
    download_thread.start()
    # continue doing stuff

Vous voudrez peut-être vérifier si le thread est terminé avant de passer à autre chose en appelant download_thread.isAlive()

95
TorelTwiddler

En règle générale, la façon de procéder consiste à utiliser un pool de threads et à mettre en file d'attente les téléchargements, ce qui émettrait un signal, comme un événement, que la tâche a terminé le traitement. Vous pouvez le faire dans le cadre du module de thread Python fournit.

Pour effectuer ces actions, j'utiliserais objets d'événement et module Queue .

Cependant, une démonstration rapide et sale de ce que vous pouvez faire en utilisant un simple threading.Thread l'implémentation peut être vue ci-dessous:

import os
import threading
import time
import urllib2


class ImageDownloader(threading.Thread):

    def __init__(self, function_that_downloads):
        threading.Thread.__init__(self)
        self.runnable = function_that_downloads
        self.daemon = True

    def run(self):
        self.runnable()


def downloads():
    with open('somefile.html', 'w+') as f:
        try:
            f.write(urllib2.urlopen('http://google.com').read())
        except urllib2.HTTPError:
            f.write('sorry no dice')


print 'hi there user'
print 'how are you today?'
thread = ImageDownloader(downloads)
thread.start()
while not os.path.exists('somefile.html'):
    print 'i am executing but the thread has started to download'
    time.sleep(1)

print 'look ma, thread is not alive: ', thread.is_alive()

Il serait probablement logique de ne pas sonder comme je le fais ci-dessus. Dans ce cas, je changerais le code en ceci:

import os
import threading
import time
import urllib2


class ImageDownloader(threading.Thread):

    def __init__(self, function_that_downloads):
        threading.Thread.__init__(self)
        self.runnable = function_that_downloads

    def run(self):
        self.runnable()


def downloads():
    with open('somefile.html', 'w+') as f:
        try:
            f.write(urllib2.urlopen('http://google.com').read())
        except urllib2.HTTPError:
            f.write('sorry no dice')


print 'hi there user'
print 'how are you today?'
thread = ImageDownloader(downloads)
thread.start()
# show message
thread.join()
# display image

Notez qu'aucun drapeau démon n'est défini ici.

6
Mahmoud Abdelkader

Je préfère utiliser gevent pour ce genre de chose:

import gevent
from gevent import monkey; monkey.patch_all()

greenlet = gevent.spawn( function_to_download_image )
display_message()
# ... perhaps interaction with the user here

# this will wait for the operation to complete (optional)
greenlet.join()
# alternatively if the image display is no longer important, this will abort it:
#greenlet.kill()

Tout fonctionne dans un seul thread, mais chaque fois qu'une opération du noyau se bloque, gevent change de contexte lorsqu'il y a d'autres "greenlets" en cours d'exécution. Les inquiétudes concernant le verrouillage, etc. sont beaucoup réduites, car il n'y a qu'une seule chose en cours d'exécution à la fois, mais l'image continuera à se télécharger chaque fois qu'une opération de blocage s'exécute dans le contexte "principal".

Selon la quantité et le type de choses que vous souhaitez faire en arrière-plan, cela peut être meilleur ou pire que les solutions basées sur le threading; certes, il est beaucoup plus évolutif (c'est-à-dire que vous pouvez faire beaucoup plus de choses en arrière-plan), mais cela pourrait ne pas être un problème dans la situation actuelle.

3
shaunc