De nouvelles fonctionnalités intéressantes à essayer – Real Python

By | octobre 5, 2020

trouver un expert Python

Python 3.9 est là! Des volontaires du monde entier ont travaillé sur des améliorations de Python au cours de la dernière année. Alors que les versions bêta sont disponibles depuis un certain temps, la première version officielle de Python 3.9 est sortie le 5 octobre 2020.

Chaque version de Python inclut des fonctionnalités nouvelles, améliorées et obsolètes, et Python 3.9 n'est pas différent. La documentation donne une liste complète des changements. Ci-dessous, vous examinerez en détail les fonctionnalités les plus intéressantes de la dernière version de Python.

Dans ce didacticiel, vous découvrirez:

  • Accéder et calculer avec fuseaux horaires
  • Fusion et mise à jour dictionnaires effectivement
  • En utilisant décorateurs basé sur expressions
  • Combiner conseils de saisie et autres annotations

Pour essayer vous-même les nouvelles fonctionnalités, vous devez avoir installé Python 3.9. Vous pouvez le télécharger et l'installer à partir de la page d'accueil de Python. Vous pouvez également l'essayer en utilisant l'image officielle de Docker. Consultez Exécuter les versions de Python dans Docker: Comment essayer la dernière version de Python pour plus de détails.

Prise en charge appropriée du fuseau horaire

Python a un support étendu pour travailler avec des dates et des heures via le datetime module dans la bibliothèque standard. Cependant, le support pour travailler avec les fuseaux horaires a fait quelque peu défaut. Jusqu'à présent, la méthode recommandée pour travailler avec les fuseaux horaires était d'utiliser des bibliothèques tierces telles que dateutil.

Le plus grand défi pour travailler avec des fuseaux horaires en Python simple est que vous avez dû implémenter vous-même des règles de fuseaux horaires. UNE datetime prend en charge la définition des fuseaux horaires, mais seul UTC est immédiatement disponible. D'autres fuseaux horaires doivent être implémentés en plus du résumé tzinfo classe de base.

Accéder aux fuseaux horaires

Vous pouvez obtenir un horodatage UTC à partir du datetime bibliothèque comme celle-ci:

>>>

>>> de datetime importer datetime, fuseau horaire

>>> datetime.maintenant(tz=fuseau horaire.UTC)
datetime.datetime (2020, 9, 8, 15, 4, 15, 361413, tzinfo = datetime.timezone.utc)

Notez que l'horodatage résultant est conscient du fuseau horaire. Il a un fuseau horaire associé tel que spécifié par tzinfo. Les horodatages sans aucune information de fuseau horaire sont appelés naïve.

Paul Ganssle a été le mainteneur de dateutil pendant des années. Il a rejoint les développeurs principaux de Python en 2019 et a aidé à ajouter un nouveau zoneinfo bibliothèque standard qui rend le travail avec les fuseaux horaires beaucoup plus pratique.

zoneinfo donne accès à la base de données des fuseaux horaires de l'IANA (Internet Assigned Numbers Authority). L’IANA met à jour sa base de données plusieurs fois par an, et c’est la source la plus fiable pour les informations sur les fuseaux horaires.

En utilisant zoneinfo, vous pouvez obtenir un objet décrivant n'importe quel fuseau horaire de la base de données:

>>>

>>> de zoneinfo importer ZoneInfo
>>> ZoneInfo("Amérique / Vancouver")
zoneinfo.ZoneInfo (clé = 'Amérique / Vancouver')

Vous accédez à un fuseau horaire à l'aide de l'une des nombreuses touches. Dans ce cas, vous utilisez "Amérique / Vancouver".

Vous pouvez créer des horodatages tenant compte des fuseaux horaires à l’aide de tz ou tzinfo arguments à datetime les fonctions:

>>>

>>> de datetime importer datetime
>>> de zoneinfo importer ZoneInfo
>>> datetime.maintenant(tz=ZoneInfo("Europe / Oslo"))
datetime.datetime (2020, 9, 8, 17, 12, 0, 939001,
                                                                        tzinfo = zoneinfo.ZoneInfo (clé = 'Europe / Oslo'))

>>> datetime(2020, dix, 5, 3, 9, tzinfo=ZoneInfo("Amérique / Vancouver"))
datetime.datetime (2020, 10, 5, 3, 9,
                                                                        tzinfo = zoneinfo.ZoneInfo (clé = 'Amérique / Vancouver'))

L'enregistrement du fuseau horaire avec l'horodatage est idéal pour la tenue de registres. Cela facilite également la conversion entre les fuseaux horaires:

>>>

>>> de datetime importer datetime
>>> de zoneinfo importer ZoneInfo
>>> Libération = datetime(2020, dix, 5, 3, 9, tzinfo=ZoneInfo("Amérique / Vancouver"))
>>> Libération.astimezone(ZoneInfo("Europe / Oslo"))
datetime.datetime (2020, 10, 5, 12, 9,
                                                                        tzinfo = zoneinfo.ZoneInfo (clé = 'Europe / Oslo'))

Notez que l'heure à Oslo est neuf heures plus tard qu'à Vancouver.

Enquête sur les fuseaux horaires

La base de données des fuseaux horaires de l'IANA est assez massive. Vous pouvez lister tous les fuseaux horaires disponibles en utilisant zoneinfo.available_timezones ():

>>>

>>> importer zoneinfo
>>> zoneinfo.available_timezones()
'America / St_Lucia', 'SystemV / MST7', 'Asia / Aqtau', 'EST', ... 'Asia / Beirut'

>>> len(zoneinfo.available_timezones())
609

Le nombre de fuseaux horaires dans la base de données peut varier avec votre installation. Dans cet exemple, vous pouvez voir qu'il y a 609 noms de fuseau horaire répertoriés. Chacun de ces fuseaux horaires documente les changements historiques qui se sont produits et vous pouvez les examiner de plus près.

