python pour débutant
- Episode # 278 Enseignez aux enfants Python avec de la vraie programmation et des jeux amusants à Code Combat
- Le didacticiel WordPress essentiel pour les développeurs PHP • WPShout
- Votre guide du code source CPython – Real Python
- Programmation linéaire, PySimpleGUI et plus – Le podcast Real Python
- Épisode #325 MicroPython + CircuitPython
le avec
en Python est un outil très utile pour gérer correctement les ressources externes dans vos programmes. Il vous permet de profiter de l'existant gestionnaires de contexte pour gérer automatiquement les phases de configuration et de démontage chaque fois que vous avez affaire à des ressources externes ou à des opérations qui nécessitent ces phases.
En plus, le protocole de gestion de contexte vous permet de créer vos propres gestionnaires de contexte afin de personnaliser la façon dont vous gérez les ressources système. Alors, quel est le avec
déclaration bonne pour?
Dans ce tutoriel, vous apprendrez :
- Qu'est-ce que le Python
avec
déclaration est pour et comment l'utiliser - Qu'est-ce que le protocole de gestion de contexte est
- Comment mettre en œuvre votre propre gestionnaires de contexte
Avec cette connaissance, vous écrirez un code plus expressif et éviterez les fuites de ressources dans vos programmes. le avec
L'instruction vous aide à implémenter certains modèles de gestion des ressources courants en faisant abstraction de leurs fonctionnalités et en leur permettant d'être factorisés et réutilisés.
Gestion des ressources en Python
Un problème courant auquel vous serez confronté dans la programmation est de savoir comment gérer correctement ressources externes, tels que les fichiers, les verrous et les connexions réseau. Parfois, un programme conservera ces ressources pour toujours, même si vous n'en avez plus besoin. Ce type de problème est appelé fuite de mémoire car la mémoire disponible est réduite chaque fois que vous créez et ouvrez une nouvelle instance d'une ressource donnée sans en fermer une existante.
La bonne gestion des ressources est souvent un problème délicat. Cela nécessite à la fois un mettre en place phase et une démolir phase. Cette dernière phase vous oblige à effectuer certaines actions de nettoyage, telles que la fermeture d'un fichier, la libération d'un verrou ou la fermeture d'une connexion réseau. Si vous oubliez d'effectuer ces actions de nettoyage, votre application maintient la ressource en vie. Cela pourrait compromettre des ressources système précieuses, telles que la mémoire et la bande passante du réseau.
Par exemple, un problème courant qui peut survenir lorsque les développeurs travaillent avec des bases de données est lorsqu'un programme continue de créer de nouvelles connexions sans les libérer ou les réutiliser. Dans ce cas, le back-end de la base de données peut cesser d'accepter de nouvelles connexions. Cela peut nécessiter qu'un administrateur se connecte et supprime manuellement ces connexions obsolètes pour rendre la base de données utilisable à nouveau.
Un autre problème fréquent apparaît lorsque les développeurs travaillent avec des fichiers. L'écriture de texte dans des fichiers est généralement une opération mise en mémoire tampon. Cela signifie que l'appel .écrivez()
sur un fichier n'entraînera pas immédiatement l'écriture de texte dans le fichier physique mais dans un tampon temporaire. Parfois, lorsque le tampon n'est pas plein et que les développeurs oublient d'appeler .Fermer()
, une partie des données peut être perdue à jamais.
Une autre possibilité est que votre application rencontre des erreurs ou des exceptions qui font que le flux de contrôle contourne le code responsable de la libération de la ressource disponible. Voici un exemple dans lequel vous utilisez ouvert()
pour écrire du texte dans un fichier :
déposer = ouvert("bonjour.txt", "w")
déposer.écrivez("Bonjour le monde!")
déposer.Fermer()
Cette implémentation ne garantit pas que le fichier sera fermé si une exception se produit pendant la .écrivez()
appel. Dans ce cas, le code n'appellera jamais .Fermer()
, et par conséquent, votre programme peut divulguer un descripteur de fichier.
En Python, vous pouvez utiliser deux approches générales pour gérer la gestion des ressources. Vous pouvez encapsuler votre code dans :
- UNE
essayer
…finalement
construction - UNE
avec
construction
La première approche est assez générale et vous permet de fournir du code d'installation et de démontage pour gérer tout type de ressource. Cependant, c'est un peu bavard. Et si vous oubliez des actions de nettoyage ?
La deuxième approche fournit un moyen simple de fournir et de réutiliser le code de configuration et de démontage. Dans ce cas, vous aurez la limitation que le avec
L'instruction ne fonctionne qu'avec les gestionnaires de contexte. Dans les deux sections suivantes, vous apprendrez à utiliser les deux approches dans votre code.
le essayer
… finalement
Approcher
Travailler avec des fichiers est probablement l'exemple le plus courant de gestion des ressources en programmation. En Python, vous pouvez utiliser un essayer
… finalement
instruction pour gérer correctement l'ouverture et la fermeture des fichiers :
# Ouvrez le fichier en toute sécurité
déposer = ouvert("bonjour.txt", "w")
essayer:
déposer.écrivez("Bonjour le monde!")
finalement:
# Assurez-vous de fermer le fichier après l'avoir utilisé
déposer.Fermer()
Dans cet exemple, vous devez ouvrir le fichier en toute sécurité bonjour.txt
, ce que vous pouvez faire en enveloppant l'appel vers ouvert()
dans un essayer
… sauf
déclaration. Plus tard, lorsque vous essayez d'écrire à déposer
, les finalement
clause garantira que déposer
est correctement fermé, même si une exception survient lors de l'appel à .écrivez()
dans le essayer
clause. Vous pouvez utiliser ce modèle pour gérer la logique de configuration et de démontage lorsque vous gérez des ressources externes en Python.
le essayer
block dans l'exemple ci-dessus peut potentiellement déclencher des exceptions, telles que AttributeError
ou alors NameError
. Vous pouvez gérer ces exceptions dans un sauf
clause comme celle-ci :
# Ouvrez le fichier en toute sécurité
déposer = ouvert("bonjour.txt", "w")
essayer:
déposer.écrivez("Bonjour le monde!")
sauf Exception comme e:
imprimer(F"Une erreur s'est produite lors de l'écriture dans le fichier : e")
finalement:
# Assurez-vous de fermer le fichier après l'avoir utilisé
déposer.Fermer()
Dans cet exemple, vous interceptez toutes les exceptions potentielles qui peuvent se produire lors de l'écriture dans le fichier. Dans des situations réelles, vous devez utiliser un type d'exception spécifique au lieu du général Exception
pour empêcher les erreurs inconnues de passer en silence.
le avec
Approche de l'énoncé
Le Python avec
déclaration crée un contexte d'exécution qui vous permet d'exécuter un groupe d'instructions sous le contrôle d'un gestionnaire de contexte. PEP 343 a ajouté le avec
déclaration pour permettre de factoriser les cas d'utilisation standard du essayer
… finalement
déclaration.
Par rapport au traditionnel essayer
… finalement
construit, le avec
peut rendre votre code plus clair, plus sûr et réutilisable. De nombreuses classes de la bibliothèque standard prennent en charge le avec
déclaration. Un exemple classique en est ouvert()
, qui vous permet de travailler avec des objets fichier en utilisant avec
.
Pour écrire un avec
instruction, vous devez utiliser la syntaxe générale suivante :
avec expression comme var_cible:
faire quelque chose(var_cible)
L'objet gestionnaire de contexte résulte de l'évaluation de la expression
après avec
. Autrement dit, expression
doit retourner un objet qui implémente le protocole de gestion de contexte. Ce protocole se compose de deux méthodes spéciales :
.__Entrer__()
est appelé par leavec
pour entrer dans le contexte d'exécution..__sortir__()
est appelé lorsque l'exécution quitte leavec
bloc de code.
le comme
le spécificateur est facultatif. Si vous fournissez un var_cible
avec comme
, puis la valeur de retour de l'appel .__Entrer__()
sur l'objet gestionnaire de contexte est lié à cette variable.
Noter: Certains gestionnaires de contexte reviennent Rien
de .__Entrer__()
car ils n'ont aucun objet utile à rendre à l'appelant. Dans ces cas, spécifier un var_cible
ça n'a aucun sens.
Voici comment le avec
L'instruction se poursuit lorsque Python l'exécute :
- Appel
expression
pour obtenir un gestionnaire de contexte. - Stocker le gestionnaire de contexte
.__Entrer__()
et.__sortir__()
méthodes pour une utilisation ultérieure. - Appel
.__Entrer__()
sur le gestionnaire de contexte et lier sa valeur de retour àvar_cible
si fourni. - Exécuter le
avec
bloc de code. - Appel
.__sortir__()
sur le gestionnaire de contexte lorsque leavec
le bloc de code se termine.
Dans ce cas, .__Entrer__()
, fournit généralement le code de configuration. le avec
est une instruction composée qui démarre un bloc de code, comme une instruction conditionnelle ou un pour
boucle. À l'intérieur de ce bloc de code, vous pouvez exécuter plusieurs instructions. En règle générale, vous utilisez le avec
bloc de code à manipuler var_cible
le cas échéant.
Une fois la avec
le bloc de code se termine, .__sortir__()
est appelé. Cette méthode fournit généralement la logique de démontage ou le code de nettoyage, comme l'appel .Fermer()
sur un objet fichier ouvert. C'est pourquoi le avec
déclaration est si utile. Cela facilite l'acquisition et la libération des ressources.
Voici comment ouvrir votre bonjour.txt
fichier pour l'écriture à l'aide du avec
déclaration:
avec ouvert("bonjour.txt", mode="w") comme déposer:
déposer.écrivez("Bonjour le monde!")
Lorsque vous exécutez ce avec
déclaration, ouvert()
renvoie un io.TextIOBase
objet. Cet objet est aussi un gestionnaire de contexte, donc le avec
appels de déclaration .__Entrer__()
et attribue sa valeur de retour à déposer
. Ensuite, vous pouvez manipuler le fichier à l'intérieur du avec
bloc de code. Lorsque le bloc se termine, .__sortir__()
est automatiquement appelé et ferme le fichier pour vous, même si une exception est levée dans le avec
bloquer.
Cette avec
la construction est plus courte que sa essayer
… finalement
alternative, mais c'est aussi moins général, comme vous l'avez déjà vu. Vous ne pouvez utiliser que le avec
avec des objets qui prennent en charge le protocole de gestion de contexte, alors que essayer
… finalement
vous permet d'effectuer des actions de nettoyage pour des objets arbitraires sans avoir besoin de prendre en charge le protocole de gestion de contexte.
Dans Python 3.1 et versions ultérieures, le avec
L'instruction prend en charge plusieurs gestionnaires de contexte. Vous pouvez fournir n'importe quel nombre de gestionnaires de contexte séparés par des virgules :
avec UNE() comme une, B() comme b:
passe
Cela fonctionne comme imbriqué avec
déclarations mais sans imbrication. Cela peut être utile lorsque vous devez ouvrir deux fichiers à la fois, le premier pour la lecture et le second pour l'écriture :
avec ouvert("entrée.txt") comme dans le fichier, ouvert("sortie.txt", "w") comme out_file:
# Lire le contenu de input.txt
# Transformez le contenu
# Écrivez le contenu transformé dans output.txt
passe
Dans cet exemple, vous pouvez ajouter du code pour lire et transformer le contenu de entrée.txt
. Ensuite, vous écrivez le résultat final sur sortie.txt
dans le même bloc de code.
Utilisation de plusieurs gestionnaires de contexte dans un seul avec
a un inconvénient, cependant. Si vous utilisez cette fonctionnalité, vous dépasserez probablement votre limite de longueur de ligne. Pour contourner ce problème, vous devez utiliser des barres obliques inverses () pour la continuation de ligne, vous pourriez donc vous retrouver avec un résultat final moche.
le avec
peut rendre le code qui traite des ressources système plus lisible, réutilisable et concis, pour ne pas dire plus sûr. Cela permet d'éviter les bogues et les fuites en rendant presque impossible d'oublier le nettoyage, la fermeture et la libération d'une ressource une fois que vous en avez terminé.
Utilisant avec
vous permet d'abstraire la plupart de la logique de gestion des ressources. Au lieu d'avoir à écrire un texte explicite essayer
… finalement
déclaration avec le code d'installation et de démontage à chaque fois, avec
s'en occupe pour vous et évite les répétitions.
Utiliser le Python avec
Déclaration
Tant que les développeurs Python ont intégré le avec
déclaration dans leur pratique de codage, l'outil s'est avéré avoir plusieurs cas d'utilisation précieux. De plus en plus d'objets de la bibliothèque standard Python prennent désormais en charge le protocole de gestion de contexte afin que vous puissiez les utiliser dans un avec
déclaration.
Dans cette section, vous allez coder quelques exemples qui montrent comment utiliser le avec
avec plusieurs classes à la fois dans la bibliothèque standard et dans les bibliothèques tierces.
Travailler avec des fichiers
Jusqu'à présent, vous avez utilisé ouvert()
fournir un gestionnaire de contexte et manipuler des fichiers dans un avec
construction. Ouverture de fichiers à l'aide du avec
est généralement recommandée car elle garantit que les descripteurs de fichiers ouverts sont automatiquement fermés une fois que le flux d'exécution a quitté le avec
bloc de code.
Comme vous l'avez vu précédemment, la façon la plus courante d'ouvrir un fichier en utilisant avec
est à travers le intégré ouvert()
:
avec ouvert("bonjour.txt", mode="w") comme déposer:
déposer.écrivez("Bonjour le monde!")
Dans ce cas, puisque le gestionnaire de contexte ferme le fichier après avoir quitté le avec
bloc de code, une erreur courante peut être la suivante :
>>> déposer = ouvert("bonjour.txt", mode="w")
>>> avec déposer:
... déposer.écrivez("Bonjour le monde!")
...
13
>>> avec déposer:
... déposer.écrivez("Bienvenue dans le vrai Python !")
...
Traceback (appel le plus récent en dernier) :
Déposer "" , ligne 1, dans
Erreur de valeur: Opération d'E/S sur fichier fermé.
La première avec
écrit avec succès "Bonjour le monde!"
dans bonjour.txt
. Noter que .écrivez()
renvoie le nombre d'octets écrits dans le fichier, 13
. Quand tu essaies de courir une seconde avec
, cependant, vous obtenez un Erreur de valeur
parce que votre déposer
est déjà fermé.
Une autre façon d'utiliser le avec
instruction pour ouvrir et gérer les fichiers est en utilisant pathlib.Path.open()
:
>>> importer pathlib
>>> chemin du fichier = pathlib.Chemin("bonjour.txt")
>>> avec chemin du fichier.ouvert("w") comme déposer:
... déposer.écrivez("Bonjour le monde!")
...
13
Chemin
est une classe qui représente des chemins concrets vers des fichiers physiques sur votre ordinateur. Appel .ouvert()
sur un Chemin
objet qui pointe vers un fichier physique l'ouvre comme ouvert()
ferait. Donc, Chemin.open()
fonctionne de la même manière que ouvert()
, mais le chemin du fichier est automatiquement fourni par le Chemin
objet sur lequel vous appelez la méthode.
Depuis pathlib
fournit un moyen élégant, simple et Pythonic de manipuler les chemins du système de fichiers, vous devriez envisager d'utiliser Chemin.open()
dans votre avec
déclarations en tant que meilleure pratique en Python.
Enfin, chaque fois que vous chargez un fichier externe, votre programme doit rechercher d'éventuels problèmes, tels qu'un fichier manquant, un accès en écriture et en lecture, etc. Voici un modèle général que vous devriez envisager d'utiliser lorsque vous travaillez avec des fichiers :
importer pathlib
importer enregistrement
chemin du fichier = pathlib.Chemin("bonjour.txt")
essayer:
avec chemin du fichier.ouvert(mode="w") comme déposer:
déposer.écrivez("Bonjour le monde!")
sauf OSError comme Erreur:
enregistrement.Erreur("Écrire au dossier %s a échoué en raison de : %s", chemin du fichier, Erreur)
Dans cet exemple, vous enveloppez le avec
déclaration dans un essayer
… sauf
déclaration. Si un OSError
survient lors de l'exécution de avec
, alors tu utilises enregistrement
pour consigner l'erreur avec un message convivial et descriptif.
Parcourir les répertoires
le système d'exploitation
module fournit une fonction appelée scandir()
, qui renvoie un itérateur sur os.DirEntry
objets correspondant aux entrées d'un répertoire donné. Cette fonction est spécialement conçue pour fournir des performances optimales lorsque vous parcourez une structure de répertoires.
Un appel à scandir()
avec le chemin d'accès à un répertoire donné en argument renvoie un itérateur qui prend en charge le protocole de gestion de contexte :
>>> importer système d'exploitation
>>> avec système d'exploitation.scandir(".") comme entrées:
... pour entrée dans entrées:
... imprimer(entrée.Nom, "->", entrée.statistique().st_taille, "octets")
...
Documents -> 4096 octets
Vidéos -> 12288 octets
Bureau -> 4096 octets
DevSpace -> 4096 octets
.profile -> 807 octets
Modèles -> 4096 octets
Images -> 12288 octets
Public -> 4096 octets
Téléchargements -> 4096 octets
Dans cet exemple, vous écrivez un avec
déclaration avec os.scandir()
en tant que fournisseur de gestionnaire de contexte. Ensuite, vous parcourez les entrées du répertoire sélectionné ("."
) et imprimez leur nom et leur taille à l'écran. Dans ce cas, .__sortir__()
appels scandir.close()
pour fermer l'itérateur et libérer les ressources acquises. Notez que si vous l'exécutez sur votre machine, vous obtiendrez une sortie différente en fonction du contenu de votre répertoire actuel.
Effectuer des calculs de haute précision
Contrairement aux nombres à virgule flottante intégrés, le décimal
module fournit un moyen d'ajuster la précision à utiliser dans un calcul donné qui implique Décimal
Nombres. La précision par défaut est 28
endroits, mais vous pouvez le modifier pour répondre aux exigences de votre problème. Un moyen rapide d'effectuer des calculs avec une précision personnalisée consiste à utiliser contexte local()
de décimal
:
>>> de décimal importer Décimal, contexte local
>>> avec contexte local() comme ctx:
... ctx.prec = 42
... Décimal("1") / Décimal("42")
...
Décimal('0.0238095238095238095238095238095238095238095')
>>> Décimal("1") / Décimal("42")
Décimal('0.02380952380952380952380952381')
Ici, contexte local()
fournit un gestionnaire de contexte qui crée un contexte décimal local et vous permet d'effectuer des calculs avec une précision personnalisée. Dans le avec
bloc de code, vous devez définir .prec
à la nouvelle précision que vous souhaitez utiliser, qui est 42
endroits dans l'exemple ci-dessus. Quand le avec
le bloc de code se termine, la précision est réinitialisée à sa valeur par défaut, 28
des endroits.
Gestion des verrous dans les programmes multithreads
Un autre bon exemple d'utilisation du avec
déclaration efficace dans la bibliothèque standard Python est filetage.Verrouillage
. Cette classe fournit un verrou primitif pour empêcher plusieurs threads de modifier une ressource partagée en même temps dans une application multithread.
Vous pouvez utiliser un Fermer à clé
objet en tant que gestionnaire de contexte dans un avec
pour acquérir et libérer automatiquement un verrou donné. Par exemple, supposons que vous deviez protéger le solde d'un compte bancaire :
importer enfilage
balance_lock = enfilage.Fermer à clé()
# Utilisez le modèle try ... finally
balance_lock.acquérir()
essayer:
# Mettez à jour le solde du compte ici ...
finalement:
balance_lock.Libération()
# Utilisez le modèle avec
avec balance_lock:
# Mettez à jour le solde du compte ici ...
le avec
L'instruction dans le deuxième exemple acquiert et libère automatiquement un verrou lorsque le flux d'exécution entre et sort de l'instruction. De cette façon, vous pouvez vous concentrer sur ce qui compte vraiment dans votre code et oublier ces opérations répétitives.
Dans cet exemple, le verrou du avec
L'instruction crée une zone protégée connue sous le nom de section critique, qui empêche l'accès simultané au solde du compte.
Tester les exceptions avec pytest
Jusqu'à présent, vous avez codé quelques exemples à l'aide de gestionnaires de contexte disponibles dans la bibliothèque standard Python. Cependant, plusieurs bibliothèques tierces incluent des objets qui prennent en charge le protocole de gestion de contexte.
Disons que vous testez votre code avec pytest. Certaines de vos fonctions et blocs de code lèvent des exceptions dans certaines situations, et vous souhaitez tester ces cas. Pour ce faire, vous pouvez utiliser pytest.raises()
. Cette fonction vous permet d'affirmer qu'un bloc de code ou un appel de fonction lève une exception donnée.
Depuis pytest.raises()
fournit un gestionnaire de contexte, vous pouvez l'utiliser dans un avec
déclaration comme celle-ci :
>>> importer pytest
>>> 1 / 0
Traceback (appel le plus récent en dernier) :
Déposer "" , ligne 1, dans
ZeroDivisionError: division par zéro
>>> avec pytest.soulève(ZeroDivisionError):
... 1 / 0
...
>>> favoris = "fruit": "Pomme", "animaux": "chien"
>>> favoris[[[["voiture"]
Traceback (appel le plus récent en dernier) :
Déposer "" , ligne 1, dans
KeyError: 'voiture'
>>> avec pytest.soulève(KeyError):
... favoris[[[["voiture"]
...
Dans le premier exemple, vous utilisez pytest.raises()
pour capturer le ZeroDivisionError
que l'expression dix
soulève. Le deuxième exemple utilise la fonction pour capturer le KeyError
qui est déclenché lorsque vous accédez à une clé qui n'existe pas dans un dictionnaire donné.
Si votre fonction ou bloc de code ne lève pas l'exception attendue, alors pytest.raises()
lève une exception d'échec :
>>> importer pytest
>>> avec pytest.soulève(ZeroDivisionError):
... 4 / 2
...
2.0
Traceback (appel le plus récent en dernier) :
...
Manqué: N'A PAS AUGMENTÉ
Une autre fonctionnalité intéressante de pytest.raises()
est que vous pouvez spécifier une variable cible pour inspecter l'exception déclenchée. Par exemple, si vous souhaitez vérifier le message d'erreur, vous pouvez faire quelque chose comme ceci :
>>> avec pytest.soulève(ZeroDivisionError) comme sauf:
... 1 / 0
...
>>> affirmer str(sauf.valeur) == "division par zéro"
Vous pouvez utiliser tous ces pytest.raises()
fonctionnalités pour capturer les exceptions que vous soulevez à partir de vos fonctions et de votre bloc de code. Il s'agit d'un outil sympa et utile que vous pouvez intégrer à votre stratégie de test actuelle.
Résumant le avec
Avantages de la déclaration
Pour résumer ce que vous avez appris jusqu'à présent, voici une liste non exhaustive des avantages généraux de l'utilisation de Python avec
déclaration dans votre code:
- Fait du la gestion des ressources plus sûr que son équivalent
essayer
…finalement
déclarations - Encapsule les utilisations standard de
essayer
…finalement
déclarations dans gestionnaires de contexte - Permet de réutiliser le code qui gère automatiquement le mettre en place et démolir phases d'une opération donnée
- Aide à éviter fuites de ressources
En utilisant le avec
peut améliorer la qualité générale de votre code et le rendre plus sûr en évitant les problèmes de fuite de ressources.
En utilisant le asynchrone avec
Déclaration
le avec
instruction a également une version asynchrone, asynchrone avec
. Vous pouvez l'utiliser pour écrire des gestionnaires de contexte qui dépendent du code asynchrone. Il est assez courant de voir asynchrone avec
dans ce type de code, car de nombreuses opérations d'E/S impliquent des phases de configuration et de démontage.
Par exemple, supposons que vous ayez besoin de coder une fonction asynchrone pour vérifier si un site donné est en ligne. Pour ce faire, vous pouvez utiliser aiohttp
, asynchrone
, et asynchrone avec
comme ça:
1# site_checker_v0.py
2
3importer aiohttp
4importer asynchrone
5
6asynchrone déf Chèque(URL):
7 asynchrone avec aiohttp.Session Client() comme session:
8 asynchrone avec session.obtenir(URL) comme réponse:
9 imprimer(F"URL: état -> réponse.statut")
dix html = attendre réponse.texte()
11 imprimer(F"URL: tapez -> html[:[:[:[:17].déshabiller()")
12
13asynchrone déf principale():
14 attendre asynchrone.rassembler(
15 Chèque("https://realpython.com"),
16 Chèque("https://pycoders.com"),
17 )
18
19asynchrone.Cours(principale())
Voici ce que fait ce script :
- Ligne 3 importations
aiohttp
, qui fournit un client et un serveur HTTP asynchrones pourasynchrone
et Python. Noter queaiohttp
est un package tiers que vous pouvez installer en exécutantpython -m pip installer aiohttp
sur votre ligne de commande. - Ligne 4 importations
asynchrone
, qui vous permet d'écrire du code concurrent en utilisant leasynchrone
etattendre
syntaxe. - Ligne 6 définit
Chèque()
en tant que fonction asynchrone utilisant leasynchrone
mot-clé.
À l'intérieur Chèque()
, vous définissez deux imbriqués asynchrone avec
déclarations :
- Ligne 7 définit un extérieur
asynchrone avec
qui instancieaiohttp.ClientSession()
pour obtenir un gestionnaire de contexte. Il stocke l'objet renvoyé danssession
. - Ligne 8 définit un intérieur
asynchrone avec
déclaration qui appelle.obtenir()
ausession
utilisantURL
comme argument. Cela crée un deuxième gestionnaire de contexte et renvoie unréponse
. - Ligne 9 imprime le code d'état de la réponse pour le
URL
à portée de main. - Ligne 10 lance un appel en attente à
.texte()
auréponse
et enregistre le résultat danshtml
. - Ligne 11 imprime le site
URL
et son type de document,type de document
. - Ligne 13 définit le script
principale()
fonction, qui est aussi une coroutine. - Ligne 14 appels
rassembler()
deasynchrone
. Cette fonction exécute simultanément des objets en attente dans une séquence. Dans cet exemple,rassembler()
exécute deux instances deChèque()
avec une URL différente pour chacun. - Ligne 19 s'exécute
principale()
utilisantasyncio.run()
. Cette fonction crée un nouveauasynchrone
boucle événementielle et la ferme à la fin de l'opération.
Si vous exécutez ce script à partir de votre ligne de commande, vous obtenez une sortie semblable à la suivante :
$ python site_checker_v0.py
https://realpython.com : statut -> 200
https://pycoders.com : état -> 200
https://pycoders.com : tapez ->
https://realpython.com : tapez ->
Frais! Votre script fonctionne et vous confirmez que les deux sites sont actuellement disponibles. Vous récupérez également les informations concernant le type de document à partir de la page d'accueil de chaque site.
Noter: Votre sortie peut être légèrement différente en raison de la nature non déterministe de la planification des tâches simultanées et de la latence du réseau. En particulier, les lignes individuelles peuvent sortir dans un ordre différent.
le asynchrone avec
l'instruction fonctionne de manière similaire à la normale avec
déclaration, mais cela nécessite une gestionnaire de contexte asynchrone. En d'autres termes, il a besoin d'un gestionnaire de contexte capable de suspendre l'exécution dans ses méthodes d'entrée et de sortie. Les gestionnaires de contexte asynchrones implémentent les méthodes spéciales .__aenter__()
et .__aexit__()
, qui correspondent à .__Entrer__()
et .__sortir__()
dans un gestionnaire de contexte régulier.
le async avec ctx_mgr
construction utilise implicitement attendre ctx_mgr.__aenter__()
en entrant dans le contexte et attendre ctx_mgr.__aexit__()
en le sortant. Cela atteint asynchrone
le comportement du gestionnaire de contexte de manière transparente.
Création de gestionnaires de contexte personnalisés
Vous avez déjà travaillé avec des gestionnaires de contexte de la bibliothèque standard et des bibliothèques tierces. Il n'y a rien de spécial ou de magique ouvert()
, filetage.Verrouillage
, decimal.localcontext()
, ou les autres. Ils renvoient simplement des objets qui implémentent le protocole de gestion de contexte.
Vous pouvez fournir la même fonctionnalité en implémentant à la fois le .__Entrer__()
et le .__sortir__()
méthodes spéciales dans votre basé sur la classe gestionnaires de contexte. Vous pouvez également créer sur mesure basé sur la fonction gestionnaires de contexte utilisant le contextlib.contextmanager
décorateur de la bibliothèque standard et une fonction génératrice codée de manière appropriée.
En général, les gestionnaires de contexte et les avec
ne se limitent pas à la gestion des ressources. Ils vous permettent de fournir et de réutiliser le code d'installation et de démontage commun. En d'autres termes, avec les gestionnaires de contexte, vous pouvez effectuer n'importe quelle paire d'opérations à effectuer avant que et après une autre opération ou procédure, telle que :
- Ouvrir et fermer
- Verrouiller et libérer
- Modifier et réinitialiser
- Créer et supprimer
- Entrer et sortir
- Démarrer et arrêter
- Installation et démontage
Vous pouvez fournir du code pour gérer en toute sécurité l'une de ces paires d'opérations dans un gestionnaire de contexte. Ensuite, vous pouvez réutiliser ce gestionnaire de contexte dans avec
déclarations tout au long de votre code. Cela évite les erreurs et réduit le code passe-partout répétitif. Cela rend également vos API plus sûres, plus propres et plus conviviales.
Dans les deux sections suivantes, vous apprendrez les bases de la création de gestionnaires de contexte basés sur les classes et les fonctions.
Codage des gestionnaires de contexte basés sur les classes
Pour mettre en œuvre le protocole de gestion de contexte et créer basé sur la classe gestionnaires de contexte, vous devez ajouter à la fois les .__Entrer__()
et le __sortir__()
méthodes spéciales à vos classes. Le tableau ci-dessous résume le fonctionnement de ces méthodes, les arguments qu'elles prennent et la logique que vous pouvez y mettre :
Méthode | La description |
---|---|
.__enter__(self) |
Cette méthode gère la logique de configuration et est appelée lors de la saisie d'un nouveau avec le contexte. Sa valeur de retour est liée au avec variable cible. |
.__exit__(self, exc_type, exc_value, exc_tb) |
Cette méthode gère la logique de démontage et est appelée lorsque le flux d'exécution quitte le avec le contexte. Si une exception se produit, alors exc_type , valeur_exc , et exc_tb contiennent respectivement le type d'exception, la valeur et les informations de traçabilité. |
Quand le avec
l'instruction s'exécute, elle appelle .__Entrer__()
sur l'objet gestionnaire de contexte pour signaler que vous entrez dans un nouveau contexte d'exécution. Si vous fournissez une variable cible avec le comme
spécificateur, puis la valeur de retour de .__Entrer__()
est affecté à cette variable.
Lorsque le flux d'exécution quitte le contexte, .__sortir__()
est appelé. Si aucune exception ne se produit dans le avec
bloc de code, puis les trois derniers arguments à .__sortir__()
sont réglés sur Rien
. Sinon, ils contiennent le type, la valeur et le retraçage associés à l'exception à portée de main.
Si la .__sortir__()
la méthode renvoie Vrai
, alors toute exception qui se produit dans le avec
bloc est avalé et l'exécution continue à l'instruction suivante après avec
. Si .__sortir__()
Retour Faux
, les exceptions sont propagées hors du contexte. C'est également le comportement par défaut lorsque la méthode ne renvoie rien de manière explicite. Vous pouvez tirer parti de cette fonctionnalité pour encapsuler la gestion des exceptions dans le gestionnaire de contexte.
Écrire un exemple de gestionnaire de contexte basé sur les classes
Voici un exemple de gestionnaire de contexte basé sur les classes qui implémente les deux méthodes, .__Entrer__()
et .__sortir__()
. Il montre également comment Python les appelle dans un avec
construction:
>>> classer BonjourContextManager:
... déf __Entrer__(soi):
... imprimer("Entrer dans le contexte...")
... revenir "Bonjour le monde!"
... déf __sortir__(soi, exc_type, valeur_exc, exc_tb):
... imprimer("Quitter le contexte...")
... imprimer(exc_type, valeur_exc, exc_tb, SEP="n")
...
>>> avec BonjourContextManager() comme Bonjour:
... imprimer(Bonjour)
...
Entrer dans le contexte...
Bonjour le monde!
Quitter le contexte...
Rien
Rien
Rien
BonjourContextManager
met en œuvre à la fois .__Entrer__()
et .__sortir__()
. Dans .__Entrer__()
, vous imprimez d'abord un message pour signaler que le flux d'exécution entre dans un nouveau contexte. Ensuite, vous retournez le "Bonjour le monde!"
chaîne. Dans .__sortir__()
, vous imprimez un message pour signaler que le flux d'exécution quitte le contexte. Vous imprimez également le contenu de ses trois arguments.
Quand le avec
l'instruction s'exécute, Python crée une nouvelle instance de BonjourContextManager
et appelle son .__Entrer__()
méthode. Vous le savez parce que vous obtenez Entrer dans le contexte...
imprimé à l'écran.
Noter: Une erreur courante lorsque vous utilisez des gestionnaires de contexte est d'oublier d'appeler l'objet passé au avec
déclaration.
Dans ce cas, l'instruction ne peut pas obtenir le gestionnaire de contexte requis, et vous obtenez un AttributeError
comme ça:
>>> avec BonjourContextManager comme Bonjour:
... imprimer(Bonjour)
...
Traceback (appel le plus récent en dernier) :
Déposer "" , ligne 1, dans
AttributeError: __Entrer__
Le message d'exception n'en dit pas trop et vous pourriez vous sentir confus dans ce genre de situation. Donc, assurez-vous d'appeler l'objet dans le avec
pour fournir le gestionnaire de contexte correspondant.
Ensuite, Python exécute le avec
bloc de code, qui imprime Bonjour
à l'écran. Noter que Bonjour
détient la valeur de retour de .__Entrer__()
.
Lorsque le flux d'exécution quitte le avec
bloc de code, appels Python .__sortir__()
. Vous le savez parce que vous obtenez Quitter le contexte...
imprimé sur votre écran. La dernière ligne de la sortie confirme que les trois arguments à .__sortir__()
sont réglés sur Rien
.
Noter: Une astuce courante lorsque vous ne vous souvenez pas de la signature exacte de .__sortir__()
et n'ont pas besoin d'accéder à ses arguments est d'utiliser *args
et **kwargs
comme dans def __exit__(self, *args, **kwargs):
.
Maintenant, que se passe-t-il si une exception se produit lors de l'exécution du avec
bloquer? Allez-y et écrivez ce qui suit avec
déclaration:
>>> avec BonjourContextManager() comme Bonjour:
... imprimer(Bonjour)
... Bonjour[[[[100]
...
Entrer dans le contexte...
Bonjour le monde!
Quitter le contexte...
index de chaîne hors limites
Traceback (appel le plus récent en dernier) :
Déposer "" , ligne 3, dans
IndexError: index de chaîne hors limites
Dans ce cas, vous essayez de récupérer la valeur à l'index 100
dans la chaîne "Bonjour le monde!"
. Cela soulève un IndexError
, et les arguments de .__sortir__()
sont définis comme suit :
exc_type
is the exception class,IndexError
.exc_value
is the exception instance.exc_tb
is the traceback object.
This behavior is quite useful when you want to encapsulate the exception handling in your context managers.
Handling Exceptions in a Context Manager
As an example of encapsulating exception handling in a context manager, say you expect IndexError
to be the most common exception when you’re working with HelloContextManager
. You might want to handle that exception in the context manager so you don’t have to repeat the exception-handling code in every with
code block. In that case, you can do something like this:
# exc_handling.py
class HelloContextManager:
def __enter__(self):
imprimer("Entering the context...")
return "Hello, World!"
def __exit__(self, exc_type, exc_value, exc_tb):
imprimer("Leaving the context...")
if isinstance(exc_value, IndexError):
# Handle IndexError here...
imprimer(F"An exception occurred in your with block: exc_type")
imprimer(F"Exception message: exc_value")
return True
with HelloContextManager() as Bonjour:
imprimer(Bonjour)
Bonjour[[[[100]
imprimer("Continue normally from here...")
Dans .__exit__()
, you check if exc_value
is an instance of IndexError
. If so, then you print a couple of informative messages and finally return with True
. Returning a truthy value makes it possible to swallow the exception and continue the normal execution after the with
code block.
In this example, if no IndexError
occurs, then the method returns None
and the exception propagates out. However, if you want to be more explicit, then you can return False
from outside the if
block.
If you run exc_handling.py
from your command line, then you get the following output:
$ python exc_handling.py
Entering the context...
Hello, World!
Leaving the context...
An exception occurred in your with block:
Exception message: string index out of range
Continue normally from here...
HelloContextManager
is now able to handle IndexError
exceptions that occur in the with
code block. Since you return True
when an IndexError
occurs, the flow of execution continues in the next line, right after exiting the with
code block.
Opening Files for Writing: First Version
Now that you know how to implement the context management protocol, you can get a sense of what this would look like by coding a practical example. Here’s how you can take advantage of open()
to create a context manager that opens files for writing:
# writable.py
class WritableFile:
def __init__(self, file_path):
self.file_path = file_path
def __enter__(self):
self.file_obj = open(self.file_path, mode="w")
return self.file_obj
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file_obj:
self.file_obj.close()
WritableFile
implements the context management protocol and supports the with
statement, just like the original open()
does, but it always opens the file for writing using the "w"
mode. Here’s how you can use your new context manager:
>>> de writable import WritableFile
>>> with WritableFile("hello.txt") as déposer:
... déposer.write("Hello, World!")
...
After running this code, your hello.txt
file contains the "Hello, World!"
chaîne. As an exercise, you can write a complementary context manager that opens files for reading, but using pathlib
functionalities. Go ahead and give it a shot!
Redirecting the Standard Output
A subtle detail to consider when you’re writing your own context managers is that sometimes you don’t have a useful object to return from .__enter__()
and therefore to assign to the with
target variable. In those cases, you can return None
explicitly or you can just rely on Python’s implicit return value, which is None
as well.
For example, say you need to temporarily redirect the standard output, sys.stdout
, to a given file on your disk. To do this, you can create a context manager like this:
# redirect.py
import sys
class RedirectedStdout:
def __init__(self, new_output):
self.new_output = new_output
def __enter__(self):
self.saved_output = sys.stdout
sys.stdout = self.new_output
def __exit__(self, exc_type, exc_val, exc_tb):
sys.stdout = self.saved_output
This context manager takes a file object through its constructor. Dans .__enter__()
, you reassign the standard output, sys.stdout
, to an instance attribute to avoid losing the reference to it. Then you reassign the standard output to point to the file on your disk. Dans .__exit__()
, you just restore the standard output to its original value.
To use RedirectedStdout
, you can do something like this:
>>> de redirect import RedirectedStdout
>>> with open("hello.txt", "w") as déposer:
... with RedirectedStdout(déposer):
... imprimer("Hello, World!")
... imprimer("Back to the standard output...")
...
Back to the standard output...
The outer with
statement in this example provides the file object that you’re going to use as your new output, hello.txt
. The inner with
temporarily redirects the standard output to hello.txt
, so the first call to print()
writes directly to that file instead of printing "Hello, World!"
on your screen. Note that when you leave the inner with
code block, the standard output goes back to its original value.
RedirectedStdout
is a quick example of a context manager that doesn’t have a useful value to return from .__enter__()
. However, if you’re only redirecting the print()
output, you can get the same functionality without the need for coding a context manager. You just need to provide a déposer
argument to print()
like this:
>>> with open("hello.txt", "w") as déposer:
... imprimer("Hello, World!", déposer=déposer)
...
In this examples, print()
takes your hello.txt
file as an argument. This causes print()
to write directly into the physical file on your disk instead of printing "Hello, World!"
to your screen.
Measuring Execution Time
Just like every other class, a context manager can encapsulate some internal state. The following example shows how to create a stateful context manager to measure the execution time of a given code block or function:
# timing.py
de time import perf_counter
class Timer:
def __enter__(self):
self.démarrer = perf_counter()
self.end = 0.0
return lambda: self.end - self.démarrer
def __exit__(self, *args):
self.end = perf_counter()
When you use Timer
in a with
statement, .__enter__()
gets called. This method uses time.perf_counter()
to get the time at the beginning of the with
code block and stores it in .start
. It also initializes .end
and returns a lambda
function that computes a time delta. In this case, .start
holds the initial state or time measurement.
Une fois la with
block ends, .__exit__()
gets called. The method gets the time at the end of the block and updates the value of .end
so that the lambda
function can compute the time required to run the with
code block.
Here’s how you can use this context manager in your code:
>>> de time import sleep
>>> de timing import Timer
>>> with Timer() as timer:
... # Time-consuming code goes here...
... sleep(0.5)
...
>>> timer()
0.5005456680000862
Avec Timer
, you can measure the execution time of any piece of code. In this example, timer
holds an instance of the lambda
function that computes the time delta, so you need to call timer()
to get the final result.
Creating Function-Based Context Managers
Python’s generator functions and the contextlib.contextmanager
decorator provide an alternative and convenient way to implement the context management protocol. If you decorate an appropriately coded generator function with @contextmanager
, then you get a function-based context manager that automatically provides both required methods, .__enter__()
et .__exit__()
. This can make your life more pleasant by saving you some boilerplate code.
The general pattern to create a context manager using @contextmanager
along with a generator function goes like this:
>>> de contextlib import contextmanager
>>> @contextmanager
... def hello_context_manager():
... imprimer("Entering the context...")
... yield "Hello, World!"
... imprimer("Leaving the context...")
...
>>> with hello_context_manager() as Bonjour:
... imprimer(Bonjour)
...
Entering the context...
Hello, World!
Leaving the context...
In this example, you can identify two visible sections in hello_context_manager()
. Before the yield
statement, you have the setup section. There, you can place the code that acquires the managed resources. Everything before the yield
runs when the flow of execution enters the context.
After the yield
statement, you have the teardown section, in which you can release the resources and do the cleanup. The code after yield
runs at the end of the with
block. le yield
statement itself provides the object that will be assigned to the with
target variable.
This implementation and the one that uses the context management protocol are practically equivalent. Depending on which one you find more readable, you might prefer one over the other. A downside of the function-based implementation is that it requires an understanding of advanced Python topics, such as decorators and generators.
le @contextmanager
decorator reduces the boilerplate required to create a context manager. Instead of writing a whole class with .__enter__()
et .__exit__()
methods, you just need to implement a generator function with a single yield
that produces whatever you want .__enter__()
to return.
Opening Files for Writing: Second Version
You can use the @contextmanager
to reimplement your WritableFile
context manager. Here’s what rewriting it with this technique looks like:
>>> de contextlib import contextmanager
>>> @contextmanager
... def writable_file(file_path):
... déposer = open(file_path, mode="w")
... try:
... yield déposer
... finally:
... déposer.close()
...
>>> with writable_file("hello.txt") as déposer:
... déposer.write("Hello, World!")
...
In this case, writable_file()
is a generator function that opens déposer
for writing. Then it temporarily suspends its own execution and yields the resource so with
can bind it to its target variable. When the flow of execution leaves the with
code block, the function continues to execute and closes déposer
correctly.
Mocking the Time
As a final example of how to create custom context managers with @contextmanager
, say you’re testing a piece of code that works with time measurements. The code uses time.time()
to get the current time measurement and do some further computations. Since time measurements vary, you decide to mock time.time()
so you can test your code.
Here’s a function-based context manager that can help you do that:
>>> de contextlib import contextmanager
>>> de time import time
>>> @contextmanager
... def mock_time():
... global time
... saved_time = time
... time = lambda: 42
... yield
... time = saved_time
...
>>> with mock_time():
... imprimer(F"Mocked time: time()")
...
Mocked time: 42
>>> # Back to normal time
>>> time()
1616075222.4410584
Inside mock_time()
, you use a global
statement to signal that you’re going to modify the global name time
. Then you save the original time()
function object in saved_time
so you can safely restore it later. The next step is to monkey patch time()
using a lambda
function that always returns the same value, 42
.
The bare yield
statement specifies that this context manager doesn’t have a useful object to send back to the with
target variable for later use. Après yield
, you reset the global time
to its original content.
When the execution enters the with
block, any calls to time()
return 42
. Once you leave the with
code block, calls to time()
return the expected current time. That’s it! Now you can test your time-related code.
Writing Good APIs With Context Managers
Context managers are quite flexible, and if you use the with
statement creatively, then you can define convenient APIs for your classes, modules, and packages.
For example, what if the resource you wanted to manage is the text indentation level in some kind of report generator application? In that case, you could write code like this:
with Indenter() as indent:
indent.imprimer("hi!")
with indent:
indent.imprimer("hello")
with indent:
indent.imprimer("bonjour")
indent.imprimer("hey")
This almost reads like a domain-specific language (DSL) for indenting text. Also, notice how this code enters and leaves the same context manager multiple times to switch between different indentation levels. Running this code snippet leads to the following output and prints neatly formatted text:
How would you implement a context manager to support this functionality? This could be a great exercise to wrap your head around how context managers work. So, before you check out the implementation below, you might take some time and try to solve this by yourself as a learning exercise.
Ready? Here’s how you might implement this functionality using a context manager class:
class Indenter:
def __init__(self):
self.level = -1
def __enter__(self):
self.level += 1
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self.level -= 1
def imprimer(self, text):
imprimer(" " * self.level + text)
Here, .__enter__()
increments .level
par 1
every time the flow of execution enters the context. The method also returns the current instance, self
. Dans .__exit__()
, you decrease .level
so the printed text steps back one indentation level every time you exit the context.
The key point in this example is that returning self
de .__enter__()
allows you to reuse the same context manager across several nested with
statements. This changes the text indentation level every time you enter and leave a given context.
A good exercise for you at this point would be to write a function-based version of this context manager. Go ahead and give it a try!
Creating an Asynchronous Context Manager
To create an asynchronous context manager, you need to define the .__aenter__()
et .__aexit__()
methods. The script below is a reimplementation of the original script site_checker_v0.py
you saw before, but this time you provide a custom asynchronous context manager to wrap the session creation and closing functionalities:
# site_checker_v1.py
import aiohttp
import asyncio
class AsyncSession:
def __init__(self, url):
self._url = url
async def __aenter__(self):
self.session = aiohttp.ClientSession()
réponse = await self.session.get(self._url)
return réponse
async def __aexit__(self, exc_type, exc_value, exc_tb):
await self.session.close()
async def check(url):
async with AsyncSession(url) as réponse:
imprimer(F"url: status -> réponse.status")
html = await réponse.text()
imprimer(F"url: type -> html[:[:[:[:17].strip()")
async def main():
await asyncio.gather(
check("https://realpython.com"),
check("https://pycoders.com"),
)
asyncio.run(main())
This script works similar to its previous version, site_checker_v0.py
. The main difference is that, in this example, you extract the logic of the original outer async with
statement and encapsulate it in AsyncSession
.
Dans .__aenter__()
, you create an aiohttp.ClientSession()
, await the .get()
response, and finally return the response itself. Dans .__aexit__()
, you close the session, which corresponds to the teardown logic in this specific case. Note that .__aenter__()
et .__aexit__()
must return awaitable objects. In other words, you must define them with async def
, which returns a coroutine object that is awaitable by definition.
If you run the script from your command line, then you get an output similar to this:
$ python site_checker_v1.py
https://realpython.com: status -> 200
https://pycoders.com: status -> 200
https://realpython.com: type ->
https://pycoders.com: type ->
Génial! Your script works just like its first version. It sends GET
requests to both sites concurrently and processes the corresponding responses.
Finally, a common practice when you’re writing asynchronous context managers is to implement the four special methods:
.__aenter__()
.__aexit__()
.__enter__()
.__exit__()
This makes your context manager usable with both variations of with
.
Conclusion
The Python with
déclaration is a powerful tool when it comes to managing external resources in your programs. Its use cases, however, aren’t limited to resource management. You can use the with
statement along with existing and custom context managers to handle the setup and teardown phases of a given process or operation.
The underlying context management protocol allows you to create custom context managers and factor out the setup and teardown logic so you can reuse them in your code.
In this tutorial, you learned:
- What the Python
with
déclaration is for and how to use it - What the context management protocol is
- How to implement your own context managers
With this knowledge, you’ll write safe, concise, and expressive code. You’ll also avoid resource leaks in your programs.
[ad_2]