Comment ajouter des délais à votre code – Real Python

By | octobre 16, 2019

Formation gratuite Python

Avez-vous déjà eu besoin de faire attendre votre programme Python? La plupart du temps, vous voudriez que votre code soit exécuté le plus rapidement possible. Mais il arrive parfois que laisser votre code dormir pendant un certain temps soit réellement dans votre intérêt.

Par exemple, vous pourriez utiliser un fichier Python dormir() appelez pour simuler un retard dans votre programme. Peut-être devez-vous attendre qu'un fichier soit chargé ou téléchargé, ou qu'un graphique soit chargé ou dessiné à l'écran. Vous devrez peut-être même faire une pause entre les appels d'une API Web ou entre les requêtes d'une base de données. Ajout de Python dormir() les appels à votre programme peuvent vous aider dans chacun de ces cas, et bien plus!

Dans ce tutoriel, vous apprendrez à ajouter Python dormir() Appels avec:

  • le sommeil de temps()
  • Décorateurs
  • Les fils
  • Async IO
  • Interfaces utilisateur graphiques

Cet article est destiné aux développeurs de niveau intermédiaire qui souhaitent approfondir leurs connaissances de Python. Si cela vous ressemble, alors commençons!

Ajout d'un Python dormir() Appeler avec le sommeil de temps()

Python a un support intégré pour mettre votre programme en veille. le temps le module a une fonction dormir() que vous pouvez utiliser pour suspendre l’exécution du thread appelant pendant le nombre de secondes spécifié.

Voici un exemple d'utilisation le sommeil de temps():

>>>

>>> importation temps
>>> temps.dormir(3) # Sommeil pendant 3 secondes

Si vous exécutez ce code dans votre console, vous devez attendre un certain temps avant de pouvoir entrer une nouvelle instruction dans le REPL.

Vous pouvez tester la durée du sommeil en utilisant Python. temps module:

$ python3 -m timeit -n 3 "import time; time.sleep (3)"
3 boucles, le meilleur de 3: 3 secondes par boucle

Ici, vous lancez le temps module avec le -n paramètre qui dit temps combien de fois exécuter l'instruction qui suit. Tu peux voir ça temps a exécuté la déclaration 3 fois et que le meilleur temps d'exécution était de 3 secondes, ce qui était ce à quoi on s'attendait.

Le nombre de fois par défaut que temps exécutera votre code est un million. Si vous deviez exécuter le code ci-dessus avec la valeur par défaut -n, puis à 3 secondes par itération, votre terminal resterait suspendu pendant environ 34 jours! le temps Le module dispose de plusieurs autres options de ligne de commande que vous pouvez extraire dans sa documentation.

Créons quelque chose d’un peu plus réaliste. Un administrateur système doit savoir quand l'un de ses sites Web tombe en panne. Vous souhaitez pouvoir vérifier régulièrement le code d’état du site Web, mais vous ne pouvez pas interroger le serveur Web de façon constante, sinon cela affectera les performances. Une façon de faire cette vérification est d’utiliser un fichier Python. dormir() appel système:

importation temps
importation urllib.request
importation urllib.error

def Uptime_bot(url):
    tandis que Vrai:
        essayer:
            Connecticut = urllib.demande.urlopen(url)
        sauf urllib.Erreur.Erreur HTTP comme e:
            # Email admin / log
            impression(F'Erreur HTTP: Code e    pour url')
        sauf urllib.Erreur.URLError comme e:
            # Email admin / log
            impression(F'URLError: Code e    pour url')
        autre:
            # Le site Web est en place
            impression(F'url    est en place')
        temps.dormir(60)

si __Nom__ == '__principale__':
    url = "http://www.google.com/py"
    Uptime_bot(url)

Ici vous créez Uptime_bot (), qui prend une URL comme argument. La fonction tente ensuite d'ouvrir cette URL avec urllib. S'il y a un Erreur HTTP ou URLError, le programme le détecte et imprime l’erreur. (Dans un environnement en direct, vous enregistrez l'erreur et envoyez probablement un courrier électronique au webmaster ou à l'administrateur système.)