Kiritimati, également connue sous le nom d'île Christmas, se trouve actuellement dans le fuseau horaire le plus occidental du monde, UTC + 14. Cela n’a pas toujours été le cas. Avant 1995, l'île se trouvait de l'autre côté de la ligne de date internationale, en UTC-10. Afin de traverser la ligne de date, Kiritimati a complètement sauté le 31 décembre 1994.

Vous pouvez voir comment cela s'est produit en regardant de plus près "Pacifique / Kiritimati" objet de fuseau horaire:

>>>

>>> de datetime importer datetime, timedelta
>>> de zoneinfo importer ZoneInfo
>>> heure = timedelta(heures=1)
>>> tz_kiritimati = ZoneInfo("Pacifique / Kiritimati")
>>> ts = datetime(1994, 12, 31, 9, 0, tzinfo=ZoneInfo("UTC"))

>>> ts.astimezone(tz_kiritimati)
datetime.datetime (1994, 12, 30, 23, 0,
                                                                        tzinfo = zoneinfo.ZoneInfo (clé = 'Pacific / Kiritimati'))

>>> (ts + 1 * heure).astimezone(tz_kiritimati)
datetime.datetime (1995, 1, 1, 0, 0,
                                                                        tzinfo = zoneinfo.ZoneInfo (clé = 'Pacific / Kiritimati'))

La nouvelle année a commencé une heure après l'horloge du 30 décembre 1994 à 23h00 sur Kiritimati. Le 31 décembre 1994, jamais arrivé!

Vous pouvez également voir que le décalage par rapport à UTC a changé:

>>>

>>> tz_kiritimati.utcoffset(datetime(1994, 12, 30)) / heure
-10,0

>>> tz_kiritimati.utcoffset(datetime(1995, 1, 1)) / heure
14,0

.utcoffset () renvoie un timedelta. Le moyen le plus efficace de calculer combien d'heures sont représentées par un timedelta est de le diviser par un timedelta représentant une heure.

Il existe de nombreuses autres histoires étranges sur les fuseaux horaires. Paul Ganssle en couvre certains dans sa présentation PyCon 2019, Travailler avec les fuseaux horaires: tout ce que vous souhaitez que vous n’ayez pas besoin de savoir. Voyez si vous pouvez trouver des traces de l'un des autres dans la base de données de fuseaux horaires.

Utilisation des meilleures pratiques

Travailler avec des fuseaux horaires peut être délicat. Cependant, avec la disponibilité de zoneinfo dans la bibliothèque standard, c'est devenu un peu plus facile. Voici quelques suggestions à garder à l'esprit lorsque vous travaillez avec des dates et des heures:

  • Temps civil comme l'heure d'une réunion, d'un départ de train ou d'un concert, il est préférable de stocker dans leur fuseau horaire d'origine. Vous pouvez souvent le faire en stockant un horodatage naïf avec la clé IANA du fuseau horaire. Un exemple d'heure civile stockée sous forme de chaîne serait "2020-10-05T14: 00: 00, Europe / Oslo". Avoir des informations sur le fuseau horaire garantit que vous pouvez toujours récupérer les informations, même si les fuseaux horaires eux-mêmes changent.

  • Horodatage représentent des moments spécifiques dans le temps et enregistrent généralement un ordre d'événements. Les journaux informatiques en sont un exemple. Vous ne voulez pas que vos journaux soient mélangés simplement parce que votre fuseau horaire passe de l'heure d'été à l'heure standard. Habituellement, vous stockez ces types d'horodatages en tant que datetimes naïfs en UTC.

Étant donné que la base de données de fuseaux horaires IANA est mise à jour en permanence, vous devez être conscient de garder votre base de données de fuseaux horaires locaux synchronisée. Ceci est particulièrement important si vous exécutez des applications sensibles aux fuseaux horaires.

Sur Mac et Linux, vous pouvez généralement faire confiance à votre système pour maintenir la base de données locale à jour. Si vous comptez sur le tzdata package, alors n'oubliez pas de le mettre à jour de temps en temps. En particulier, vous ne devriez pas le laisser épinglé sur une version particulière pendant des années.

Des noms comme "Amérique / Vancouver" vous donner un accès sans ambiguïté à un fuseau horaire donné. Cependant, lorsque vous communiquez à vos utilisateurs des dates et heures tenant compte des fuseaux horaires, il est préférable d’utiliser des noms de fuseaux horaires normaux. Ceux-ci sont disponibles en .tzname () sur un objet de fuseau horaire:

>>>

>>> de datetime importer datetime
>>> de zoneinfo importer ZoneInfo
>>> tz = ZoneInfo("Amérique / Vancouver")
>>> Libération = datetime(2020, dix, 5, 3, 9, tzinfo=tz)
>>> F"Date de sortie: Libération:% b% d,% Y à% H:% M tz.tzname(Libération)"
'Date de sortie: 05 octobre 2020 à 03h09 PDT'

Vous devez fournir un horodatage pour .tzname (). Ceci est nécessaire car le nom du fuseau horaire peut changer avec le temps, comme pour l'heure d'été:

>>>

>>> tz.tzname(datetime(2021, 1, 28))
'TVP'

En hiver, Vancouver est à l’heure normale du Pacifique (PST), tandis qu’en été, il est à l’heure avancée du Pacifique (PDT).

zoneinfo est disponible dans la bibliothèque standard uniquement pour Python 3.9 et versions ultérieures. Cependant, si vous utilisez des versions antérieures de Python, vous pouvez toujours profiter de zoneinfo. Un backport est disponible sur PyPI et peut être installé avec pépin:

$ python -m pip installer backports.zoneinfo

Vous pouvez ensuite utiliser l'idiome suivant lors de l'importation zoneinfo:

essayer:
    importer zoneinfo
sauf ImportError:
    de backports importer zoneinfo

Cela rend votre programme compatible avec toutes les versions de Python à partir de la version 3.6. Voir PEP 615 pour plus de détails sur zoneinfo.

Mise à jour plus simple des dictionnaires

