trouver un expert Python
Alors que de nombreux développeurs reconnaissent Python comme un langage de programmation efficace, les programmes Python purs peuvent s'exécuter plus lentement que leurs homologues dans les langages compilés comme C, Rust et Java. Tout au long de ce didacticiel, vous verrez comment utiliser un Minuterie Python pour surveiller la vitesse d'exécution de vos programmes.
Dans ce didacticiel, vous allez apprendre à utiliser:
time.perf_counter ()
mesurer le temps en Python- Des classes garder l'état
- Gestionnaires de contexte travailler avec un bloc de code
- Décorateurs personnaliser une fonction
Vous acquerrez également des connaissances de base sur le fonctionnement des classes, des gestionnaires de contexte et des décorateurs. Comme vous voyez des exemples de chaque concept, vous serez inspiré d'utiliser un ou plusieurs d'entre eux dans votre code, à la fois pour l'exécution du code de synchronisation et d'autres applications. Chaque méthode a ses avantages et vous apprendrez laquelle utiliser en fonction de la situation. De plus, vous disposerez d'une minuterie Python fonctionnelle que vous pourrez utiliser pour surveiller vos programmes!
Minuteurs Python
Tout d'abord, vous examinerez un exemple de code que vous utiliserez tout au long du didacticiel. Plus tard, vous ajouterez un Minuterie Python à ce code pour surveiller ses performances. Vous verrez également certaines des façons les plus simples de mesurer le temps d'exécution de cet exemple.
Fonctions de minuterie Python
Si vous regardez le construit temps
module en Python, alors vous remarquerez plusieurs fonctions qui peuvent mesurer le temps:
Python 3.7 a introduit plusieurs nouvelles fonctions, comme thread_time ()
, aussi bien que nanoseconde versions de toutes les fonctions ci-dessus, nommées avec un _ns
suffixe. Par exemple, perf_counter_ns ()
est la version nanoseconde de perf_counter ()
. Vous en apprendrez plus sur ces fonctions plus tard. Pour l'instant, notez ce que la documentation a à dire sur perf_counter ()
:
Renvoie la valeur (en fraction de seconde) d'un compteur de performances, c'est-à-dire une horloge avec la résolution disponible la plus élevée pour mesurer une courte durée. (La source)
Tout d'abord, vous utiliserez perf_counter ()
pour créer une minuterie Python. Plus tard, vous allez comparer cela avec d'autres fonctions de minuterie Python et découvrir pourquoi perf_counter ()
est généralement le meilleur choix.
Exemple: télécharger des didacticiels
Pour mieux comparer les différentes façons d'ajouter un minuteur Python à votre code, vous appliquerez différentes fonctions de minuteur Python au même exemple de code tout au long de ce didacticiel. Si vous avez déjà du code que vous souhaitez mesurer, n'hésitez pas à suivre les exemples à la place.
L'exemple que vous verrez dans ce didacticiel est une fonction courte qui utilise le lecteur realpython
package pour télécharger les derniers tutoriels disponibles ici sur Vrai Python. Pour en savoir plus sur Real Python Reader et son fonctionnement, consultez Comment publier un package Python Open-Source sur PyPI. Vous pouvez installer lecteur realpython
sur votre système avec pépin
:
$ python -m pip installe realpython-reader
Ensuite, vous pouvez importer le package en tant que lecteur
.
Vous stockerez l'exemple dans un fichier nommé latest_tutorial.py
. Le code se compose d'une fonction qui télécharge et imprime le dernier didacticiel de Vrai Python:
1 # latest_tutorial.py
2
3 de lecteur importation alimentation
4
5 def principale():
6 "" "Téléchargez et imprimez le dernier didacticiel de Real Python" ""
sept Didacticiel = alimentation.get_article(0)
8 impression(Didacticiel)
9
dix si __Nom__ == "__principale__":
11 principale()
lecteur realpython
gère la plupart du travail acharné:
- Ligne 3 importations
alimentation
delecteur realpython
. Ce module contient des fonctionnalités pour télécharger des didacticiels à partir du Vrai Python alimentation. - Ligne 7 télécharge le dernier tutoriel depuis Vrai Python. Le nombre
0
est un décalage, où0
signifie le didacticiel le plus récent,1
est le didacticiel précédent, etc. - Ligne 8 imprime le didacticiel sur la console.
- Ligne 11 appels
principale()
lorsque vous exécutez le script.
Lorsque vous exécutez cet exemple, votre sortie ressemble généralement à ceci:
$ python latest_tutorial.py
# Fonctions de minuterie Python: trois façons de surveiller votre code
Alors que de nombreux développeurs reconnaissent Python comme un langage de programmation efficace,
les programmes Python purs peuvent s'exécuter plus lentement que leurs homologues compilés
des langages comme C, Rust et Java. Tout au long de ce didacticiel, vous verrez comment
utilisez une minuterie Python pour surveiller la vitesse d'exécution de vos programmes.
[ ... The full text of the tutorial ... ]
Le code peut prendre un peu de temps à s'exécuter en fonction du réseau, vous pouvez donc utiliser un minuteur Python pour surveiller les performances du script.
Votre première minuterie Python
Ajoutons un minuteur Python simple à l'exemple avec time.perf_counter ()
. Encore une fois, c'est un compteur de performance qui est bien adapté pour chronométrer des parties de votre code.
perf_counter ()
mesure le temps en secondes à partir d'un moment dans le temps non spécifié, ce qui signifie que la valeur de retour d'un seul appel à la fonction n'est pas utile. Cependant, lorsque vous regardez la différence entre deux appels à perf_counter ()
, vous pouvez déterminer combien de secondes se sont écoulées entre les deux appels:
>>> importation temps
>>> temps.perf_counter()
32311.48899951
>>> temps.perf_counter() # Quelques secondes plus tard
32315.261320793
Dans cet exemple, vous avez appelé deux fois perf_counter ()
près de 4 secondes d'intervalle. Vous pouvez le confirmer en calculant la différence entre les deux sorties: 32315,26 – 32311,49 = 3,77.
Vous pouvez maintenant ajouter une minuterie Python à l'exemple de code:
1 # latest_tutorial.py
2
3 importation temps
4 de lecteur importation alimentation
5
6 def principale():
sept "" "Imprimer le dernier didacticiel de Real Python" ""
8 tic = temps.perf_counter()
9 Didacticiel = alimentation.get_article(0)
dix toc = temps.perf_counter()
11 impression(F"Téléchargé le tutoriel en toc - tic: 0.4f secondes")
12
13 impression(Didacticiel)
14
15 si __Nom__ == "__principale__":
16 principale()
Notez que vous appelez perf_counter ()
avant et après le téléchargement du didacticiel. Vous imprimez ensuite le temps nécessaire au téléchargement du didacticiel en calculant la différence entre les deux appels.
Remarque: À la ligne 11, le F
avant la chaîne indique qu'il s'agit d'un f-string, qui est un moyen pratique de formater une chaîne de texte. : 0,4f
est un spécificateur de format qui indique le nombre, toc - tic
, doit être imprimé sous la forme d'un nombre décimal avec quatre décimales.
Les chaînes f ne sont disponibles que dans Python 3.6 et versions ultérieures. Pour plus d'informations, consultez f-Strings de Python 3: une syntaxe de mise en forme des chaînes améliorée.
Maintenant, lorsque vous exécutez l'exemple, vous verrez le temps écoulé avant le didacticiel:
$ python latest_tutorial.py
Téléchargé le tutoriel en 0,67 secondes
# Fonctions de minuterie Python: trois façons de surveiller votre code
[ ... The full text of the tutorial ... ]
C'est ça! Vous avez abordé les bases du chronométrage de votre propre code Python. Dans le reste du didacticiel, vous apprendrez comment encapsuler un minuteur Python dans une classe, un gestionnaire de contexte et un décorateur pour le rendre plus cohérent et plus pratique à utiliser.
Une classe de minuterie Python
Revoyez comment vous avez ajouté le minuteur Python à l'exemple ci-dessus. Notez que vous avez besoin d'au moins une variable (tic
) pour stocker l'état du minuteur Python avant de télécharger le didacticiel. Après avoir regardé un peu le code, vous remarquerez peut-être également que les trois lignes en surbrillance sont ajoutées uniquement à des fins de synchronisation! Maintenant, vous allez créer une classe qui fait la même chose que vos appels manuels à perf_counter ()
, mais d'une manière plus lisible et cohérente.
Tout au long de ce didacticiel, vous allez créer et mettre à jour Minuteur
, une classe que vous pouvez utiliser pour chronométrer votre code de différentes manières. Le code final est également disponible sur PyPI sous le nom codetiming
. Vous pouvez l'installer sur votre système comme ceci:
$ python -m pip install codetiming
Vous pouvez trouver plus d'informations sur codetiming
plus loin dans ce didacticiel, dans la section intitulée The Python Timer Code.
Comprendre les classes en Python
Des classes sont les principaux éléments constitutifs de la programmation orientée objet. UNE classe est essentiellement un modèle que vous pouvez utiliser pour créer objets. Bien que Python ne vous oblige pas à programmer de manière orientée objet, les classes sont partout dans le langage. Pour une preuve rapide, examinons les temps
module:
>>> importation temps
>>> type(temps)
>>> temps.__classe__
type()
renvoie le type d'un objet. Ici, vous pouvez voir que les modules sont en fait des objets créés à partir d'un module
classe. L'attribut spécial .__classe__
peut être utilisé pour accéder à la classe qui définit un objet. En fait, presque tout en Python est une classe:
>>> type(3)
>>> type(Aucun)
>>> type(impression)
>>> type(type)
En Python, les classes sont excellentes lorsque vous devez modéliser quelque chose qui doit garder la trace d'un état particulier. En général, une classe est une collection de propriétés (appelée les attributs) et les comportements (appelés les méthodes). Pour plus d'informations sur les classes et la programmation orientée objet, consultez la programmation orientée objet (OOP) en Python 3 ou les documents officiels.
Création d'une classe de minuterie Python
Les cours sont bons pour le suivi Etat. Dans un Minuteur
vous voulez savoir quand un chronomètre démarre et combien de temps s'est écoulé depuis. Pour la première mise en œuvre de Minuteur
, vous allez ajouter un ._Heure de début
attribut, ainsi que .début()
et .Arrêtez()
méthodes. Ajoutez le code suivant à un fichier nommé timer.py
:
1 # timer.py
2
3 importation temps
4
5 classe TimerError(Exception):
6 "" "Une exception personnalisée utilisée pour signaler les erreurs d'utilisation de la classe Timer" ""
sept
8 classe Minuteur:
9 def __init__(soi):
dix soi._Heure de début = Aucun
11
12 def début(soi):
13 "" "Démarrer une nouvelle minuterie" ""
14 si soi._Heure de début est ne pas Aucun:
15 élever TimerError(F"La minuterie est en cours d'exécution. Utilisez .stop () pour l'arrêter")
16
17 soi._Heure de début = temps.perf_counter()
18
19 def Arrêtez(soi):
20 "" "Arrêtez la minuterie et signalez le temps écoulé" ""
21 si soi._Heure de début est Aucun:
22 élever TimerError(F"Le minuteur ne fonctionne pas. Utilisez .start () pour le démarrer")
23
24 temps écoulé = temps.perf_counter() - soi._Heure de début
25 soi._Heure de début = Aucun
26 impression(F"Temps écoulé: elapsed_time: 0.4f secondes ")
Plusieurs choses se produisent ici, alors parcourons le code étape par étape.
À la ligne 5, vous définissez un TimerError
classe. le (Exception)
la notation signifie que TimerError
hérite d'une autre classe appelée Exception
. Python utilise cette classe intégrée pour la gestion des erreurs. Vous n'avez pas besoin d'ajouter d'attributs ou de méthodes à TimerError
. Cependant, avoir une erreur personnalisée vous donnera plus de flexibilité pour gérer les problèmes à l'intérieur Minuteur
. Pour plus d'informations, consultez Exceptions Python: une introduction.
La définition de Minuteur
lui-même commence à la ligne 8. Lorsque vous créez ou instancier un objet d'une classe, votre code appelle la méthode spéciale .__ init __ ()
. Dans cette première version de Minuteur
, vous initialisez uniquement le ._Heure de début
, que vous utiliserez pour suivre l'état de votre minuterie Python. Il a la valeur Aucun
lorsque la minuterie ne fonctionne pas. Une fois le chronomètre en marche, ._Heure de début
garde une trace du démarrage de la minuterie.
Remarque: Le préfixe de soulignement de ._Heure de début
est une convention Python. Cela signale que ._Heure de début
est un attribut interne qui ne doit pas être manipulé par les utilisateurs du Minuteur
classe.
Quand vous appelez .début()
pour démarrer un nouveau minuteur Python, vous devez d'abord vérifier que le minuteur n'est pas déjà en cours d'exécution. Ensuite, vous stockez la valeur actuelle de perf_counter ()
dans ._Heure de début
. D'un autre côté, lorsque vous appelez .Arrêtez()
, vous vérifiez d'abord que le minuteur Python est en cours d'exécution. Si tel est le cas, vous calculez le temps écoulé comme la différence entre la valeur actuelle de perf_counter ()
et celui dans lequel vous avez stocké ._Heure de début
. Enfin, vous réinitialisez ._Heure de début
afin que la minuterie puisse être redémarrée et imprimer le temps écoulé.
Voici comment vous utilisez Minuteur
:
>>> de minuteur importation Minuteur
>>> t = Minuteur()
>>> t.début()
>>> t.Arrêtez() # Quelques secondes plus tard
Temps écoulé: 3,8191 secondes
Comparez cela à l'exemple précédent où vous avez utilisé perf_counter ()
directement. La structure du code est assez similaire, mais maintenant le code est plus clair, et c'est l'un des avantages de l'utilisation des classes. En choisissant soigneusement vos noms de classe, de méthode et d'attribut, vous pouvez rendre votre code très descriptif!
Utilisation de la classe de minuterie Python
Appliquons Minuteur
à latest_tutorial.py
. Vous n'avez qu'à apporter quelques modifications à votre code précédent:
# latest_tutorial.py
de minuteur importation Minuteur
de lecteur importation alimentation
def principale():
"" "Imprimer le dernier didacticiel de Real Python" ""
t = Minuteur()
t.début()
Didacticiel = alimentation.get_article(0)
t.Arrêtez()
impression(Didacticiel)
si __Nom__ == "__principale__":
principale()
Notez que le code est très similaire à ce que vous avez vu précédemment. En plus de rendre le code plus lisible, Minuteur
prend en charge l'impression du temps écoulé sur la console, ce qui rend la consignation du temps passé plus cohérente. Lorsque vous exécutez le code, vous verrez à peu près la même sortie:
$ python latest_tutorial.py
Temps écoulé: 0,64 secondes
# Fonctions de minuterie Python: trois façons de surveiller votre code
[ ... The full text of the tutorial ... ]
Impression du temps écoulé depuis Minuteur
peut être cohérent, mais il semble que cette approche ne soit pas très flexible. Dans la section suivante, vous verrez comment personnaliser votre classe.
Plus de commodité et de flexibilité
Jusqu'à présent, vous avez vu que les classes conviennent lorsque vous souhaitez encapsuler un état et garantir un comportement cohérent dans votre code. Dans cette section, vous ajouterez plus de commodité et de flexibilité à votre minuterie Python:
- Utilisation texte et mise en forme adaptables lors du rapport du temps passé
- Appliquer journalisation flexible, à l'écran, dans un fichier journal ou dans d'autres parties de votre programme
- Créer une minuterie Python qui peut s'accumuler sur plusieurs invocations
- Construire une représentation informative d'un temporisateur Python
Voyons d'abord comment personnaliser le texte utilisé pour signaler le temps passé. Dans le code précédent, le texte f "Temps écoulé: elapsed_time: 0.4f secondes"
est codé en dur dans .Arrêtez()
. Vous pouvez ajouter de la flexibilité aux classes en utilisant variables d'instance. Leurs valeurs sont normalement passées comme arguments à .__ init __ ()
et stocké sous soi
les attributs. Pour plus de commodité, vous pouvez également fournir des valeurs par défaut raisonnables.
Ajouter .texte
comme un Minuteur
variable d'instance, vous allez faire quelque chose comme ceci:
def __init__(soi, texte="Temps écoulé: : 0.4f secondes "):
soi._Heure de début = Aucun
soi.texte = texte
Notez que le texte par défaut, "Temps écoulé: : 0.4f secondes"
, est donné sous forme de chaîne régulière et non sous forme de f-chaîne. Vous ne pouvez pas utiliser de chaîne f ici car ils évaluent immédiatement et lorsque vous instanciez Minuteur
, votre code n'a pas encore calculé le temps écoulé.
Remarque: Si vous souhaitez utiliser une chaîne f pour spécifier .texte
, vous devez ensuite utiliser des accolades doubles pour échapper aux accolades que le temps réel écoulé remplacera.
Un exemple serait f "tâche terminée en : 0.4f secondes"
. Si la valeur de tâche
est "en train de lire"
, alors cette chaîne f serait évaluée comme "Lecture terminée en : 0.4f secondes"
.
Dans .Arrêtez()
, tu utilises .texte
comme modèle et .format()
pour remplir le modèle:
def Arrêtez(soi):
"" "Arrêtez la minuterie et signalez le temps écoulé" ""
si soi._Heure de début est Aucun:
élever TimerError(F"Le minuteur ne fonctionne pas. Utilisez .start () pour le démarrer")
temps écoulé = temps.perf_counter() - soi._Heure de début
soi._Heure de début = Aucun
impression(soi.texte.format(temps écoulé))
Après cette mise à jour vers timer.py
, vous pouvez modifier le texte comme suit:
>>> de minuteur importation Minuteur
>>> t = Minuteur(texte="Tu as attendu : .1f secondes ")
>>> t.début()
>>> t.Arrêtez() # Quelques secondes plus tard
Vous avez attendu 4,1 secondes
Supposez ensuite que vous ne souhaitez pas simplement imprimer un message sur la console. Vous souhaitez peut-être enregistrer vos mesures de temps afin de pouvoir les stocker dans une base de données. Vous pouvez le faire en renvoyant la valeur de temps écoulé
de .Arrêtez()
. Ensuite, le code appelant peut choisir d'ignorer cette valeur de retour ou de l'enregistrer pour un traitement ultérieur.
Vous souhaitez peut-être intégrer Minuteur
dans vos routines de journalisation. Pour prendre en charge la journalisation ou d'autres sorties de Minuteur
vous devez changer l'appel impression()
afin que l'utilisateur puisse fournir sa propre fonction de journalisation. Cela peut être fait de la même manière que vous avez personnalisé le texte précédemment:
def __init__(soi, texte="Temps écoulé: : 0.4f secondes ", enregistreur=impression):
soi._Heure de début = Aucun
soi.texte = texte
soi.enregistreur = enregistreur
def Arrêtez(soi):
"" "Arrêtez la minuterie et signalez le temps écoulé" ""
si soi._Heure de début est Aucun:
élever TimerError(F"Le minuteur ne fonctionne pas. Utilisez .start () pour le démarrer")
temps écoulé = temps.perf_counter() - soi._Heure de début
soi._Heure de début = Aucun
si soi.enregistreur:
soi.enregistreur(soi.texte.format(temps écoulé))
revenir temps écoulé
À la place d'utiliser impression()
directement, vous créez une autre variable d'instance, self.logger
, cela devrait faire référence à une fonction qui prend une chaîne en argument. En plus de impression()
, vous pouvez utiliser des fonctions comme logging.info ()
ou .écrire()
sur les objets fichier. Notez également le si
test, qui vous permet de désactiver complètement l'impression en passant enregistreur = Aucun
.
Voici deux exemples qui montrent la nouvelle fonctionnalité en action:
>>> de minuteur importation Minuteur
>>> importation enregistrement
>>> t = Minuteur(enregistreur=enregistrement.avertissement)
>>> t.début()
>>> t.Arrêtez() # Quelques secondes plus tard
AVERTISSEMENT: root: Temps écoulé: 3,1610 secondes
3.1609658249999484
>>> t = Minuteur(enregistreur=Aucun)
>>> t.début()
>>> valeur = t.Arrêtez() # Quelques secondes plus tard
>>> valeur
4.710851433001153
Lorsque vous exécutez ces exemples dans un shell interactif, Python imprime automatiquement la valeur de retour.
La troisième amélioration que vous ajouterez est la possibilité d'accumuler mesures du temps. Vous voudrez peut-être le faire, par exemple, lorsque vous appelez une fonction lente dans une boucle. Vous allez ajouter un peu plus de fonctionnalités sous la forme de temporisateurs nommés avec un dictionnaire qui garde la trace de chaque temporisateur Python dans votre code.
Supposons que vous étendez latest_tutorial.py
à un latest_tutorials.py
script qui télécharge et imprime les dix derniers didacticiels de Vrai Python. Voici une mise en œuvre possible:
# latest_tutorials.py
de minuteur importation Minuteur
de lecteur importation alimentation
def principale():
"" "Imprimez les 10 derniers didacticiels de Real Python" ""
t = Minuteur(texte="10 tutoriels téléchargés en : 0.2f secondes ")
t.début()
pour tutorial_num dans intervalle(dix):
Didacticiel = alimentation.get_article(tutorial_num)
impression(Didacticiel)
t.Arrêtez()
si __Nom__ == "__principale__":
principale()
Le code boucle sur les nombres de 0 à 9 et les utilise comme arguments de décalage pour feed.get_article ()
. Lorsque vous exécutez le script, vous verrez de nombreuses informations imprimées sur votre console:
$ python latest_tutorials.py
# Fonctions de minuterie Python: trois façons de surveiller votre code
[ ... The full text of ten tutorials ... ]
10 tutoriels téléchargés en 0,67 seconde
Un problème subtil avec ce code est que vous mesurez non seulement le temps nécessaire pour télécharger les didacticiels, mais également le temps que Python passe à imprimer les didacticiels sur votre écran. Cela pourrait ne pas être si important car le temps passé à imprimer devrait être négligeable par rapport au temps passé à télécharger. Pourtant, il serait bon d'avoir un moyen de chronométrer précisément ce que vous recherchez dans ce genre de situations.
Remarque: Le temps passé à télécharger dix didacticiels est à peu près le même que le temps passé à télécharger un didacticiel. Ce n'est pas un bug dans votre code! Au lieu, lecteur
met en cache le Vrai Python nourrir la première fois get_article ()
est appelé et réutilise les informations sur les appels ultérieurs.
Il existe plusieurs façons de contourner ce problème sans modifier l'implémentation actuelle de Minuteur.
Cependant, la prise en charge de ce cas d'utilisation sera très utile et peut être effectuée avec seulement quelques lignes de code.
Tout d'abord, vous allez introduire un dictionnaire appelé .timers
comme un variable de classe sur Minuteur
, ce qui signifie que toutes les instances de Minuteur
le partagera. Vous l'implémentez en le définissant en dehors de toute méthode:
classe Minuteur:
minuteries = dicter()
Les variables de classe sont accessibles soit directement sur la classe, soit via une instance de la classe:
>>> de minuteur importation Minuteur
>>> Minuteur.minuteries
>>> t = Minuteur()
>>> t.minuteries
>>> Minuteur.minuteries est t.minuteries
Vrai
Dans les deux cas, le code renvoie le même dictionnaire de classe vide.
Ensuite, vous ajouterez des noms facultatifs à votre minuterie Python. Vous pouvez utiliser le nom à deux fins différentes:
- En levant le temps écoulé plus tard dans votre code
- Accumuler minuteries du même nom
Pour ajouter des noms à votre minuterie Python, vous devez apporter deux modifications supplémentaires à timer.py
. Premier, Minuteur
devrait accepter le Nom
comme paramètre. Deuxièmement, le temps écoulé devrait être ajouté à .timers
quand une minuterie s'arrête:
classe Minuteur:
minuteries = dicter()
def __init__(
soi,
Nom=Aucun,
texte="Temps écoulé: : 0.4f secondes ",
enregistreur=impression,
):
soi._Heure de début = Aucun
soi.Nom = Nom
soi.texte = texte
soi.enregistreur = enregistreur
# Ajouter de nouveaux temporisateurs nommés au dictionnaire des temporisateurs
si Nom:
soi.minuteries.définir par defaut(Nom, 0)
# Les autres méthodes sont inchangées
def Arrêtez(soi):
"" "Arrêtez la minuterie et signalez le temps écoulé" ""
si soi._Heure de début est Aucun:
élever TimerError(F"Le minuteur ne fonctionne pas. Utilisez .start () pour le démarrer")
temps écoulé = temps.perf_counter() - soi._Heure de début
soi._Heure de début = Aucun
si soi.enregistreur:
soi.enregistreur(soi.texte.format(temps écoulé))
si soi.Nom:
soi.minuteries[[[[soi.Nom] + = temps écoulé
revenir temps écoulé
Notez que vous utilisez .définir par defaut()
lors de l'ajout du nouveau minuteur Python à .timers
. Ceci est une excellente fonctionnalité qui ne définit la valeur que si Nom
n'est pas déjà défini dans le dictionnaire. Si Nom
est déjà utilisé dans .timers
, la valeur reste inchangée. Cela vous permet d'accumuler plusieurs minuteries:
>>> de minuteur importation Minuteur
>>> t = Minuteur("accumuler")
>>> t.début()
>>> t.Arrêtez() # Quelques secondes plus tard
Temps écoulé: 3,7036 secondes
3.703554293999332
>>> t.début()
>>> t.Arrêtez() # Quelques secondes plus tard
Temps écoulé: 2,3449 secondes
2.3448921170001995
>>> Minuteur.minuteries
'accumuler': 6.0484464109995315
Vous pouvez maintenant revoir latest_tutorials.py
et assurez-vous que seul le temps consacré au téléchargement des didacticiels est mesuré:
# latest_tutorials.py
de minuteur importation Minuteur
de lecteur importation alimentation
def principale():
"" "Imprimez les 10 derniers didacticiels de Real Python" ""
t = Minuteur("Télécharger", enregistreur=Aucun)
pour tutorial_num dans intervalle(dix):
t.début()
Didacticiel = alimentation.get_article(tutorial_num)
t.Arrêtez()
impression(Didacticiel)
download_time = Minuteur.minuteries[[[["Télécharger"]
impression(F"10 tutoriels téléchargés en download_time: 0.2f secondes ")
si __Nom__ == "__principale__":
principale()
La réexécution du script donnera une sortie similaire à celle précédente, bien que vous ne chronométrez maintenant que le téléchargement réel des didacticiels:
$ python latest_tutorials.py
# Fonctions de minuterie Python: trois façons de surveiller votre code
[ ... The full text of ten tutorials ... ]
10 tutoriels téléchargés en 0,65 seconde
La dernière amélioration que vous apporterez à Minuteur
est de le rendre plus informatif lorsque vous travaillez avec lui de manière interactive. Essayez ce qui suit:
>>> de minuteur importation Minuteur
>>> t = Minuteur()
>>> t
Cette dernière ligne est la façon par défaut dont Python représente les objets. Bien que vous puissiez en tirer des informations, elles ne sont généralement pas très utiles. Au lieu de cela, ce serait bien de voir des choses comme le nom du Minuteur
, ou comment il rendra compte des horaires.
Dans Python 3.7, des classes de données ont été ajoutées à la bibliothèque standard. Ceux-ci offrent plusieurs commodités à vos classes, y compris une chaîne de représentation plus informative.
Vous convertissez votre minuterie Python en une classe de données à l'aide de la @dataclass
décorateur. Vous en apprendrez plus sur les décorateurs plus loin dans ce didacticiel. Pour l'instant, vous pouvez considérer cela comme une notation qui indique à Python que Minuteur
est une classe de données:
1 de classes de données importation classe de données, champ
2 de dactylographie importation Tout, ClassVar
3
4 @dataclass
5 classe Minuteur:
6 minuteries: ClassVar = dicter()
sept Nom: Tout = Aucun
8 texte: Tout = "Temps écoulé: : 0.4f secondes "
9 enregistreur: Tout = impression
dix _Heure de début: Tout = champ(défaut=Aucun, init=Faux, repr=Faux)
11
12 def __post_init__(soi):
13 "" "Initialisation: ajouter une minuterie au dict des minuteries" ""
14 si soi.Nom:
15 soi.minuteries.définir par defaut(soi.Nom, 0)
16
17 # Le reste du code est inchangé
Ce code remplace votre ancien .__ init __ ()
méthode. Notez comment les classes de données utilisent une syntaxe qui ressemble à la syntaxe des variables de classe que vous avez vue précédemment pour définir toutes les variables. En réalité, .__ init __ ()
est créé automatiquement pour les classes de données, sur la base de variables annotées dans la définition de la classe.
Vous devez annoter vos variables pour utiliser une classe de données. Vous pouvez l'utiliser pour ajouter des indications de type à votre code. Si vous ne souhaitez pas utiliser d'indices de type, vous pouvez annoter toutes les variables avec Tout
, comme vous l'avez fait ci-dessus. Vous verrez bientôt comment ajouter des conseils de type réels à votre classe de données.
Voici quelques notes sur le Minuteur
classe de données:
-
Ligne 4: le
@dataclass
décorateur définitMinuteur
être une classe de données. -
Ligne 6: Le spécial
ClassVar
une annotation est nécessaire pour que les classes de données spécifient que.timers
est une variable de classe. -
Lignes 7 à 9:
.Nom
,.texte
, et.logger
sera défini comme des attributs surMinuteur
, dont les valeurs peuvent être spécifiées lors de la créationMinuteur
instances. Ils ont tous les valeurs par défaut données. -
Ligne 10: Rappeler que
._Heure de début
est un attribut spécial utilisé pour garder une trace de l'état du temporisateur Python, mais qui doit être caché à l'utilisateur. En utilisantdataclasses.field ()
vous dites que._Heure de début
devrait être retiré de.__ init __ ()
et la représentation deMinuteur
. -
Lignes 12 à 15: Vous pouvez utiliser le spécial
.__ post_init __ ()
méthode pour toute initialisation que vous devez faire en dehors de la définition des attributs d'instance. Ici, vous l'utilisez pour ajouter des minuteries nommées à.timers
.
Votre nouveau Minuteur
La classe de données fonctionne exactement comme votre classe régulière précédente, sauf qu'elle a maintenant une belle représentation:
>>> de minuteur importation Minuteur
>>> t = Minuteur()
>>> t
Timer (name = None, text = 'Elapsed time: : 0.4f seconds',
logger =)
>>> t.début()
>>> t.Arrêtez() # Quelques secondes plus tard
Temps écoulé: 6,7197 secondes
6.719705373998295
Maintenant, vous avez une version assez soignée de Minuteur
c'est cohérent, flexible, pratique et instructif! De nombreuses améliorations que vous avez vues dans cette section peuvent également être appliquées à d'autres types de classes dans vos projets.
Avant de terminer cette section, examinons le code source complet de Minuteur
dans sa forme actuelle. Vous remarquerez l'ajout d'indices de type au code pour une documentation supplémentaire:
# timer.py
de classes de données importation classe de données, champ
importation temps
de dactylographie importation Callable, ClassVar, Dict, Optionnel
classe TimerError(Exception):
"" "Une exception personnalisée utilisée pour signaler les erreurs d'utilisation de la classe Timer" ""
@dataclass
classe Minuteur:
minuteries: ClassVar[[[[Dict[[[[str, flotte]] = dicter()
Nom: Optionnel[[[[str] = Aucun
texte: str = "Temps écoulé: : 0.4f secondes "
enregistreur: Optionnel[[[[Callable[[[[[[[[str], Aucun]] = impression
_Heure de début: Optionnel[[[[flotte] = champ(défaut=Aucun, init=Faux, repr=Faux)
def __post_init__(soi) -> Aucun:
"" "Ajouter un minuteur pour dicter les minuteurs après l'initialisation" ""
si soi.Nom est ne pas Aucun:
soi.minuteries.définir par defaut(soi.Nom, 0)
def début(soi) -> Aucun:
"" "Démarrer une nouvelle minuterie" ""
si soi._Heure de début est ne pas Aucun:
élever TimerError(F"La minuterie est en cours d'exécution. Utilisez .stop () pour l'arrêter")
soi._Heure de début = temps.perf_counter()
def Arrêtez(soi) -> flotte:
"" "Arrêtez la minuterie et signalez le temps écoulé" ""
si soi._Heure de début est Aucun:
élever TimerError(F"Le minuteur ne fonctionne pas. Utilisez .start () pour le démarrer")
# Calculer le temps écoulé
temps écoulé = temps.perf_counter() - soi._Heure de début
soi._Heure de début = Aucun
# Rapport du temps écoulé
si soi.enregistreur:
soi.enregistreur(soi.texte.format(temps écoulé))
si soi.Nom:
soi.minuteries[[[[soi.Nom] + = temps écoulé
revenir temps écoulé
L'utilisation d'une classe pour créer un minuteur Python présente plusieurs avantages:
- Lisibilité: Votre code se lira plus naturellement si vous choisissez soigneusement les noms de classe et de méthode.
- Cohérence: Votre code sera plus facile à utiliser si vous encapsulez des propriétés et des comportements dans des attributs et des méthodes.
- La flexibilité: Votre code sera réutilisable si vous utilisez des attributs avec des valeurs par défaut au lieu de valeurs codées en dur.
Cette classe est très flexible et vous pouvez l'utiliser dans presque toutes les situations où vous souhaitez surveiller le temps nécessaire à l'exécution du code. Cependant, dans les sections suivantes, vous apprendrez à utiliser les gestionnaires de contexte et les décorateurs, ce qui sera plus pratique pour chronométrer les blocs de code et les fonctions.
Un gestionnaire de contexte de minuterie Python
Votre Python Minuteur
la classe a parcouru un long chemin! Comparé au premier temporisateur Python que vous avez créé, votre code est devenu assez puissant. Cependant, il reste encore un peu de code standard pour utiliser votre Minuteur
:
- Tout d'abord, instanciez la classe.
- Appel
.début()
avant le bloc de code que vous souhaitez chronométrer. - Appel
.Arrêtez()
après le bloc de code.
Heureusement, Python a une construction unique pour appeler des fonctions avant et après un bloc de code: le gestionnaire de contexte. Dans cette section, vous apprendrez ce que sont les gestionnaires de contexte et comment créer le vôtre. Ensuite, vous verrez comment développer Minuteur
afin qu'il puisse également fonctionner en tant que gestionnaire de contexte. Enfin, vous verrez comment utiliser Minuteur
en tant que gestionnaire de contexte peut simplifier votre code.
Comprendre les gestionnaires de contexte en Python
Les gestionnaires de contexte font partie de Python depuis longtemps. Ils ont été introduits par PEP 343 en 2005 et mis en œuvre pour la première fois en Python 2.5. Vous pouvez reconnaître les gestionnaires de contexte dans le code en utilisant le avec
mot-clé:
avec EXPRESSION comme VARIABLE:
BLOQUER
EXPRESSION
est une expression Python qui renvoie un gestionnaire de contexte. Le gestionnaire de contexte est éventuellement lié au nom VARIABLE
. Finalement, BLOQUER
est un bloc de code Python normal. Le gestionnaire de contexte garantira que votre programme appelle du code avant BLOQUER
et un autre code après BLOQUER
s'exécute. Ce dernier se produira, même si BLOQUER
lève une exception.
L'utilisation la plus courante des gestionnaires de contexte consiste probablement à gérer différentes ressources, comme les fichiers, les verrous et les connexions à la base de données. Le gestionnaire de contexte est ensuite utilisé pour libérer et nettoyer la ressource après l'avoir utilisée. L'exemple suivant révèle la structure fondamentale de timer.py
en imprimant uniquement les lignes contenant deux points. Plus important encore, il montre l'idiome commun pour ouvrir un fichier en Python:
>>> avec ouvert("timer.py") comme fp:
... impression("".joindre(ln pour ln dans fp si ":" dans ln))
...
classe TimerError (Exception):
Minuterie de classe:
minuteries: ClassVar[Dict[str, float]]= dict ()
nom: (optionnel[str] = Aucun
text: str = "Temps écoulé: : 0.4f secondes"
enregistreur: facultatif[Appelable[[Appelable[[Callable[[Callable[[str], Aucun]]= imprimer
_start_time: facultatif[float] = champ (par défaut = Aucun, init = Faux, repr = Faux)
def __post_init __ (self) -> Aucun:
si self.name n'est pas None:
def start (self) -> Aucun:
si self._start_time n'est pas None:
def stop (self) -> float:
si self._start_time est None:
si self.logger:
si self.name:
Notez que fp
, le pointeur de fichier, n'est jamais explicitement fermé car vous avez utilisé ouvert()
en tant que gestionnaire de contexte. Vous pouvez confirmer que fp
s'est fermé automatiquement:
Dans cet exemple, open ("timer.py")
est une expression qui renvoie un gestionnaire de contexte. Ce gestionnaire de contexte est lié au nom fp
. Le gestionnaire de contexte est en vigueur lors de l'exécution de impression()
. Ce bloc de code d'une ligne s'exécute dans le contexte de fp
.
Qu'est-ce que cela signifie fp
est un gestionnaire de contexte? Techniquement, cela signifie que fp
met en œuvre le protocole du gestionnaire de contexte. Il existe de nombreux protocoles différents sous-jacents au langage Python. Vous pouvez considérer un protocole comme un contrat qui indique quelles méthodes spécifiques votre code doit implémenter.
Le protocole du gestionnaire de contexte se compose de deux méthodes:
- Appel
.__entrer__()
lors de la saisie du contexte lié au gestionnaire de contexte. - Appel
.__sortie__()
à la sortie du contexte lié au gestionnaire de contexte.
En d'autres termes, pour créer vous-même un gestionnaire de contexte, vous devez écrire une classe qui implémente .__entrer__()
et .__sortie__()
. Ni plus ni moins. Essayons un Bonjour le monde! exemple de gestionnaire de contexte:
# greeter.py
classe Greeter:
def __init__(soi, Nom):
soi.Nom = Nom
def __entrer__(soi):
impression(F"Bonjour self.name")
revenir soi
def __sortie__(soi, exc_type, exc_value, exc_tb):
impression(F"À plus tard, self.name")
Greeter
est un gestionnaire de contexte car il implémente le protocole du gestionnaire de contexte. Vous pouvez l'utiliser comme ceci:
>>> de saluer importation Greeter
>>> avec Greeter("Pseudo"):
... impression("Faire des trucs ...")
...
Bonjour Nick
Faire des trucs ...
A plus tard, Nick
Tout d'abord, notez comment .__entrer__()
est appelé avant de faire des choses, tandis que .__sortie__()
est appelé après. Dans cet exemple simplifié, vous ne faites pas référence au gestionnaire de contexte. Dans ce cas, vous n'avez pas besoin de donner au gestionnaire de contexte un nom avec comme
.
Ensuite, remarquez comment .__entrer__()
Retour soi
. La valeur de retour de .__entrer__()
est ce qui est lié par comme
. Vous souhaitez généralement revenir soi
de .__entrer__()
lors de la création de gestionnaires de contexte. Vous pouvez utiliser cette valeur de retour comme suit:
>>> de saluer importation Greeter
>>> avec Greeter("Emilie") comme grt:
... impression(F"grt.name fait des trucs ... ")
...
Bonjour Emily
Emily fait des trucs ...
A plus tard, Emily
Finalement, .__sortie__()
prend trois arguments: exc_type
, exc_value
, et exc_tb
. Ils sont utilisés pour la gestion des erreurs dans le gestionnaire de contexte et reflètent les valeurs de retour de sys.exc_info ()
. Si une exception se produit pendant l'exécution du bloc, votre code appelle .__sortie__()
avec le type de l'exception, une instance d'exception et un objet traceback. Souvent, vous pouvez les ignorer dans votre gestionnaire de contexte, auquel cas .__sortie__()
est appelé avant que l'exception ne soit relancée:
>>> de saluer importation Greeter
>>> avec Greeter("Coquin") comme grt:
... impression(F"grt.age n'existe pas")
...
Bonjour Rascal
A plus tard, Rascal
Traceback (dernier appel le plus récent):
Fichier "" , ligne 2, dans
AttributeError: L'objet 'Greeter' n'a pas d'attribut 'age'
Tu peux voir ça "A plus tard, Rascal"
est imprimé, même s'il y a une erreur dans le code.
Vous savez maintenant ce que sont les gestionnaires de contexte et comment créer le vôtre. Si vous voulez plonger plus profondément, consultez contextlib
dans la bibliothèque standard. Il comprend des moyens pratiques pour définir de nouveaux gestionnaires de contexte, ainsi que des gestionnaires de contexte prêts à l'emploi qui peuvent être utilisés pour fermer des objets, supprimer des erreurs ou même ne rien faire! Pour encore plus d'informations, consultez les gestionnaires de contexte Python et la déclaration «with» et le didacticiel qui l'accompagne.
Création d'un gestionnaire de contexte de minuterie Python
Vous avez vu comment les gestionnaires de contexte fonctionnent en général, mais comment peuvent-ils aider avec le code temporel? Si vous pouvez exécuter certaines fonctions avant et après un bloc de code, vous pouvez simplifier le fonctionnement de votre minuterie Python. Jusqu'à présent, vous avez dû appeler .début()
et .Arrêtez()
explicitement lors du chronométrage de votre code, mais un gestionnaire de contexte peut le faire automatiquement.
Encore une fois, pour Minuteur
pour fonctionner en tant que gestionnaire de contexte, il doit respecter le protocole du gestionnaire de contexte. En d'autres termes, il doit mettre en œuvre .__entrer__()
et .__sortie__()
pour démarrer et arrêter le minuteur Python. Toutes les fonctionnalités nécessaires sont déjà disponibles, il n'y a donc pas beaucoup de nouveau code à écrire. Ajoutez simplement les méthodes suivantes à votre Minuteur
classe:
def __entrer__(soi):
"" "Démarrer une nouvelle minuterie en tant que gestionnaire de contexte" ""
soi.début()
revenir self
def __exit__(self, *exc_info):
"""Stop the context manager timer"""
self.Arrêtez()
Minuteur
is now a context manager. The important part of the implementation is that .__enter__()
calls .start()
to start a Python timer when the context is entered, and .__exit__()
uses .stop()
to stop the Python timer when the code leaves the context. Try it out:
>>> de timer import Minuteur
>>> import temps
>>> avec Minuteur():
... temps.sommeil(0.7)
...
Elapsed time: 0.7012 seconds
You should also note two more subtle details:
-
.__enter__()
Retourself
, lesMinuteur
instance, which allows the user to bind theMinuteur
instance to a variable usingas
. Par exemple,with Timer() as t:
will create the variablet
pointing to theMinuteur
object. -
.__exit__()
expects a triple of arguments with information about any exception that occurred during the execution of the context. In your code, these arguments are packed into a tuple calledexc_info
and then ignored, which means thatMinuteur
will not attempt any exception handling.
.__exit__()
doesn’t do any error handling in this case. Still, one of the great features of context managers is that they’re guaranteed to call .__exit__()
, no matter how the context exits. In the following example, you purposely create an error by dividing by zero:
>>> de timer import Minuteur
>>> avec Minuteur():
... pour num dans intervalle(-3, 3):
... impression(f"1 / num = 1 / num:.3f")
...
1 / -3 = -0.333
1 / -2 = -0.500
1 / -1 = -1.000
Elapsed time: 0.0001 seconds
Traceback (most recent call last):
Fichier "" , line 3, in
ZeroDivisionError: division by zero
Notez que Minuteur
prints out the elapsed time, even though the code crashed. It’s possible to inspect and suppress errors in .__exit__()
. See the documentation for more information.
Using the Python Timer Context Manager
Let’s see how to use the Minuteur
context manager to time the download of Real Python tutorials. Recall how you used Minuteur
earlier:
# latest_tutorial.py
de timer import Minuteur
de reader import feed
def principale():
"""Print the latest tutorial from Real Python"""
t = Minuteur()
t.début()
Didacticiel = feed.get_article(0)
t.Arrêtez()
impression(Didacticiel)
si __name__ == "__main__":
principale()
You’re timing the call to feed.get_article()
. You can use the context manager to make the code shorter, simpler, and more readable:
# latest_tutorial.py
de timer import Minuteur
de reader import feed
def principale():
"""Print the latest tutorial from Real Python"""
avec Minuteur():
Didacticiel = feed.get_article(0)
impression(Didacticiel)
si __name__ == "__main__":
principale()
This code does virtually the same as the code above. The main difference is that you don’t define the extraneous variable t
, which keeps your namespace cleaner.
Running the script should give a familiar result:
$ python latest_tutorial.py
Elapsed time: 0.71 seconds
# Python Timer Functions: Three Ways to Monitor Your Code
[ ... The full text of the tutorial ... ]
There are a few advantages to adding context manager capabilities to your Python timer class:
- Low effort: You only need one extra line of code to time the execution of a block of code.
- Readability: Invoking the context manager is readable, and you can more clearly visualize the code block you’re timing.
En utilisant Minuteur
as a context manager is almost as flexible as using .start()
et .stop()
directly, while it has less boilerplate code. In the next section, you’ll see how Minuteur
can be used as a decorator as well. This will make it easier to monitor the runtime of complete functions.
A Python Timer Decorator
Votre Minuteur
class is now very versatile. However, there’s one use case where it could be even more streamlined. Say that you want to track the time spent inside one given function in your codebase. Using a context manager, you have essentially two different options:
-
Utilisation
Minuteur
every time you call the function:avec Minuteur("some_name"): do_something()
If you call
do_something()
in many places, then this will become cumbersome and hard to maintain. -
Wrap the code in your function inside a context manager:
def do_something(): avec Minuteur("some_name"): ...
le
Minuteur
only needs to be added in one place, but this adds a level of indentation to the whole definition ofdo_something()
.
A better solution is to use Minuteur
comme un decorator. Decorators are powerful constructs that you use to modify the behavior of functions and classes. In this section, you’ll learn a little about how decorators work, how Minuteur
can be extended to be a decorator, and how that will simplify timing functions. For a more in-depth explanation of decorators, see Primer on Python Decorators.
Understanding Decorators in Python
UNE decorator is a function that wraps another function to modify its behavior. This technique is possible because functions are first-class objects in Python. In other words, functions can be assigned to variables and used as arguments to other functions, just like any other object. This gives you a lot of flexibility and is the basis for several of Python’s more powerful features.
As a first example, you’ll create a decorator that does nothing:
def turn_off(func):
revenir lambda *args, **kwargs: Aucun
First, note that turn_off()
is just a regular function. What makes this a decorator is that it takes a function as its only argument and returns a function. You can use this to modify other functions like this:
>>> impression("Hello")
Bonjour
>>> impression = turn_off(impression)
>>> impression("Hush")
>>> # Nothing is printed
The line print = turn_off(print)
decorates the print statement with the turn_off()
decorator. Effectively, it replaces print()
avec lambda *args, **kwargs: None
returned by turn_off()
. The lambda statement represents an anonymous function that does nothing except return Aucun
.
For you to define more interesting decorators, you need to know about inner functions. Un inner function is a function defined inside another function. One common use of inner functions is to create function factories:
def create_multiplier(factor):
def multiplier(num):
revenir factor * num
revenir multiplier
multiplier()
is an inner function, defined inside create_multiplier()
. Note that you have access to factor
à l'intérieur multiplier()
, tandis que multiplier()
is not defined outside create_multiplier()
:
>>> multiplier
Traceback (most recent call last):
Fichier "" , line 1, in
NameError: name 'multiplier' is not defined
Instead you use create_multiplier()
to create new multiplier functions, each based on a different factor:
>>> double = create_multiplier(factor=2)
>>> double(3)
6
>>> quadruple = create_multiplier(factor=4)
>>> quadruple(sept)
28
Similarly, you can use inner functions to create decorators. Remember, a decorator is a function that returns a function:
1 def triple(func):
2 def wrapper_triple(*args, **kwargs):
3 impression(f"Tripled func.__name__!r")
4 valeur = func(*args, **kwargs)
5 revenir valeur * 3
6 revenir wrapper_triple
triple()
is a decorator, because it’s a function that expects a function as it’s only argument, func()
, and returns another function, wrapper_triple()
. Note the structure of triple()
itself:
- Line 1 starts the definition of
triple()
and expects a function as an argument. - Lines 2 to 5 define the inner function
wrapper_triple()
. - Line 6 Retour
wrapper_triple()
.
This pattern is prevalent for defining decorators. The interesting parts are those happening inside the inner function:
- Line 2 starts the definition of
wrapper_triple()
. This function will replace whichever functiontriple()
decorates. The parameters are*args
et**kwargs
, which collect whichever positional and keyword arguments you pass to the function. This gives you the flexibility to usetriple()
on any function. - Line 3 prints out the name of the decorated function, and note that
triple()
has been applied to it. - Line 4 calls
func()
, the function that has been decorated bytriple()
. It passes on all arguments passed towrapper_triple()
. - Line 5 triples the return value of
func()
and returns it.
Let’s try it out! knock()
is a function that returns the word Penny
. See what happens if it’s tripled:
>>> def knock():
... revenir "Penny! "
...
>>> knock = triple(knock)
>>> result = knock()
Tripled 'knock'
>>> result
'Penny! Penny! Penny! "
Multiplying a text string by a number is a form of repetition, so Penny
repeats three times. The decoration happens at knock = triple(knock)
.
It feels a bit clunky to keep repeating knock
. Instead, PEP 318 introduced a more convenient syntax for applying decorators. The following definition of knock()
does the same as the one above:
>>> @triple
... def knock():
... revenir "Penny! "
...
>>> result = knock()
Tripled 'knock'
>>> result
'Penny! Penny! Penny! "
le @
symbol is used to apply decorators. In this case, @triple
means that triple()
is applied to the function defined just after it.
One of the few decorators defined in the standard library is @functools.wraps
. This one is quite helpful when defining your own decorators. Since decorators effectively replace one function with another, they create a subtle issue with your functions:
>>> knock
<function triple..wrapper_triple at 0x7fa3bfe5dd90>
@triple
decorates knock()
, which is then replaced by the wrapper_triple()
inner function, as the output above confirms. This will also replace the name, docstring, and other metadata. Often, this will not have much effect, but it can make introspection difficult.
Sometimes, decorated functions must have correct metadata. @functools.wraps
fixes exactly this issue:
import functools
def triple(func):
@functools.wraps(func)
def wrapper_triple(*args, **kwargs):
impression(f"Tripled func.__name__!r")
valeur = func(*args, **kwargs)
revenir valeur * 3
revenir wrapper_triple
With this new definition of @triple
, metadata are preserved:
>>> @triple
... def knock():
... revenir "Penny! "
...
>>> knock
Notez que knock()
now keeps its proper name, even after being decorated. It’s good form to use @functools.wraps
whenever you define a decorator. A blueprint you can use for most of your decorators is the following:
import functools
def decorator(func):
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# Do something before
valeur = func(*args, **kwargs)
# Do something after
revenir valeur
revenir wrapper_decorator
To see more examples of how to define decorators, check out the examples listed in Primer on Python Decorators.
Creating a Python Timer Decorator
In this section, you’ll learn how to extend your Python timer so that you can use it as a decorator as well. However, as a first exercise, let’s create a Python timer decorator from scratch.
Based on the blueprint above, you only need to decide what to do before and after you call the decorated function. This is similar to the considerations about what to do when entering and exiting the context manager. You want to start a Python timer before calling the decorated function, and stop the Python timer after the call finishes. UNE @timer
decorator can be defined as follows:
import functools
import temps
def timer(func):
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
tic = temps.perf_counter()
valeur = func(*args, **kwargs)
toc = temps.perf_counter()
elapsed_time = toc - tic
impression(f"Elapsed time: elapsed_time:0.4f seconds")
revenir valeur
revenir wrapper_timer
Note how much wrapper_timer()
resembles the early pattern you established for timing Python code. You can apply @timer
comme suit:
>>> @timer
... def latest_tutorial():
... Didacticiel = feed.get_article(0)
... impression(Didacticiel)
...
>>> latest_tutorial()
# Python Timer Functions: Three Ways to Monitor Your Code
[ ... The full text of the tutorial ... ]
Elapsed time: 0.5414 seconds
Recall that you can also apply a decorator to a previously defined function:
>>> feed.get_article = timer(feed.get_article)
Puisque @
applies when functions are defined, you need to use the more basic form in these cases. One advantage of using a decorator is that you only need to apply it once, and it’ll time the function every time:
>>> Didacticiel = feed.get_article(0)
Elapsed time: 0.5512 seconds
@timer
does the job. However, in a sense, you’re back to square one, since @timer
does not have any of the flexibility or convenience of Minuteur
. Can you also make your Minuteur
class act like a decorator?
So far, you’ve used decorators as functions applied to other functions, but that’s not entirely correct. Decorators must be callables. There are many callable types in Python. You can make your own objects callable by defining the special .__call__()
method in their class. The following function and class behave similarly:
>>> def carré(num):
... revenir num ** 2
...
>>> carré(4)
16
>>> class Squarer:
... def __call__(self, num):
... revenir num ** 2
...
>>> carré = Squarer()
>>> carré(4)
16
Ici, carré
is an instance that is callable and can square numbers, just like the square()
function in the first example.
This gives you a way of adding decorator capabilities to the existing Minuteur
class:
def __call__(self, func):
"""Support using Timer as a decorator"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
avec self:
revenir func(*args, **kwargs)
revenir wrapper_timer
.__call__()
uses the fact that Minuteur
is already a context manager to take advantage of the conveniences you’ve already defined there. Make sure you also import functools
at the top of timer.py
.
You can now use Minuteur
as a decorator:
>>> @Timer(text="Downloaded the tutorial in :.2f seconds")
... def latest_tutorial():
... Didacticiel = feed.get_article(0)
... impression(Didacticiel)
...
>>> latest_tutorial()
# Python Timer Functions: Three Ways to Monitor Your Code
[ ... The full text of the tutorial ... ]
Downloaded the tutorial in 0.72 seconds
Before rounding out this section, know that there’s a more straightforward way of turning your Python timer into a decorator. You’ve already seen some of the similarities between context managers and decorators. They’re both typically used to do something before and after executing some given code.
Based on these similarities, there’s a mixin class defined in the standard library called ContextDecorator
. You can add decorator abilities to your context manager classes simply by inheriting ContextDecorator
:
de contextlib import ContextDecorator
class Minuteur(ContextDecorator):
# Implementation of Timer is unchanged
When you use ContextDecorator
this way, there’s no need to implement .__call__()
yourself, so you can safely delete it from the Minuteur
class.
Using the Python Timer Decorator
Let’s redo the latest_tutorial.py
example one last time, using the Python timer as a decorator:
1 # latest_tutorial.py
2
3 de timer import Minuteur
4 de reader import feed
5
6 @Timer()
sept def principale():
8 """Print the latest tutorial from Real Python"""
9 Didacticiel = feed.get_article(0)
dix impression(Didacticiel)
11
12 si __name__ == "__main__":
13 principale()
If you compare this implementation with the original implementation without any timing, then you’ll notice that the only differences are the import of Minuteur
on line 3 and the application of @Timer()
on line 6. A significant advantage of using decorators is that they’re usually straightforward to apply, as you see here.
However, the decorator still applies to the whole function. This means your code is taking into account the time it takes to print the tutorial, in addition to the time it takes to download. Let’s run the script one final time:
$ python latest_tutorial.py
# Python Timer Functions: Three Ways to Monitor Your Code
[ ... The full text of the tutorial ... ]
Elapsed time: 0.69 seconds
The location of the elapsed time output is a tell-tale sign that your code is considering the time it takes to print time as well. As you see here, your code prints the elapsed time après the tutorial.
When you use Minuteur
as a decorator, you’ll see similar advantages as you did with context managers:
- Low effort: You only need one extra line of code to time the execution of a function.
- Readability: When you add the decorator, you can note more clearly that your code will time the function.
- Consistency: You only need to add the decorator when the function is defined. Your code will consistently time it every time it’s called.
However, decorators are not as flexible as context managers. You can only apply them to complete functions. It’s possible to add decorators to already defined functions, but this is a bit clunky and less common.
The Python Timer Code
You can expand the code block below to view the final source code for your Python timer:
# timer.py
de contextlib import ContextDecorator
de dataclasses import dataclass, field
import temps
de typing import Tout, Callable, ClassVar, Dict, Optional
class TimerError(Exception):
"""A custom exception used to report errors in use of Timer class"""
@dataclass
class Minuteur(ContextDecorator):
"""Time your code using a class, context manager, or decorator"""
timers: ClassVar[[[[Dict[[[[str, float]] = dict()
name: Optional[[[[str] = Aucun
text: str = "Elapsed time: :0.4f seconds"
enregistreur: Optional[[[[Callable[[[[[[[[str], Aucun]] = impression
_start_time: Optional[[[[float] = field(default=Aucun, init=Faux, repr=Faux)
def __post_init__(self) -> Aucun:
"""Initialization: add timer to dict of timers"""
si self.name:
self.timers.setdefault(self.name, 0)
def début(self) -> Aucun:
"""Start a new timer"""
si self._start_time est ne pas Aucun:
raise TimerError(f"Timer is running. Use .stop() to stop it")
self._start_time = temps.perf_counter()
def Arrêtez(self) -> float:
"""Stop the timer, and report the elapsed time"""
si self._start_time est Aucun:
raise TimerError(f"Timer is not running. Use .start() to start it")
# Calculate elapsed time
elapsed_time = temps.perf_counter() - self._start_time
self._start_time = Aucun
# Report elapsed time
si self.enregistreur:
self.enregistreur(self.text.format(elapsed_time))
si self.name:
self.timers[[[[self.name] += elapsed_time
revenir elapsed_time
def __enter__(self) -> "Timer":
"""Start a new timer as a context manager"""
self.début()
revenir self
def __exit__(self, *exc_info: Tout) -> Aucun:
"""Stop the context manager timer"""
self.Arrêtez()
The code is also available in the codetiming
repository on GitHub.
You can use the code yourself by saving it to a file named timer.py
and importing it into your program:
>>> de timer import Minuteur
Minuteur
is also available on PyPI, so an even easier option is to install it using pip
:
$ python -m pip install codetiming
Note that the package name on PyPI is codetiming
. You’ll need to use this name both when you install the package and when you import Minuteur
:
>>> de codetiming import Minuteur
Apart from this, codetiming.Timer
works exactly as timer.Timer
. To summarize, you can use Minuteur
de trois manières différentes:
-
Comme un class:
t = Minuteur(name="class") t.début() # Do something t.Arrêtez()
-
Comme un context manager:
avec Minuteur(name="context manager"): # Do something
-
Comme un decorator:
@Timer(name="decorator") def des trucs(): # Do something
This kind of Python timer is mainly useful for monitoring the time your code spends at individual key code blocks or functions. In the next section, you’ll get a quick overview of alternatives you can use if you’re looking to optimize your code.
Other Python Timer Functions
There are many options for timing your code with Python. In this tutorial, you’ve learned how to create a flexible and convenient class that you can use in several different ways. A quick search on PyPI shows that there are already many projects available that offer Python timer solutions.
In this section, you’ll first learn more about the different functions available in the standard library for measuring time, and why perf_counter()
is preferable. Then, you’ll see alternatives for optimizing your code, for which Minuteur
is not well-suited.
Using Alternative Python Timer Functions
You’ve been using perf_counter()
throughout this tutorial to do the actual time measurements, but Python’s temps
library comes with several other functions that also measure time. Here are some alternatives:
One of the reasons why there are several functions is that Python represents time as a float
. Floating-point numbers are inaccurate by nature. You may have seen results like these before:
>>> 0,1 + 0,1 + 0,1
0.30000000000000004
>>> 0,1 + 0,1 + 0,1 == 0.3
Faux
Python’s float
follows the IEEE 754 Standard for Floating-Point Arithmetic, which tries to represent all floating-point numbers in 64 bits. Since there are infinitely many floating-point numbers, you can’t express them all with a finite number of bits.
IEEE 754 prescribes a system where the density of numbers that you can represent varies. The closer you are to 1, the more numbers you can represent. For larger numbers, there’s more espace between the numbers that you can express. This has some consequences when you use a float
to represent time.
Considérer time()
. The main purpose of this function is to represent the actual time right now. It does this as the number of seconds since a given point in time, called the epoch. The number returned by time()
is quite big, which means that there are fewer numbers available, and the resolution suffers. Specifically, time()
is not able to measure nanosecond differences:
>>> import temps
>>> t = temps.temps()
>>> t
1564342757.0654016
>>> t + 1e-9
1564342757.0654016
>>> t == t + 1e-9
True
A nanosecond is one-billionth of a second. Note that adding a nanosecond to t
does not affect the result. perf_counter()
, on the other hand, uses some undefined point in time as its epoch, allowing it to work with smaller numbers and therefore obtain a better resolution:
>>> import temps
>>> p = temps.perf_counter()
>>> p
11370.015653846
>>> p + 1e-9
11370.015653847
>>> p == p + 1e-9
Faux
Here, you see that adding a nanosecond to p
actually affects the outcome. For more information about how to work with time()
, see A Beginner’s Guide to the Python time Module.
The challenges with representing time as a float
are well known, so Python 3.7 introduced a new option. Chaque temps
measurement function now has a corresponding _ns
function that returns the number of nanoseconds as an int
instead of the number of seconds as a float
. For instance, time()
now has a nanosecond counterpart called time_ns()
:
>>> import temps
>>> temps.time_ns()
1564342792866601283
Integers are unbounded in Python, so this allows time_ns()
to give nanosecond resolution for all eternity. De même, perf_counter_ns()
is a nanosecond variant of perf_counter()
:
>>> import temps
>>> temps.perf_counter()
13580.153084446
>>> temps.perf_counter_ns()
13580765666638
Puisque perf_counter()
already provides nanosecond resolution, there are fewer advantages to using perf_counter_ns()
.
Remarque: perf_counter_ns()
is only available in Python 3.7 and later. In this tutorial, you’ve used perf_counter()
in your Minuteur
class. That way, Minuteur
can be used on older Python versions as well.
For more information about the _ns
functions in temps
, check out Cool New Features in Python 3.7.
There are two functions in temps
that do not measure the time spent sleeping. Ceux-ci sont process_time()
et thread_time()
, which are useful in some settings. However, for Minuteur
, you typically want to measure the full time spent. The final function in the list above is monotonic()
. The name alludes to this function being a monotonic timer, which is a Python timer that can never move backward.
All these functions are monotonic except time()
, which can go backward if the system time is adjusted. On some systems, monotonic()
is the same function as perf_counter()
, and you can use them interchangeably. However, this is not always the case. You can use time.get_clock_info()
to get more information about a Python timer function. Using Python 3.7 on Linux I get the following information:
>>> import temps
>>> temps.get_clock_info("monotonic")
namespace(adjustable=False, implementation='clock_gettime(CLOCK_MONOTONIC)',
monotonic=True, resolution=1e-09)
>>> temps.get_clock_info("perf_counter")
namespace(adjustable=False, implementation='clock_gettime(CLOCK_MONOTONIC)',
monotonic=True, resolution=1e-09)
The results could be different on your system.
PEP 418 describes some of the rationale behind introducing these functions. It includes the following short descriptions:
time.monotonic()
: timeout and scheduling, not affected by system clock updatestime.perf_counter()
: benchmarking, most precise clock for short periodtime.process_time()
: profiling, CPU time of the process (Source)
As you can see, it’s usually the best choice for you to use perf_counter()
for your Python timer.
Estimating Running Time With timeit
Say you’re trying to squeeze the last bit of performance out of your code, and you’re wondering about the most effective way to convert a list to a set. You want to compare using set()
and the set literal, ...
. You can use your Python timer for this:
>>> de timer import Minuteur
>>> Nombres = [[[[sept, 6, 1, 4, 1, 8, 0, 6]
>>> avec Minuteur(text=":.8f"):
... ensemble(Nombres)
...
0, 1, 4, 6, 7, 8
0.00007373
>>> avec Minuteur(text=":.8f"):
... *Nombres
...
0, 1, 4, 6, 7, 8
0.00006204
This test seems to indicate that the set literal might be slightly faster. However, these results are quite uncertain, and if you rerun the code, you might get wildly different results. That’s because you’re only trying the code once. You could, for instance, get unlucky and run the script just as your computer is becoming busy with other tasks.
A better way is to use the timeit
standard library. It’s designed precisely to measure the execution time of small code snippets. While you can import and call timeit.timeit()
from Python as a regular function, it is usually more convenient to use the command-line interface. You can time the two variants as follows:
$ python -m timeit --setup "nums = [7, 6, 1, 4, 1, 8, 0, 6]" "set(nums)"
2000000 loops, best of 5: 163 nsec per loop
$ python -m timeit --setup "nums = [7, 6, 1, 4, 1, 8, 0, 6]" "*nums"
2000000 loops, best of 5: 121 nsec per loop
timeit
automatically calls your code many times to average out noisy measurements. The results from timeit
confirm that the set literal is faster than set()
. You can find more information about this particular issue at Michael Bassili’s blog.
Remarque: Be careful when you’re using timeit
on code that can download files or access databases. Puisque timeit
automatically calls your program several times, you could unintentionally end up spamming the server with requests!
Finally, the IPython interactive shell and the Jupyter notebook have extra support for this functionality with the %timeit
magic command:
Dans [1]: Nombres = [[[[sept, 6, 1, 4, 1, 8, 0, 6]
Dans [2]: %timeit set(numbers)
171 ns ± 0.748 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Dans [3]: %timeit *numbers
147 ns ± 2.62 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Again, the measurements indicate that using a set literal is faster. In Jupyter Notebooks you can also use the %%timeit
cell-magic to measure the time of running a whole cell.
Finding Bottlenecks in Your Code With Profilers
timeit
is excellent for benchmarking a particular snippet of code. However, it would be very cumbersome to use it to check all parts of your program and locate which sections take the most time. Instead, you can use a profiler.
cProfile
is a profiler that you can access at any time from the standard library. You can use it in several ways, although it’s usually most straightforward to use it as a command-line tool:
$ python -m cProfile -o latest_tutorial.prof latest_tutorial.py
This command runs latest_tutorial.py
with profiling turned on. You save the output from cProfile
dans latest_tutorial.prof
, as specified by the -o
option. The output data is in a binary format that needs a dedicated program to make sense of it. Again, Python has an option right in the standard library! Runnin the pstats
module on your .prof
file opens an interactive profile statistics browser:
$ python -m pstats latest_tutorial.prof
Welcome to the profile statistics browser.
latest_tutorial.prof% help
Documented commands (type help ):
========================================
EOF add callees callers help quit read reverse sort stats strip
To use pstats
you type commands at the prompt. Here you can see the integrated help system. Typically you’ll use the sort
et stats
commands. To get a cleaner output, strip
can be useful:
latest_tutorial.prof% strip
latest_tutorial.prof% sort cumtime
latest_tutorial.prof% stats 10
1393801 function calls (1389027 primitive calls) in 0.586 seconds
Ordered by: cumulative time
List reduced from 1443 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
144/1 0.001 0.000 0.586 0.586 built-in method builtins.exec
1 0.000 0.000 0.586 0.586 latest_tutorial.py:3()
1 0.000 0.000 0.521 0.521 contextlib.py:71(inner)
1 0.000 0.000 0.521 0.521 latest_tutorial.py:6(read_latest_tutorial)
1 0.000 0.000 0.521 0.521 feed.py:28(get_article)
1 0.000 0.000 0.469 0.469 feed.py:15(_feed)
1 0.000 0.000 0.469 0.469 feedparser.py:3817(parse)
1 0.000 0.000 0.271 0.271 expatreader.py:103(parse)
1 0.000 0.000 0.271 0.271 xmlreader.py:115(parse)
13 0.000 0.000 0.270 0.021 expatreader.py:206(feed)
This output shows that the total runtime was 0.586 seconds. It also lists the ten functions where your code spent most of its time. Here you’ve sorted by cumulative time (cumtime
), which means that your code counts time when the given function has called another function.
You can see that your code spends virtually all its time inside the latest_tutorial
module, and in particular, inside read_latest_tutorial()
. While this might be useful confirmation of what you already know, it’s often more interesting to find where your code actually spends time.
The total time (tottime
) column indicates how much time your code spent inside a function, excluding time in sub-functions. You can see that none of the functions above really spend any time doing this. To find where the code spent most of its time, issue another sort
commander:
latest_tutorial.prof% sort tottime
latest_tutorial.prof% stats 10
1393801 function calls (1389027 primitive calls) in 0.586 seconds
Ordered by: internal time
List reduced from 1443 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
59 0.091 0.002 0.091 0.002 method 'read' of '_ssl._SSLSocket'
114215 0.070 0.000 0.099 0.000 feedparser.py:308(__getitem__)
113341 0.046 0.000 0.173 0.000 feedparser.py:756(handle_data)
1 0.033 0.033 0.033 0.033 method 'do_handshake' of '_ssl._SSLSocket'
1 0.029 0.029 0.029 0.029 method 'connect' of '_socket.socket'
13 0.026 0.002 0.270 0.021 method 'Parse' of 'pyexpat.xmlparser'
113806 0.024 0.000 0.123 0.000 feedparser.py:373(get)
3455 0.023 0.000 0.024 0.000 method 'sub' of 're.Pattern'
113341 0.019 0.000 0.193 0.000 feedparser.py:2033(characters)
236 0.017 0.000 0.017 0.000 method 'translate' of 'str'
You can now see that latest_tutorial.py
actually spends most of its time working with sockets or handling data inside feedparser
. The latter is one of the dependencies of the Real Python Reader that’s used to parse the tutorial feed.
You can use pstats
to get some idea on where your code is spending most of its time and see if you can optimize any bottlenecks you find. You can also use the tool to understand the structure of your code better. For instance, the commands callees
et callers
will show you which functions call and are called by a given function.
You can also investigate certain functions. Let’s see how much overhead Minuteur
causes by filtering the results with the phrase timer
:
latest_tutorial.prof% stats timer
1393801 function calls (1389027 primitive calls) in 0.586 seconds
Ordered by: internal time
List reduced from 1443 to 8 due to restriction <'timer'>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 timer.py:13(Timer)
1 0.000 0.000 0.000 0.000 timer.py:35(stop)
1 0.000 0.000 0.003 0.003 timer.py:3()
1 0.000 0.000 0.000 0.000 timer.py:28(start)
1 0.000 0.000 0.000 0.000 timer.py:9(TimerError)
1 0.000 0.000 0.000 0.000 timer.py:23(__post_init__)
1 0.000 0.000 0.000 0.000 timer.py:57(__exit__)
1 0.000 0.000 0.000 0.000 timer.py:52(__enter__)
Heureusement, Minuteur
causes only minimal overhead. Utilisation quit
to leave the pstats
browser when you’re done investigating.
For a more powerful interface into profile data, check out KCacheGrind. It uses its own data format, but you can convert data from cProfile
en utilisant pyprof2calltree
:
$ pyprof2calltree -k -i latest_tutorial.prof
This command will convert latest_tutorial.prof
and open KCacheGrind to analyze the data.
The last option you’ll see here for timing your code is line_profiler
. cProfile
can tell you which functions your code spends the most time in, but it won’t give you insights into which lines inside that function are the slowest. That’s where line_profiler
can help you.
Remarque: You can also profile the memory consumption of your code. This falls outside the scope of this tutorial. However, you can have a look at memory-profiler
if you need to monitor the memory consumption of your programs.
Note that line profiling takes time and adds a fair bit of overhead to your runtime. A more standard workflow is first to use cProfile
to identify which functions to look at and then run line_profiler
on those functions. line_profiler
is not part of the standard library, so you should first follow the installation instructions to set it up.
Before you run the profiler, you need to tell it which functions to profile. You do this by adding a @profile
decorator inside your source code. For example, to profile Timer.stop()
you add the following inside timer.py
:
@profile
def Arrêtez(self) -> float:
# The rest of the code is unchanged
Note that you don’t import profil
anywhere. Instead, it’s automatically added to the global namespace when you run the profiler. You need to delete the line when you’re done profiling, though. Otherwise, you’ll get a NameError
.
Next, run the profiler using kernprof
, which is part of the line_profiler
package:
$ kernprof -l latest_tutorial.py
This command automatically saves the profiler data in a file called latest_tutorial.py.lprof
. You can see those results using line_profiler
:
$ python -m line_profiler latest_tutorial.py.lprof
Timer unit: 1e-06 s
Total time: 1.6e-05 s
File: /home/realpython/timer.py
Function: stop at line 35
# Hits Time PrHit %Time Line Contents
=====================================
35 @profile
36 def stop(self) -> float:
37 """Stop the timer, and report the elapsed time"""
38 1 1.0 1.0 6.2 if self._start_time is None:
39 raise TimerError(f"Timer is not running. ...")
40
41 # Calculate elapsed time
42 1 2.0 2.0 12.5 elapsed_time = time.perf_counter() - self._start_time
43 1 0.0 0.0 0.0 self._start_time = None
44
45 # Report elapsed time
46 1 0.0 0.0 0.0 if self.logger:
47 1 11.0 11.0 68.8 self.logger(self.text.format(elapsed_time))
48 1 1.0 1.0 6.2 if self.name:
49 1 1.0 1.0 6.2 self.timers[self.name] += elapsed_time
50
51 1 0.0 0.0 0.0 return elapsed_time
First, note that the time unit in this report is microseconds (1e-06 s
). Usually, the most accessible number to look at is %Time
, which tells you the percentage of the total time your code spends inside a function at each line. In this example, you can see that your code spends almost 70% of the time on line 47, which is the line that formats and prints the result of the timer.
Conclusion
In this tutorial, you’ve seen several different approaches to adding a Python timer to your code:
-
You used a class to keep state and add a user-friendly interface. Classes are very flexible, and using
Minuteur
directly gives you full control over how and when to invoke the timer. -
You used a context manager to add features to a block of code and, if necessary, to clean up afterward. Context managers are straightforward to use, and adding
with Timer()
can help you more clearly distinguish your code visually. -
You used a decorator to add behavior to a function. Decorators are concise and compelling, and using
@Timer()
is a quick way to monitor your code’s runtime.
You’ve also seen why you should prefer time.perf_counter()
plus de time.time()
when benchmarking code, as well as what other alternatives are useful when you’re optimizing your code.
Now you can add Python timer functions to your own code! Keeping track of how fast your program runs in your logs will help you monitor your scripts. Do you have ideas for other use cases where classes, context managers, and decorators play well together? Leave a comment down below!
Ressources
For a deeper dive into Python timer functions, check out these resources:
codetiming
is the Python timer available on PyPI.time.perf_counter()
is a performance counter for precise timings.timeit
is a tool for comparing the runtimes of code snippets.cProfile
is a profiler for finding bottlenecks in scripts and programs.pstats
is a command-line tool for looking at profiler data.- KCachegrind is a GUI for looking at profiler data.
line_profiler
is a profiler for measuring individual lines of code.memory-profiler
is a profiler for monitoring memory usage.
[ad_2]