Si aucune erreur ne se produit, votre code indique que tout va bien. Indépendamment de ce qui se passe, votre programme dormira pendant 60 secondes. Cela signifie que vous accédez au site Web uniquement une fois par minute. L'URL utilisée dans cet exemple est incorrecte. Par conséquent, les informations suivantes seront transmises à votre console une fois par minute:

HTTPError: 404 pour http://www.google.com/py

Allez-y et mettez à jour le code pour utiliser une bonne URL connue, comme http://www.google.com. Ensuite, vous pouvez le réexécuter pour le voir fonctionner correctement. Vous pouvez également essayer de mettre à jour le code pour envoyer un courrier électronique ou consigner les erreurs. Pour plus d'informations sur la procédure à suivre, consultez Envoi d'emails avec Python et Journalisation en Python.

Ajout d'un Python dormir() Appel avec décorateurs

Il est parfois nécessaire de réessayer une fonction qui a échoué. Un cas d'utilisation courant pour cela est lorsque vous devez réessayer un téléchargement de fichier car le serveur était occupé. En règle générale, vous ne voudrez pas faire de requêtes au serveur. Par conséquent, ajouter un fichier Python dormir() un appel entre chaque demande est souhaitable.

Un autre cas d’utilisation que j’ai personnellement vécu est celui où je dois vérifier l’état d’une interface utilisateur lors d’un test automatisé. L’interface utilisateur peut se charger plus rapidement ou plus lentement que d’habitude, selon l’ordinateur sur lequel le test est exécuté. Cela peut changer ce qui est affiché à l’écran au moment où mon programme vérifie quelque chose.

Dans ce cas, je peux demander au programme de dormir un instant, puis de revérifier les choses une ou deux secondes plus tard. Cela peut faire la différence entre un test réussi et un test échoué.

Vous pouvez utiliser un décorateur ajouter un Python dormir() appel système dans l’un ou l’autre de ces cas. Si vous n’êtes pas familier avec les décorateurs ou si vous souhaitez les améliorer, consultez Primer on Python Decorators. Regardons un exemple:

importation temps
importation urllib.request
importation urllib.error

def dormir(temps libre, recommencez=3):
    def the_real_decorator(une fonction):
        def emballage(*args, **Kwargs):
            retries = 0
            tandis que retries < recommencez:
                essayer:
                    valeur = une fonction(*args, **Kwargs)
                    si valeur est Aucun:
                        revenir
                sauf:
                    impression(F'Dormir pour temps libre    secondes ')
                    temps.dormir(temps libre)
                    retries + = 1
        revenir emballage
    revenir the_real_decorator

dormir() est votre décorateur. Il accepte un temps libre la valeur et le nombre de fois qu'il devrait recommencez, qui par défaut est 3. À l'intérieur dormir() est une autre fonction, the_real_decorator (), qui accepte la fonction décorée.

Enfin, la fonction la plus interne wrapper () accepte les arguments et les arguments de mots clés que vous transmettez à la fonction décorée. C'est là que la magie opère! Vous utilisez un tandis que boucle pour réessayer d'appeler la fonction. S'il y a une exception, alors vous appelez le sommeil de temps(), incrémenter le retries compteur et essayez d’exécuter à nouveau la fonction.

Maintenant réécrire Uptime_bot () utiliser votre nouveau décorateur:

@dormir(3)
def Uptime_bot(url):
    essayer:
        Connecticut = urllib.demande.urlopen(url)
    sauf urllib.Erreur.Erreur HTTP comme e:
        # Email admin / log
        impression(F'Erreur HTTP: Code e    pour url')
        # Relancez l'exception pour le décorateur
        élever urllib.Erreur.Erreur HTTP
    sauf urllib.Erreur.URLError comme e:
        # Email admin / log
        impression(F'URLError: Code e    pour url')
        # Relancez l'exception pour le décorateur
        élever urllib.Erreur.URLError
    autre:
        # Le site Web est en place
        impression(F'url    est en place')

si __Nom__ == '__principale__':
    url = "http://www.google.com/py"
    Uptime_bot(url)

Ici, tu décores Uptime_bot () avec un dormir() de 3 secondes. Vous avez également supprimé l'original tandis que boucle, ainsi que l'ancien appel à dormir (60). Le décorateur s'en occupe maintenant.