Les dictionnaires sont l'une des structures de données fondamentales de Python. Ils sont utilisés partout dans la langue et ont été assez optimisés au fil du temps.

Il existe plusieurs façons de fusionner deux dictionnaires. Cependant, la syntaxe est un peu cryptique ou lourde:

>>>

>>> pycon = 2016: «Portland», 2018: «Cleveland»
>>> europython = 2017: "Rimini", 2018: "Edinbourg", 2019: "Bâle"

>>> **pycon, **europython
2016: «Portland», 2018: «Édimbourg», 2017: «Rimini», 2019: «Bâle»

>>> fusionné = pycon.copie()
>>> pour clé, valeur dans europython.articles():
...     fusionné[[[[clé] = valeur
...
>>> fusionné
2016: «Portland», 2018: «Édimbourg», 2017: «Rimini», 2019: «Bâle»

Ces deux méthodes fusionnent les dictionnaires sans modifier les données d'origine. Notez que «Cleveland» a été écrasé par "Edinbourg" dans fusionné. Vous pouvez également mettre à jour un dictionnaire sur place:

>>>

>>> pycon.mise à jour(europython)
>>> pycon
2016: «Portland», 2018: «Édimbourg», 2017: «Rimini», 2019: «Bâle»

Cela change cependant votre dictionnaire d'origine. Souviens-toi que .mise à jour() ne renvoie pas le dictionnaire mis à jour, donc des tentatives intelligentes d'utilisation .mise à jour() tout en laissant les données d'origine intactes ne fonctionne pas si bien:

>>>

>>> pycon = 2016: «Portland», 2018: «Cleveland»
>>> europython = 2017: "Rimini", 2018: "Edinbourg", 2019: "Bâle"

>>> fusionné = pycon.copie().mise à jour(europython)  # Ne marche pas
>>> impression(fusionné)
Aucun

Notez que fusionné est Aucun, et tandis que les deux dictionnaires ont été fusionnés, ce résultat a été rejeté. Vous pouvez utiliser l'opérateur morse (: =) introduit dans Python 3.8 pour que cela fonctionne:

>>>

>>> (fusionné : = pycon.copie()).mise à jour(europython)
>>> fusionné
2016: «Portland», 2018: «Édimbourg», 2017: «Rimini», 2019: «Bâle»

Pourtant, ce n'est pas une solution particulièrement lisible ou satisfaisante.

Basée sur PEP 584, la nouvelle version de Python introduit deux nouveaux opérateurs pour les dictionnaires: syndicat (|) et union en place (| =). Vous pouvez utiliser | pour fusionner deux dictionnaires, tandis que | = mettra à jour un dictionnaire en place:

>>>

>>> pycon = 2016: «Portland», 2018: «Cleveland»
>>> europython = 2017: "Rimini", 2018: "Edinbourg", 2019: "Bâle"

>>> pycon | europython
2016: «Portland», 2018: «Édimbourg», 2017: «Rimini», 2019: «Bâle»

>>> pycon | = europython
>>> pycon
2016: «Portland», 2018: «Édimbourg», 2017: «Rimini», 2019: «Bâle»

Si d1 et d2 sont deux dictionnaires, alors d1 | d2 fait la même chose que ** d1, ** d2. le | L'opérateur est utilisé pour calculer l'union des ensembles, de sorte que la notation peut déjà vous être familière.

Un avantage d'utiliser | est qu'il fonctionne sur différents types de type dictionnaire et conserve le type tout au long de la fusion:

>>>

>>> de collections importer defaultdict
>>> L'Europe  = defaultdict(lambda: "", "Norvège": "Oslo", "Espagne": "Madrid")
>>> Afrique = defaultdict(lambda: "", "Egypte": "Caire", "Zimbabwe": «Harare»)

>>> L'Europe  | Afrique
defaultdict (<fonction  à 0x7f0cb42a6700>,
        "Norvège": "Oslo", "Espagne": "Madrid", "Egypte": "Le Caire", "Zimbabwe": "Harare")

>>> **L'Europe , **Afrique
"Norvège": "Oslo", "Espagne": "Madrid", "Egypte": "Le Caire", "Zimbabwe": "Harare"

Vous pouvez utiliser un defaultdict lorsque vous souhaitez gérer efficacement les clés manquantes. Notez que | préserve le defaultdict, tandis que ** europe, ** afrique ne fait pas.

Il y a des similitudes entre la façon dont | fonctionne pour les dictionnaires et comment + fonctionne pour les listes. En fait, le + L'opérateur a été initialement proposé de fusionner les dictionnaires. Cette correspondance devient encore plus évidente lorsque vous regardez l'opérateur en place.

L'utilisation de base de | = consiste à mettre à jour un dictionnaire sur place, similaire à .mise à jour():

>>>

>>> bibliothèques = 
...     "collections": "Types de données de conteneur",
...     "math": "Fonctions mathématiques",
... 
>>> bibliothèques | = "zoneinfo": "Prise en charge du fuseau horaire IANA"
>>> bibliothèques
'collections': 'Types de données conteneurs', 'math': 'Fonctions mathématiques',
    'zoneinfo': 'Prise en charge du fuseau horaire IANA'

Lorsque vous fusionnez des dictionnaires avec |, les deux dictionnaires doivent être d'un type de dictionnaire approprié. D'autre part, l'opérateur en place (| =) est heureux de travailler avec n'importe quelle structure de données de type dictionnaire:

>>>

>>> bibliothèques | = [([([([("graphlib", "Fonctionnalité pour les structures de type graphique")]
>>> bibliothèques
'collections': 'Types de données conteneurs', 'math': 'Fonctions mathématiques',
    'zoneinfo': 'Prise en charge du fuseau horaire IANA',
    'graphlib': 'Fonctionnalité pour les structures de type graphique'

Dans cet exemple, vous mettez à jour bibliothèques à partir d'une liste de 2 tuples. Lorsqu'il y a des clés qui se chevauchent dans deux dictionnaires que vous souhaitez fusionner, la dernière valeur est conservée:

>>>

>>> Asie = "Géorgie": "Tbilissi", "Japon": "Tokyo"
>>> Etats-Unis = "Missouri": «Jefferson City», "Géorgie": "Atlanta"
>>> Asie | Etats-Unis
"Géorgie": "Atlanta", "Japon": "Tokyo", "Missouri": "Jefferson City"

>>> Etats-Unis | Asie
'Missouri': 'Jefferson City', 'Georgia': 'Tbilissi', 'Japan': 'Tokyo'

Dans le premier exemple, "Géorgie" pointe vers "Atlanta" parce que Etats-Unis est le dernier dictionnaire de la fusion. La valeur "Tbilissi" de Asie a été écrasé. Notez que la clé "Géorgie" est toujours le premier dans le dictionnaire résultant, car il s'agit du premier élément de Asie. L'inversion de l'ordre de la fusion modifie à la fois la position et la valeur de "Géorgie".

Les opérateurs | et | = ont été ajoutés non seulement aux dictionnaires réguliers mais également à de nombreuses classes de type dictionnaire, y compris UserDict, ChainMap, OrdonnéDict, defaultdict, WeakKeyDictionary, WeakValueDictionary, _Environ, et MappingProxyType. Ils ont ne pas été ajouté aux classes de base abstraites Cartographie ou MutableMapping. le Compteur conteneur utilise déjà | pour trouver les nombres maximaux. Cela n’a pas changé.

Vous pouvez changer le comportement de | et | = en mettant en œuvre .__ou__() et .__ ior __ (), respectivement. Voir PEP 584 pour plus de détails.

Des décorateurs plus flexibles

Traditionnellement, un décorateur devait être un objet nommé et appelable, généralement une fonction ou une classe. PEP 614 permet aux décorateurs d'être n'importe quelle expression appelable.

La plupart des gens ne considèrent pas l'ancienne syntaxe du décorateur comme limitative. En effet, assouplir la grammaire des décorateurs aide principalement dans quelques cas d'utilisation de niche. Selon le PEP, le cas d'utilisation motivant concerne les callbacks dans les frameworks GUI.

PyQT utilise des signaux et des slots pour connecter des widgets avec des rappels. Conceptuellement, vous pouvez faire quelque chose comme ce qui suit pour connecter le cliqué signal de bouton à la fente dis bonjour():

bouton = QPushButton("Dis bonjour")

@bouton.cliqué.relier
def dis bonjour():
    message.Définir le texte("Bonjour le monde!")

Cela affichera le texte Bonjour le monde! lorsque vous cliquez sur le bouton Dis bonjour.

Supposons maintenant que vous ayez plusieurs boutons, et pour en garder une trace, vous les stockez dans un dictionnaire:

boutons = 
  "Bonjour": QPushButton("Dis bonjour"),
  "partir": QPushButton("Au revoir"),
  "calculer": QPushButton("3 + 9 = 12"),

Tout va bien. Cependant, cela crée un défi pour vous si vous souhaitez utiliser un décorateur pour connecter un bouton à un emplacement. Dans les versions antérieures de Python, vous ne pouviez pas accéder aux éléments en utilisant des crochets lors de l'utilisation d'un décorateur. Vous devrez faire quelque chose comme ce qui suit:

bonjour_bouton = boutons[[[["Bonjour"]

@hello_button.cliqué.relier
def dis bonjour():
    message.Définir le texte("Bonjour le monde!")

Dans Python 3.9, ces restrictions sont levées et vous pouvez désormais utiliser n'importe quelle expression, y compris une qui accède aux éléments d'un dictionnaire:

@boutons[[[["Bonjour"].cliqué.relier
def dis bonjour():
    message.Définir le texte("Bonjour le monde!")

Bien que ce ne soit pas un grand changement, cela vous permet d'écrire du code plus propre dans quelques cas. La syntaxe étendue facilite également le choix dynamique des décorateurs lors de l'exécution. Dites que vous disposez des décorateurs suivants:

# story.py
importer functools

def Ordinaire(func):
    revenir func

def crier(func):
    @functools.enveloppements(func)
    def shout_decorator(*args, **kwargs):
        revenir func(*args, **kwargs).plus haut()

    revenir shout_decorator

def chuchotement(func):
    @functools.enveloppements(func)
    def whisper_decorator(*args, **kwargs):
        revenir func(*args, **kwargs).inférieur()

    revenir whisper_decorator

le @Ordinaire le décorateur ne modifie pas du tout la fonction d'origine, tandis que @crier et @chuchotement rendre le texte renvoyé par une fonction en majuscules ou en minuscules. Vous pouvez ensuite stocker des références à ces décorateurs dans un dictionnaire et les mettre à disposition de l'utilisateur:

# story.py (suite)
DÉCORATEURS = "Ordinaire": Ordinaire, "crier": crier, "chuchotement": chuchotement

voix = contribution(F"Choisissez votre voix (','.joindre(DÉCORATEURS)): ")

@DÉCORATEURS[[[[voix]
def get_story():
    revenir "" "
                                Alice commençait à être très fatiguée d'être assise à côté de sa sœur sur le
                                banque, et de n'avoir rien à faire: une ou deux fois, elle avait jeté un coup d'œil
                                le livre que sa sœur lisait, mais il n'y avait pas d'images ou
                                conversations dedans, "et à quoi sert un livre", pensa Alice
                                "sans photos ni conversations?"
                                "" "

impression(get_story())

Lorsque vous exécutez ce script, il vous sera demandé quel décorateur appliquer à l'histoire. Le texte résultant est ensuite imprimé à l'écran:

$ python3.9 story.py
Choisissez votre voix (normal, crier, chuchoter): crier

                                ALICE COMMENÇAIT À ÊTRE TRÈS LASSÉE DE S'ASSISE PAR SA SOEUR SUR LE
                                BANQUE, ET DE NE RIEN À FAIRE: UNE OU DEUX FOIS, ELLE AVAIT PISÉ
                                LE LIVRE QUE SA SŒUR LUT LIT, MAIS IL N'A PAS DE PHOTOS OU
                                CONVERSATIONS EN CE MOMENT, "ET QUELLE EST L'UTILISATION D'UN LIVRE", PENSAIT ALICE
                                "SANS IMAGES OU CONVERSATIONS?"

Cet exemple est le même que si @crier avait été appliqué à get_story (). Cependant, ici, il a été appliqué au moment de l'exécution en fonction de votre entrée. Comme pour l'exemple de bouton, vous pouvez obtenir le même effet dans les versions antérieures de Python en utilisant une variable temporaire.

Pour plus d'informations sur les décorateurs, consultez Primer on Python Decorators. Pour plus de détails sur la grammaire détendue, voir PEP 614.

Conseils de type annotés

Les annotations de fonction ont été introduites dans Python 3.0. La syntaxe prend en charge l'ajout de métadonnées arbitraires aux fonctions Python. Voici un exemple d'ajout d'unités à une formule:

# calculator.py

def la vitesse(distance: "pieds", temps: "secondes") -> "miles par heure":
    "" "Calculer la vitesse en tant que distance dans le temps" ""
    fps2mph = 3600 / 5280  # Pieds par seconde en miles par heure
    revenir distance / temps * fps2mph

Dans cet exemple, les annotations sont utilisées uniquement comme documentation pour le lecteur. Vous verrez plus tard comment accéder aux annotations lors de l'exécution.

La PEP 484 a suggéré que les annotations soient utilisées pour les indications de type. Au fur et à mesure que les indices de type gagnent en popularité, ils ont principalement évincé toute autre utilisation des annotations en Python.

Puisqu'il existe plusieurs cas d'utilisation pour les annotations en dehors du typage statique, PEP 593 introduit saisie., que vous pouvez utiliser pour combiner des conseils de type avec d'autres informations. Vous pouvez refaire le calculator.py exemple d'en haut comme ceci:

# calculator.py

de dactylographie importer Annoté

def la vitesse(
    distance: Annoté[[[[flotte, "pieds"], temps: Annoté[[[[flotte, "secondes"]
) -> Annoté[[[[flotte, "miles par heure"]:
    "" "Calculer la vitesse en tant que distance dans le temps" ""
    fps2mph = 3600 / 5280  # Pieds par seconde en miles par heure
    revenir distance / temps * fps2mph

Annoté prend au moins deux arguments. Le premier argument est un indice de type régulier et le reste des arguments sont des métadonnées arbitraires. Un vérificateur de type ne se souciera que du premier argument, laissant l'interprétation des métadonnées à vous et à votre application. Un indice de type comme Annoté[float, "feet"] sera traité de la même manière flotte par des vérificateurs de type.

Vous pouvez accéder aux annotations via .__ annotations__ comme d'habitude. Importer la vitesse() de calculator.py:

>>>

>>> de calculatrice importer la vitesse
>>> la vitesse.__annotations__
'distance': saisie. Annoté[float, 'feet'],
    'time': saisie.[float, 'seconds'],
    'return': saisie.[float, 'miles per hour']

Chacune des annotations est disponible dans le dictionnaire. Les métadonnées que vous avez définies avec Annoté sont stockés dans .__ métadonnées__:

>>>

>>> la vitesse.__annotations__[[[["distance"].__metadata__
('pieds',)

>>> var: e.__metadata__[[[[0] pour var, e dans la vitesse.__annotations__.articles()
'distance': 'feet', 'time': 'seconds', 'return': 'miles par heure'

Le dernier exemple sélectionne toutes les unités en lisant le premier élément de métadonnées pour chaque variable. Une autre façon d'accéder aux indications de type au moment de l'exécution consiste à utiliser get_type_hints () du dactylographie module. get_type_hints () ignorera les métadonnées par défaut:

>>>

>>> de dactylographie importer get_type_hints
>>> de calculatrice importer la vitesse
>>> get_type_hints(la vitesse)
'distance': ,
    'temps': ,
    'revenir': 

Cela devrait permettre à la plupart des programmes qui accèdent aux indications de type au moment de l'exécution de continuer à fonctionner sans changement. Vous pouvez utiliser le nouveau paramètre facultatif include_extras pour demander que les métadonnées soient incluses:

>>>

>>> get_type_hints(la vitesse, include_extras=Vrai)
'distance': saisie. Annoté[float, 'feet'],
    'time': saisie.[float, 'seconds'],
    'return': saisie.[float, 'miles per hour']

En utilisant Annoté pourrait aboutir à un code assez détaillé. Une façon de garder votre code court et lisible est d'utiliser des alias de type. Vous pouvez définir de nouvelles variables représentant des types annotés:

# calculator.py

de dactylographie importer Annoté

Pieds = Annoté[[[[flotte, "pieds"]
Secondes = Annoté[[[[flotte, "secondes"]
Miles par heure = Annoté[[[[flotte, "miles par heure"]

def la vitesse(distance: Pieds, temps: Secondes) -> Miles par heure:
    "" "Calculer la vitesse en tant que distance dans le temps" ""
    fps2mph = 3600 / 5280  # Pieds par seconde en miles par heure
    revenir distance / temps * fps2mph

La configuration des alias de type peut prendre un certain temps, mais ils peuvent rendre votre code assez clair et lisible.

Si vous avez une application dans laquelle vous utilisez de manière intensive les annotations, vous pouvez également envisager de mettre en œuvre un fabrique d'annotations. Ajoutez ce qui suit en haut de calculator.py:

# calculator.py

de dactylographie importer Annoté

classe AnnotationFactory:
    def __init__(soi, type_hint):
        soi.type_hint = type_hint

    def __obtenir l'article__(soi, clé):
        si isinstance(clé, tuple):
            revenir Annoté[([([([(soi.type_hint, ) + clé]
        autre:
            revenir Annoté[[[[soi.type_hint, clé]

    def __repr__(soi):
        revenir F"soi.__classe__.__Nom__(soi.type_hint) "

AnnotationFactory Peut créer Annoté objets avec différentes métadonnées. Vous pouvez utiliser la fabrique d'annotations pour créer des alias plus dynamiques. Mise à jour calculator.py utiliser AnnotationFactory:

# calculator.py (suite)

Flotte = AnnotationFactory(flotte)

def la vitesse(
    distance: Flotte[[[["pieds"], temps: Flotte[[[["secondes"]
) -> Flotte[[[["miles par heure"]:
    "" "Calculer la vitesse en tant que distance dans le temps" ""
    fps2mph = 3600 / 5280  # Pieds par seconde en miles par heure
    revenir distance / temps * fps2mph

Flotte[[[[] représente Annoté[flotte[flotte[float[float], donc cet exemple fonctionne exactement de la même manière que les deux exemples précédents.

Un analyseur Python plus puissant

L'une des fonctionnalités les plus intéressantes de Python 3.9 est celle que vous ne remarquerez pas dans votre vie de codage quotidienne. Un composant fondamental de l'interpréteur Python est l'analyseur. Dans la dernière version, l'analyseur a été réimplémenté.

Depuis sa création, Python a utilisé un analyseur de base LL (1) pour analyser le code source en arbres d'analyse. Vous pouvez penser à un analyseur LL (1) comme un analyseur qui lit un caractère à la fois et comprend comment interpréter le code source sans retour en arrière.

L’un des avantages d’utiliser un analyseur simple est qu’il est assez simple à mettre en œuvre et à raisonner. Un inconvénient est qu'il existe des cas difficiles que vous devez contourner avec des hacks spéciaux.

Dans une série d'articles de blog, Guido van Rossum, le créateur de Python, a étudié les analyseurs PEG (parsing expression grammar). Les analyseurs PEG sont plus puissants que les analyseurs LL (1) et évitent le recours à des hacks spéciaux. À la suite des recherches de Guido, un analyseur PEG a été implémenté dans Python 3.9. Voir PEP 617 pour plus de détails.

L'objectif est que le nouvel analyseur PEG produise le même arbre de syntaxe abstraite (AST) comme l'ancien analyseur LL (1). La dernière version est livrée avec les deux analyseurs. Alors que l'analyseur PEG est la valeur par défaut, vous pouvez exécuter votre programme en utilisant l'ancien analyseur en utilisant le -X oldparser indicateur de ligne de commande:

$ python -X oldparser nom_script.py

Vous pouvez également définir le PYTHONOLDPARSER variable d'environnement.

L'ancien analyseur sera supprimé dans Python 3.10. Cela permettra de nouvelles fonctionnalités sans les limitations d'une grammaire LL (1). Une de ces fonctionnalités actuellement envisagée pour l'inclusion dans Python 3.10 est la correspondance de modèle structurel, comme décrit dans PEP 622.

Avoir les deux analyseurs disponibles est idéal pour valider le nouvel analyseur PEG. Vous pouvez exécuter n'importe quel code sur les deux analyseurs et le comparer au niveau AST. Pendant les tests, toute la bibliothèque standard ainsi que de nombreux packages tiers populaires ont été compilés et comparés.

Vous pouvez également comparer les performances des deux analyseurs. En général, l'analyseur PEG et le LL (1) fonctionnent de la même manière. Sur l'ensemble de la bibliothèque standard, l'analyseur PEG est légèrement plus rapide, mais il utilise également un peu plus de mémoire. En pratique, vous ne devriez remarquer aucun changement de performances, bon ou mauvais, lors de l’utilisation du nouvel analyseur.

Autres fonctionnalités assez cool

Jusqu'à présent, vous avez vu les nouvelles fonctionnalités les plus importantes de Python 3.9. Cependant, chaque nouvelle version de Python inclut également de nombreux petits changements. La documentation officielle comprend une liste de tous ces changements. Dans cette section, vous découvrirez quelques-unes des autres nouvelles fonctionnalités très intéressantes avec lesquelles vous pouvez commencer à jouer.

Préfixe et suffixe de chaîne

Si vous devez supprimer le début ou la fin d'une chaîne, alors .bande() semble pouvoir faire le travail:

>>>

>>> "trois fonctionnalités intéressantes en Python".bande("Python")
«ree cool features i»

Le suffixe "Python" a été supprimé, mais "th" au début de la chaîne. Le comportement réel de .bande() est parfois surprenant et a déclenché de nombreux rapports de bogues. Il est naturel de supposer que .strip ("Python") supprimera la sous-chaîne "Python", mais il supprime les caractères individuels "", "P", "y", "t", "h", "o", et "n" au lieu.

Pour supprimer réellement un suffixe de chaîne, vous pouvez faire quelque chose comme ceci:

>>>

>>> def remove_suffix(texte, suffixe):
...     si texte.se termine par(suffixe):
...         revenir texte[:[:[:[:-len(suffix)]
...     else:
...         return text
...
>>> remove_suffix("three cool features in Python", suffix=" Python")
'three cool features in'

This works better but is a bit cumbersome. This code also has a subtle bug:

>>>

>>> remove_suffix("three cool features in Python", suffix="")
''

If suffix happens to be the empty string, somehow the whole string has been removed. This is because the length of the empty string is 0, so then text[:0] ends up being returned. You can fix this by rephrasing the test to be on suffix and text.endswith(suffix).

In Python 3.9, there are two new string methods that solve this exact use case. You can use .removeprefix() et .removesuffix() to remove the beginning or end of a string, respectively:

>>>

>>> "three cool features in Python".removesuffix(" Python")
'three cool features in'

>>> "three cool features in Python".removeprefix("three ")
'cool features in Python'

>>> "three cool features in Python".removeprefix("Something else")
'three cool features in Python'

Note that if the given prefix or suffix doesn’t match the string, then you get the string back untouched.

.removeprefix() et .removesuffix() remove at most one copy of the affix. If you want to be sure to remove all of them, then you can use a while loop:

>>>

>>> text = "Waikiki"
>>> text.removesuffix("ki")
'Waiki'

>>> while text.endswith("ki"):
...     text = text.removesuffix("ki")
...
>>> text
'Wai'

See PEP 616 for more information about .removeprefix() et .removesuffix().

Type Hint Lists and Dictionaries Directly

It’s usually quite straightforward to add type hints for basic types like str, int, et bool. You annotate with the type directly. This situation is similar with custom types you create yourself:

radius: float = 3.9

class NothingType:
    pass

nothing: NothingType = NothingType()

Generics are a different story. A generic type is typically a container that can be parametrized, such as a list of numbers. For technical reasons, in previous Python versions you haven’t been able to use list[float] ou list(float) as type hints. Instead, you’ve needed to import a different list object from the typing module:

de typing import List

numbers: List[[[[float]

In Python 3.9, this parallel hierarchy is no longer necessary. You can now finally use list for proper type hints as well:

This will make your code easier to write and eliminate the confusion about having both list et List. In the future, using typing.List and similar generics like typing.Dict et typing.Type will be deprecated and the generics will eventually be removed from typing.

If you need to write code that’s compatible with older versions of Python, then you can still take advantage of the new syntax by using the __future__ import that was made available in Python 3.7. In Python 3.7, you’ll usually see something like this:

>>>

>>> numbers: list[[[[float]
Traceback (most recent call last):
  Fichier "", line 1, in 
TypeError: 'type' object is not subscriptable

However, using the __future__ import makes this example work:

>>>

>>> de __future__ import annotations
>>> numbers: list[[[[float]
>>> __annotations__
'numbers': 'list[float]'

This works because the annotation isn’t evaluated at runtime. If you try to evaluate the annotations, then you’ll still experience the TypeError. For more information about this new syntax, see PEP 585.

Topological Sort

Graphs consisting of nodes and edges are useful for representing different kinds of data. For example, when you use pépin to install a package from PyPI, that package may depend on other packages, which may in turn have more dependencies.

This structure can be represented by a graph in which each package is a node and each dependency is represented by an edge:

A graph showing the dependencies of realpython-reader
A graph showing the dependencies of realpython-reader

This graph shows the dependencies of the realpython-reader package. It depends directly on feedparser et html2text, while feedparser in turn depends on sgmllib3k.

Say that you want to install these packages in an order such that all dependencies are always fulfilled. You would then do what’s called a topological sort to find a total order of your dependencies.

Python 3.9 introduces a new module, graphlib, into the standard library to do topological sorting. You can use it to find the total order of a set or to do more advanced scheduling taking into account tasks that can be parallelized. To see an example, you can represent the earlier dependencies in a dictionary:

>>>

>>> dependencies = 
...     "realpython-reader": "feedparser", "html2text",
...     "feedparser": "sgmllib3k",
... 
...

This expresses the dependencies you saw in the figure above. For example, realpython-reader depends on feedparser et html2text. In this case, the specific dependencies of realpython-reader are written as a set: "feedparser", "html2text". You can use any iterable to specify these, including a list.

To calculate a total order of the graph, you can use TopologicalSorter de graphlib:

>>>

>>> de graphlib import TopologicalSorter
>>> ts = TopologicalSorter(dependencies)
>>> list(ts.static_order())
['html2text', 'sgmllib3k', 'feedparser', 'realpython-reader']

The given order suggests that you should first install html2text, then sgmllib3k, then feedparser, and finally realpython-reader.

TopologicalSorter has an extensive API that allows you to add nodes and edges incrementally using .add(). You can also consume the graph iteratively, which is especially useful when scheduling tasks that can be done in parallel. See the documentation for a full example.

Greatest Common Divisor (GCD) and Least Common Multiple (LCM)

The divisors of a number are an important property that has applications in cryptography and other areas. Python has had a function for calculating the greatest common divisor (GCD) of two numbers for a long time:

>>>

>>> import math
>>> math.gcd(49, 14)
7

The GCD of 49 and 14 is 7 because 7 is the largest number that divides both 49 and 14.

The least common multiple (LCM) is related to GCD. The LCM of two numbers is the smallest number that can be divided by both of them. It’s possible to define LCM in terms of GCD:

>>>

>>> def lcm(num1, num2):
...     if num1 == num2 == 0:
...         return 0
...     return num1 * num2 // math.gcd(num1, num2)
...
>>> lcm(49, 14)
98

The least common multiple of 49 and 14 is 98 because 98 is the smallest number that can be divided by both 49 and 14. In Python 3.9, you no longer need to define your own LCM function:

>>>

>>> import math
>>> math.lcm(49, 14)
98

Both math.gcd() et math.lcm() now also support more than two numbers. You can, for instance, calculate the greatest common divisor of 273, 1729, et 6048 like this:

>>>

>>> import math
>>> math.gcd(273, 1729, 6048)
7

Note that math.gcd() et math.lcm() aren’t able to calculate based on lists. However, you can unpack lists into comma-separated arguments:

>>>

>>> import math
>>> numbers = [[[[273, 1729, 6048]
>>> math.gcd(numbers)
Traceback (most recent call last):
  Fichier "", line 1, in 
TypeError: 'list' object cannot be interpreted as an integer

>>> math.gcd(*numbers)
7

In earlier versions of Python, you would need to nest several calls to gcd() or use functools.reduce():

>>>

>>> import math
>>> math.gcd(math.gcd(273, 1729), 6048)
7

>>> import functools
>>> functools.reduce(math.gcd, [[[[273, 1729, 6048])
7

In the latest version of Python, these calculations have gotten more straightforward to write.

New HTTP Status Codes

The IANA coordinates several key Internet infrastructure resources, including the Time Zone Database you saw earlier. Another such resource is the HTTP Status Code Registry. HTTP status codes are available in the http standard library:

>>>

>>> de http import HTTPStatus
>>> HTTPStatus.OK


>>> HTTPStatus.OK.description
'Request fulfilled, document follows'

>>> HTTPStatus(404)


>>> HTTPStatus(404).phrase
'Not Found'

In Python 3.9, the new HTTP status codes 103 (Early Hints) and 425 (Too Early) have been added to http:

>>>

>>> de http import HTTPStatus
>>> HTTPStatus.EARLY_HINTS.value
103

>>> HTTPStatus(425).phrase
'Too Early'

As you can see, you can access the new codes based on both their number and their name.

The Hyper Text Coffee Pot Control Protocol (HTCPCP) was introduced on April 1, 1998, to control, monitor, and diagnose coffee pots. It introduced new methods like BREW while mainly reusing the existing HTTP status codes. One exception was the new 418 (I’m a Teapot) status code meant to prevent the disaster of destroying a good teapot by brewing coffee in it.

The Hyper Text Coffee Pot Control Protocol for Tea Efflux Appliances (HTCPCP-TEA) also included 418 (I’m a Teapot) and the code also found its way into many mainstream HTTP libraries, including requests.

An initiative in 2017 to remove 418 (I’m a Teapot) from major libraries was met with swift pushback. Ultimately, the debate ended with 418 being proposed as a reserved HTTP status code. 418 (I’m a Teapot) has also been added to http:

>>>

>>> de http import HTTPStatus
>>> HTTPStatus(418).phrase
"I'm a Teapot"

>>> HTTPStatus.IM_A_TEAPOT.description
'Server refuses to brew coffee because it is a teapot.'

There are a few places you can see a 418 error in the wild, including on Google.

Removal of Deprecated Compatibility Code

One important milestone for Python in the last year has been the sunsetting of Python 2. Python 2.7 was first released in 2010. On January 1, 2020, official support for Python 2 ended.

Python 2 has served the community well for close to twenty years and is remembered fondly by many. At the same time, being free of the concern of keeping Python 3 somewhat compatible with Python 2 allows the core developers to focus on the continued improvement of Python 3 and to do some house cleaning along the way.

Many functions that were deprecated but kept around for backward compatibility with Python 2 have been removed in Python 3.9. A few more will be removed in Python 3.10. If you’re wondering whether your code uses any of these older features, then try running it in development mode:

$ python -X dev script_name.py

Using development mode will show you more warnings that can help you future-proof your code. See What’s New In Python 3.9 for more information about features being removed.

When Is the Next Version of Python Coming?

One final change in Python 3.9 unrelated to code is described by PEP 602—Annual Release Cycle for Python. Traditionally, new versions of Python have been released about every eighteen months.

Starting with the current version of Python, new versions will be released approximately every twelve months, in October of each year. This brings several advantages, the most evident being a more predictable and consistent release schedule. With annual releases, it’s easier to plan and synchronize with other important developer events like the PyCon US sprints and the annual core sprint.

While releases will happen more frequently going forward, Python won’t become incompatible faster or get new features faster. All releases will be supported for five years after their initial release, so Python 3.9 will receive security fixes until 2025.

With shorter release cycles, new features will be released faster. At the same time, new releases will bring fewer changes, making the update less critical.

Elections for Python’s steering council are held after every Python release. Going forward, this means that there will be annual elections for the five positions in the steering council.

Even though a new version of Python will be published every twelve months, development on a new version starts about seventeen months before its release. This is because no new features are added to a release during its beta testing phase, which lasts for about five months.

In other words, development on the next version of Python, Python 3.10, is already well underway. You can already test the first alpha version of Python 3.10 by running the latest core developers’ Docker image.

The final features of Python 3.10 are still to be decided. However, the version number is somewhat special in that it’s the first Python version with a two-digit minor version. This could cause some issues if, for instance, you have code that compares versions as strings because "3.9" > "3.10". A better solution is to compare versions as tuples: (3, 9) < (3, 10). The package flake8-2020 tests for these and similar issues in your code.

So, Should You Upgrade to Python 3.9?

To start with the evident, if you want to try out any of the cool new features showcased in this tutorial, then you’ll need to use Python 3.9. It’s possible to install the latest version side by side with your current version of Python. The easiest way is to use an environment manager like pyenv ou conda. Even less intrusive would be running the new version through Docker.

When you consider upgrading to Python 3.9, there are really two different questions you should ask yourself:

  1. Should you upgrade your developer or production environment to Python 3.9?
  2. Should you make your project dependent on Python 3.9 so you can take advantage of the new features?

If you have code that’s running smoothly in Python 3.8, then you should experience few problems running the same code in Python 3.9. The main stumbling block will be if you rely on functions that have been deprecated in earlier versions of Python and are now being removed.

The new PEG parser has naturally not been tested as extensively as the old one. If you’re unlucky, you could run into some weird corner-case issues. However, remember that you can switch back to the old parser with a command-line flag.

Altogether, you should be quite safe upgrading your own environment to the latest version of Python as early as is convenient for you. If you want to be a bit more conservative, then you can wait for the first maintenance release, Python 3.9.1.

Whether you can start to really take advantage of the new features in your own code depends a lot on your user base. If your code is being run only in environments that you can control and upgrade to Python 3.9, then there’s no harm in using zoneinfo or the new dictionary merge operators.

However, if you’re distributing a library that’s being used by many others, then it’s better to be more conservative. The last version of Python 3.5 was released in September, and it’s no longer supported. If possible, you should still aim at having your library compatible with Python 3.6 and newer so that as many people as possible can enjoy your efforts.

For details about preparing your code for Python 3.9, see Porting to Python 3.9 in the official documentation.

Conclusion

The release of a new Python version is a big milestone for the community. You may not be able to start using the cool new features immediately, but in a few years Python 3.9 will be as widespread as Python 3.6 is today.

In this tutorial, you’ve seen new features like:

  • le zoneinfo module for dealing with time zones
  • Union operators that can update dictionaries
  • More expressive decorator syntax
  • Annotations that can be used for other things besides type hints

Set aside a few minutes to try out the features that excite you the most, then share your experiences in the comments below!