Un autre changement que vous avez apporté est d’ajouter un élever à l'intérieur des blocs de traitement des exceptions. C'est pour que le décorateur fonctionne correctement. Vous pouvez écrire au décorateur pour gérer ces erreurs, mais comme ces exceptions ne s'appliquent qu'à urllib, vous feriez mieux de garder le décorateur tel quel. De cette façon, cela fonctionnera avec une plus grande variété de fonctions.

Vous pouvez apporter quelques améliorations à votre décorateur. Si le nombre de tentatives est épuisé et que vous échouez toujours, vous pouvez le demander de relancer la dernière erreur. Le décorateur attendra également 3 secondes après le dernier échec, ce qui pourrait ne pas vous plaire. N'hésitez pas à les essayer comme exercice!

Ajout d'un Python dormir() Appel avec des fils

Il y a aussi des moments où vous pourriez vouloir ajouter un Python dormir() appeler un fil. Vous exécutez peut-être un script de migration sur une base de données contenant des millions d’enregistrements en production. Vous ne voulez pas causer de temps d'arrêt, mais vous ne voulez pas non plus attendre plus longtemps que nécessaire pour terminer la migration. Vous décidez donc d'utiliser des threads.

Pour que les clients ne remarquent aucun type de ralentissement, chaque thread doit s’exécuter pendant une courte période, puis se mettre en veille. Il y a deux façons de faire ça:

  1. Utilisation le sommeil de temps() comme avant.
  2. Utilisation Event.wait () du filetage module.

Commençons par regarder le sommeil de temps().

En utilisant le sommeil de temps()

Le livre de recettes de journalisation Python montre un bel exemple utilisant le sommeil de temps(). Python enregistrement module est thread-safe, donc c’est un peu plus utile que impression() déclarations pour cet exercice. Le code suivant est basé sur cet exemple:

importation enregistrement
importation filetage
importation temps

def ouvrier(se disputer):
    tandis que ne pas se disputer[[[["Arrêtez"]:
        enregistrement.déboguer("enregistrement du thread de travail")
        temps.dormir(1)

def principale():
    enregistrement.basicConfig(
        niveau=enregistrement.DÉBOGUER,
        format="% (relatifCreated) 6d % (threadName) s %(messages"
    )
    Info = "Arrêtez": Faux
    fil = filetage.Fil(cible=ouvrier, args=(Info,))
    thread_two = filetage.Fil(cible=ouvrier, args=(Info,))
    fil.début()
    thread_two.début()

    tandis que Vrai:
        essayer:
            enregistrement.déboguer("Enregistrement depuis le fil principal")
            temps.dormir(0,75)
        sauf ClavierInterrupt:
            Info[[[["Arrêtez"] = Vrai
            enregistrement.déboguer('Arrêt')
            Pause
    fil.joindre()
    thread_two.joindre()

si __Nom__ == "__principale__":
    principale()

Ici, vous utilisez Python filetage module pour créer deux threads. Vous créez également un objet de journalisation qui enregistrera le threadName pour stdout. Ensuite, vous démarrez les deux threads et initiez une boucle pour vous connecter à partir du thread principal de temps en temps. Tu utilises ClavierInterrupt pour attraper l'utilisateur en appuyant sur Ctrl+C.

Essayez d’exécuter le code ci-dessus dans votre terminal. Vous devriez voir une sortie similaire à celle-ci:

    0 Enregistrement d'un thread de travail Thread-1
    1 enregistrement de thread de travail Thread-2
    1 MainThread Enregistrement à partir du fil principal
752 MainThread Enregistrement à partir du fil principal
1001 enregistrement de thread de travail Thread-1
1001 enregistrement de thread de travail Thread-2
1502 MainThread Enregistrement à partir du fil principal
Enregistrement du thread de travail Thread-1 2003
Enregistrement du thread de travail Thread-2 2003
2253 MainThread Enregistrement à partir du fil principal
3005 enregistrement d'un thread de travail Thread-1
3005 MainThread Enregistrement à partir du fil principal
Enregistrement du thread de travail 3005 Thread-2

Chaque thread étant exécuté puis mis en veille, la sortie de journalisation est imprimée sur la console. Maintenant que vous avez essayé un exemple, vous pourrez utiliser ces concepts dans votre propre code.

En utilisant Event.wait ()

le filetage module fournit un Un événement() que vous pouvez utiliser comme le sommeil de temps(). cependant, Un événement() a l'avantage d'être plus réactif. La raison en est que, lorsque l'événement est défini, le programme sortira immédiatement de la boucle. Avec le sommeil de temps(), votre code devra attendre le code Python dormir() appelez pour terminer avant que le fil puisse sortir.

La raison pour laquelle vous souhaitez utiliser attendez() voici parce que attendez() est non bloquant, tandis que le sommeil de temps() est blocage. Cela signifie que lorsque vous utilisez le sommeil de temps(), vous empêcherez le thread principal de continuer à s'exécuter pendant qu'il attend le dormir() appeler pour terminer. attendez() résout ce problème. Vous pouvez en savoir plus sur le fonctionnement de tout cela dans la documentation de threading de Python.

Voici comment vous ajoutez un Python dormir() appeler avec Event.wait ():

importation enregistrement
importation filetage

def ouvrier(un événement):
    tandis que ne pas un événement.isSet():
        enregistrement.déboguer("enregistrement du thread de travail")
        un événement.attendez(1)

def principale():
    enregistrement.basicConfig(
        niveau=enregistrement.DÉBOGUER,
        format="% (relatifCreated) 6d % (threadName) s %(messages"
    )
    un événement = filetage.un événement()

    fil = filetage.Fil(cible=ouvrier, args=(un événement,))
    thread_two = filetage.Fil(cible=ouvrier, args=(un événement,))
    fil.début()
    thread_two.début()

    tandis que ne pas un événement.isSet():
        essayer:
            enregistrement.déboguer("Enregistrement depuis le fil principal")
            un événement.attendez(0,75)
        sauf ClavierInterrupt:
            un événement.ensemble()
            Pause

si __Nom__ == "__principale__":
    principale()

Dans cet exemple, vous créez threading.Event () et le passer à ouvrier(). (Rappelez-vous que dans l'exemple précédent, vous avez passé un dictionnaire.) Ensuite, vous configurez vos boucles pour vérifier si un événement est réglé. Si ce n’est pas le cas, votre code imprime un message et attend un peu avant de vérifier à nouveau. Pour définir l'événement, vous pouvez appuyer sur Ctrl+C. Une fois que l'événement est défini, ouvrier() reviendra et la boucle se cassera, mettant fin au programme.

Examinez le bloc de code ci-dessus. Comment passeriez-vous un temps de sommeil différent à chaque thread de travail? Pouvez-vous comprendre? N'hésitez pas à vous lancer seul dans cet exercice!

Ajout d'un Python dormir() Appel avec Async IO

Des fonctionnalités asynchrones ont été ajoutées à Python dans la version 3.4, et cet ensemble de fonctionnalités a connu une expansion agressive depuis. Programmation asynchrone est un type de programmation parallèle qui vous permet d'exécuter plusieurs tâches à la fois. Quand une tâche se termine, il en informera le thread principal.

asyncio est un module qui vous permet d'ajouter un Python dormir() appel asynchrone. Si vous n’êtes pas familiarisé avec l’implémentation de la programmation asynchrone de Python, consultez Async IO in Python: Programmation parallèle simultanée et procédure simultanée Python.

Voici un exemple tiré de la propre documentation de Python:

importation asyncio

async def principale():
    impression('Bonjour ...')
    attendre asyncio.dormir(1)
    impression('... Monde!')

# Python 3.7+
asyncio.courir(principale())

Dans cet exemple, vous exécutez principale() et le faire dormir pendant une seconde entre deux impression() appels.

Voici un exemple plus convaincant de la partie Coroutines and Tasks du asyncio Documentation:

importation asyncio
importation temps

async def sortie(dormir, texte):
    attendre asyncio.dormir(dormir)
    impression(texte)

async def principale():
    impression(F"Started: time.strftime ('%X') ")
    attendre sortie(1, 'Première')
    attendre sortie(2, 'Seconde')
    attendre sortie(3, 'Troisième')
    impression(F"Terminé: time.strftime ('%X') ")

# Python 3.7+
asyncio.courir(principale())

Dans ce code, vous créez un ouvrier appelé sortie() cela prend dans le nombre de secondes à dormir et le texte pour imprimer. Ensuite, vous utilisez Python attendre mot-clé à attendre pour le sortie() code à exécuter. attendre est nécessaire ici parce que sortie() a été marqué comme un async de sorte que vous ne puissiez pas l’appeler comme une fonction normale.

Lorsque vous exécutez ce code, votre programme sera exécuté attendre 3 fois. Le code attend 1, 2 et 3 secondes, pour un temps total d’attente de 6 secondes. Vous pouvez également réécrire le code pour que les tâches s'exécutent en parallèle:

importation asyncio
importation temps

async def sortie(texte, dormir):
    tandis que dormir > 0:
        attendre asyncio.dormir(1)
        impression(F'texte    compteur: dormir    secondes ')
        dormir - = 1

async def principale():
    tache 1 = asyncio.create_task(sortie('Première', 1))
    tâche_2 = asyncio.create_task(sortie('Seconde', 2))
    tâche_3 = asyncio.create_task(sortie('Troisième', 3))
    impression(F"Started: time.strftime ('%X') ")
    attendre tache 1
    attendre tâche_2
    attendre tâche_3                                 
    impression(F"Terminé: time.strftime ('%X') ")

si __Nom__ == '__principale__':
    asyncio.courir(principale())

Maintenant, vous utilisez le concept de les tâches, que vous pouvez faire avec create_task (). Lorsque vous utilisez des tâches dans asyncio, Python exécutera les tâches de manière asynchrone. Ainsi, lorsque vous exécutez le code ci-dessus, il devrait se terminer en 3 secondes au lieu de 6.

Ajout d'un Python dormir() Appeler avec des interfaces graphiques

Les applications en ligne de commande ne sont pas le seul endroit où vous pourriez avoir besoin d’ajouter Python. dormir() appels. Lorsque vous créez un Interface utilisateur graphique (GUI), vous aurez parfois besoin d’ajouter des délais. Par exemple, vous pouvez créer une application FTP pour télécharger des millions de fichiers, mais vous devez ajouter un fichier. dormir() d’appeler entre les lots pour ne pas écraser le serveur.

Le code de l’interface graphique exécutera tout son traitement et son dessin dans un fil principal appelé boucle d'événement. Si tu utilises le sommeil de temps() Dans le code de l’interface graphique, vous bloquez sa boucle d’événements. Du point de vue de l’utilisateur, l’application peut sembler figée. L’utilisateur ne pourra pas interagir avec votre application pendant son sommeil avec cette méthode. (Sous Windows, vous pourriez même recevoir une alerte indiquant que votre application ne répond plus.)

Heureusement, il existe d’autres méthodes que vous pouvez utiliser en plus de le sommeil de temps(). Dans les sections suivantes, vous apprendrez comment ajouter Python dormir() appels dans Tkinter et wxPython.

Dormir à Tkinter

tkinter fait partie de la bibliothèque standard Python. Il se peut qu’il ne soit pas disponible si vous utilisez une version pré-installée de Python sous Linux ou Mac. Si vous obtenez un ImportError, vous devrez alors regarder comment l’ajouter à votre système. Mais si vous installez vous-même Python, alors tkinter devrait déjà être disponible.

Vous allez commencer par regarder un exemple qui utilise le sommeil de temps(). Exécutez ce code pour voir ce qui se passe lorsque vous ajoutez un Python dormir() appeler le mauvais sens:

importation tkinter
importation temps

classe MyApp:
    def __init__(soi, parent):
        soi.racine = parent
        soi.racine.géométrie("400x400")
        soi.Cadre = tkinter.Cadre(parent)
        soi.Cadre.pack()
        b = tkinter.Bouton(texte="clique moi", commander=soi.différé)
        b.pack()

    def différé(soi):
        temps.dormir(3)

si __Nom__ == "__principale__":
    racine = tkinter.Tk()
    app = MyApp(racine)
    racine.boucle principale()

Une fois le code exécuté, appuyez sur le bouton dans votre interface graphique. Le bouton reste enfoncé pendant trois secondes en attendant dormir() pour finir. Si l’application comportait d’autres boutons, vous ne pourriez pas cliquer dessus. Vous ne pouvez pas non plus fermer l’application en veille, car elle ne peut pas répondre à l’événement de fermeture.

Obtenir tkinter pour dormir correctement, vous devrez utiliser après():

importation tkinter

classe MyApp:
    def __init__(soi, parent):
        soi.racine = parent
        soi.racine.géométrie("400x400")
        soi.Cadre = tkinter.Cadre(parent)
        soi.Cadre.pack()
        soi.racine.après(3000, soi.différé)

    def différé(soi):
        impression('J'ai été retardé')

si __Nom__ == "__principale__":
    racine = tkinter.Tk()
    app = MyApp(racine)
    racine.boucle principale()

Ici, vous créez une application de 400 pixels de large et 400 pixels de large. Il n'y a pas de widgets dessus. Tout ce qu'il va faire, c'est montrer un cadre. Ensuite, vous appelez auto.root.after ()auto-racine est une référence à la Tk () objet. après() prend deux arguments:

  1. Le nombre de millisecondes à dormir
  2. La méthode pour appeler quand le sommeil est fini

Dans ce cas, votre application imprimera une chaîne sur stdout après 3 secondes. Vous pouvez penser à après() comme le tkinter version de le sommeil de temps(), mais cela ajoute également la possibilité d’appeler une fonction une fois le sommeil terminé.

Vous pouvez utiliser cette fonctionnalité pour améliorer l'expérience utilisateur. En ajoutant un Python dormir() appel, vous pouvez donner l’impression que l’application se charge plus rapidement, puis démarrer un processus plus long une fois qu’il est en marche. De cette façon, l'utilisateur n'aura pas à attendre que l'application s'ouvre.

Dormir dans wxPython

Il y a deux différences majeures entre wxPython et Tkinter:

  1. wxPython a beaucoup plus de widgets.
  2. wxPython vise à ressembler et à se sentir natif sur toutes les plateformes.

Le framework wxPython n’est pas inclus dans Python, vous devez donc l’installer vous-même. Si vous n’êtes pas familier avec wxPython, consultez la rubrique Comment construire une application graphique Python avec wxPython.

Dans wxPython, vous pouvez utiliser wx.CallLater () ajouter un Python dormir() appel:

importation wx

classe MyFrame(wx.Cadre):
    def __init__(soi):
        super().__init__(parent=Aucun, Titre='Bonjour le monde')
        wx.Appelle plus tard(4000, soi.différé)
        soi.Spectacle()

    def différé(soi):
        impression('J'ai été retardé')

si __Nom__ == '__principale__':
    app = wx.App()
    Cadre = MyFrame()
    app.Boucle principale()

Ici, vous sous-classe wx.Frame directement et ensuite appeler wx.CallLater (). Cette fonction prend les mêmes paramètres que ceux de Tkinter. après():

  1. Le nombre de millisecondes à dormir
  2. La méthode pour appeler quand le sommeil est fini

Lorsque vous exécutez ce code, vous devriez voir une petite fenêtre vide apparaître sans aucun widget. Après 4 secondes, vous verrez la chaîne 'J'ai été retardé' imprimé sur la sortie standard.

L'un des avantages de l'utilisation wx.CallLater () est-ce que c'est thread-safe. Vous pouvez utiliser cette méthode depuis un thread pour appeler une fonction de l’application wxPython principale.

Conclusion

Avec ce tutoriel, vous avez acquis une nouvelle technique utile à ajouter à votre boîte à outils Python! Vous savez comment retarder vos applications et les empêcher d’utiliser les ressources de votre système. Vous pouvez même utiliser Python dormir() appels pour aider votre code d'interface graphique à redessiner plus efficacement. Cela améliorera considérablement l'expérience utilisateur pour vos clients!

Pour récapituler, vous avez appris à ajouter Python. dormir() appels avec les outils suivants:

  • le sommeil de temps()
  • Décorateurs
  • Les fils
  • asyncio
  • Tkinter
  • wxPython

Maintenant, vous pouvez prendre ce que vous avez appris et commencer à mettre votre code en veille!