Annotations de fonctions Python 3

Les annotations de fonction sont une fonctionnalité de Python 3 qui vous permet d’ajouter des métadonnées arbitraires aux arguments de la fonction et à la valeur renvoyée. Ils faisaient partie de la spécification originale de Python 3.0.

Dans ce tutoriel, je vais vous montrer comment tirer parti des annotations de fonctions générales et les combiner avec des décorateurs. Vous apprendrez également le pour et le contre des annotations de fonctions, quand il est approprié de les utiliser et quand il est préférable d'utiliser d'autres mécanismes tels que docstrings et plain decorators..

Annotations de fonction

Les annotations de fonction sont spécifiées dans PEP-3107. La principale motivation était de fournir un moyen standard d'associer des métadonnées à des arguments de fonction et à une valeur renvoyée. De nombreux membres de la communauté ont découvert de nouveaux cas d'utilisation, mais ont utilisé différentes méthodes telles que des décorateurs personnalisés, des formats de docstring personnalisés et l'ajout d'attributs personnalisés à l'objet fonction..

Il est important de comprendre que Python ne bénit pas les annotations avec aucune sémantique. Il fournit simplement un support syntaxique intéressant pour l’association de métadonnées ainsi qu’un moyen facile d’y accéder. De plus, les annotations sont totalement optionnelles.

Jetons un coup d'oeil à un exemple. Voici une fonction foo () cela prend trois arguments appelés a, b et c et affiche leur somme. Notez que foo () ne renvoie rien. Le premier argument une n'est pas annoté. Le deuxième argument b est annoté avec la chaîne 'annotating b', et le troisième argument c est annoté avec le type int. La valeur de retour est annotée avec le type flotte. Notez la syntaxe “->” pour l'annotation de la valeur de retour.

python def foo (a, b: 'annotant b', c: int) -> float: impression (a + b + c)

Les annotations n'ont aucun impact sur l'exécution de la fonction. Appelons foo () deux fois: une fois avec des arguments int et une fois avec des arguments chaîne. Dans les deux cas, foo () fait la bonne chose, et les annotations sont simplement ignorées.

"python foo ('Bonjour', ',', 'Monde!') Bonjour, Monde!

foo (1, 2, 3) 6 "

Arguments par défaut

Les arguments par défaut sont spécifiés après l'annotation:

"python def foo (x: 'un argument dont la valeur par défaut est 5' = 5): print (x)

foo (7) 7

foo () 5 "

Accéder aux annotations de fonctions

L'objet de fonction a un attribut appelé 'annotations'. C'est un mappage qui associe chaque nom d'argument à son annotation. L'annotation de la valeur de retour est associée à la clé 'return', ce qui ne peut entrer en conflit avec aucun nom d'argument, car 'return' est un mot réservé qui ne peut pas servir de nom d'argument. Notez qu'il est possible de passer un argument de mot-clé nommé return à une fonction:

"barre de défilement python (* arguments, ** kwargs: 'le mot-clé arguments dict'): print (kwargs ['return'])

d = 'retour': 4 bar (** d) 4 "

Revenons à notre premier exemple et vérifions ses annotations:

"python def foo (a, b: 'annotant b', c: int) -> float: print (a + b + c)

imprimer (foo.annotations) 'c': , 'b': 'annotant b', 'retour': "

C'est assez simple. Si vous annotez une fonction avec un tableau d'arguments et / ou un tableau d'arguments de mots clés, vous ne pouvez évidemment pas annoter d'arguments individuels..

"python def foo (* args: 'liste des arguments non nommés', ** kwargs: 'dict des arguments nommés'): print (args, kwargs)

imprimer (foo.annotations) 'args': 'liste d'arguments non nommés', 'kwargs': 'dict d'arguments nommés' "

Si vous lisez la section relative à l'accès aux annotations de fonctions dans PEP-3107, vous y accéderez via l'attribut 'func_annotations' de l'objet fonction. Ceci est obsolète à partir de Python 3.2. Ne soyez pas confus. C'est simplement leannotations'attribut.

Que pouvez-vous faire avec des annotations?

C'est la grande question. Les annotations n'ont pas de signification standard ni de sémantique. Il existe plusieurs catégories d'utilisations génériques. Vous pouvez les utiliser comme meilleure documentation et déplacer les arguments et les valeurs de retour en dehors de la docstring. Par exemple, cette fonction:

python def div (a, b): "" "Divise a par b arguments: a - le dividende b - le diviseur (doit être différent de 0) return: résultat de la division de a par b" "" return a / b

Peut être converti en:

python def div (a: 'le dividende', b: 'le diviseur (doit être différent de 0)') -> 'le résultat de la division de a par b': "" "Divise a par b" "" return a / b

Bien que les mêmes informations soient capturées, la version des annotations présente plusieurs avantages:

  1. Si vous renommez un argument, la version de docstring de la documentation peut être obsolète.
  2. Il est plus facile de voir si un argument n'est pas documenté.
  3. Il n'est pas nécessaire de créer un format spécial de documentation d'arguments dans la docstring pour pouvoir être analysé par des outils. le annotations attribut fournit un mécanisme d'accès direct et standard.

Une autre utilisation dont nous parlerons plus tard est la saisie facultative. Python est typé dynamiquement, ce qui signifie que vous pouvez passer n'importe quel objet en tant qu'argument d'une fonction. Mais souvent, les fonctions nécessiteront que les arguments soient d'un type spécifique. Avec les annotations, vous pouvez spécifier le type juste à côté de l'argument de manière très naturelle.

N'oubliez pas que le simple fait de spécifier le type ne le fera pas respecter et qu'un travail supplémentaire (beaucoup de travail) sera nécessaire. Néanmoins, le simple fait de spécifier le type peut rendre l’intention plus lisible que de spécifier le type dans la docstring, et cela peut aider les utilisateurs à comprendre comment appeler la fonction..

Un autre avantage des annotations par rapport à docstring est que vous pouvez attacher différents types de métadonnées sous forme de tuples ou de dict. Encore une fois, vous pouvez aussi le faire avec docstring, mais ce sera basé sur du texte et nécessitera une analyse syntaxique spéciale..

Enfin, vous pouvez attacher de nombreuses métadonnées qui seront utilisées par des outils externes spéciaux ou au moment de l'exécution via des décorateurs. Je vais explorer cette option dans la section suivante.

Annotations multiples

Supposons que vous souhaitiez annoter un argument avec à la fois son type et une chaîne d'aide. C'est très facile avec des annotations. Vous pouvez simplement annoter l'argument avec un dict qui a deux clés: 'type' et 'help'.

"python def div (a: dict (type = float, help =" le dividende ")), b: dict (type = float, help =" le diviseur (doit être différent de 0) ")) -> dict (type = float, help = "résultat de la division de a par b"): "" "Diviser a par b" "" return a / b

print (div.annotations) 'a': 'help': 'le dividende', 'type': float, 'b': 'help': 'le diviseur (doit être différent de 0)', 'type': float , 'return': 'help': 'le résultat de la division de a par b', 'type': float "

Combinaison d'annotations Python et de décorateurs

Les annotations et les décorateurs vont de pair. Pour une bonne introduction aux décorateurs Python, consultez mes deux tutoriels: Plongée profonde dans les décorateurs Python et écrivez vos propres décorateurs Python..

Premièrement, les annotations peuvent être entièrement implémentées en tant que décorateurs. Vous pouvez simplement définir un @annoter faites-le prendre un nom d'argument et une expression Python comme arguments, puis stockez-les dans la fonction cible annotations attribut. Cela peut aussi être fait pour Python 2.

Cependant, le véritable pouvoir des décorateurs est qu’ils peuvent agir sur les annotations. Cela nécessite bien entendu une coordination sur la sémantique des annotations.

Regardons un exemple. Supposons que nous voulions vérifier que les arguments sont dans une certaine plage. L'annotation sera un tuple avec les valeurs minimum et maximum pour chaque argument. Ensuite, nous avons besoin d'un décorateur qui vérifie l'annotation de chaque argument de mot clé, vérifie que la valeur est dans la plage et lève une exception sinon. Commençons par le décorateur:

python def check_range (f): def decoré (* arguments, ** kwargs): pour le nom, plage dans les annotations f .__ <= kwargs[name] <= max_value): msg = 'argument is out of range [ - ]' raise ValueError(msg.format(name, min_value, max_value)) return f(*args, **kwargs) return decorated

Maintenant, définissons notre fonction et décorons-la avec le @check_range décorateurs.

python @check_range def foo (a: (0, 8), b: (5, 9), c: (10, 20)): retourne a * b - c

Appelons foo () avec des arguments différents et voir ce qui se passe. Quand tous les arguments sont dans leur gamme, il n'y a pas de problème.

python foo (a = 4, b = 6, c = 15) 9

Mais si nous fixons c à 100 (en dehors de la plage (10, 20)), une exception est levée:

python foo (a = 4, b = 6, c = 100) ValueError: l'argument c est en dehors de la plage [10 - 20]

Quand faut-il utiliser des décorateurs au lieu d'annotations??

Il y a plusieurs situations où les décorateurs sont meilleurs que les annotations pour attacher des métadonnées.

Un cas évident est de savoir si votre code doit être compatible avec Python 2.

Un autre cas est si vous avez beaucoup de métadonnées. Comme vous l'avez vu précédemment, bien qu'il soit possible d'attacher n'importe quelle quantité de métadonnées en utilisant des dictons comme annotations, il est assez lourd et nuit à la lisibilité..

Enfin, si les métadonnées sont supposées être exploitées par un décorateur spécifique, il peut être préférable d’associer les métadonnées en tant qu’arguments pour le décorateur lui-même..

Annotations dynamiques

Les annotations ne sont qu'un attribut dict d'une fonction.

type python (foo .__ annotations__) dict

Cela signifie que vous pouvez les modifier à la volée pendant l'exécution du programme. Quels sont certains cas d'utilisation? Supposons que vous souhaitiez savoir si une valeur par défaut d'un argument est utilisée. Chaque fois que la fonction est appelée avec la valeur par défaut, vous pouvez incrémenter la valeur d'une annotation. Ou peut-être que vous voulez résumer toutes les valeurs de retour. L'aspect dynamique peut être effectué à l'intérieur de la fonction ou par un décorateur.

"python def add (a, b) -> 0: résultat = a + b add.annotations['return'] + = result résultat résultat

imprimer (ajouter.annotations['retour']) 0

add (3, 4) 7 print (ajouter.annotations['retour']) 7

ajouter (5, 5) 10 imprimer (ajouter.annotations['retour']) 17 "

Conclusion

Les annotations de fonctions sont polyvalentes et passionnantes. Ils ont le potentiel d'inaugurer une nouvelle ère d'outils introspectifs permettant aux développeurs de maîtriser des systèmes de plus en plus complexes. Ils offrent également aux développeurs les plus avancés un moyen standard et lisible d'associer directement des métadonnées à des arguments et à une valeur renvoyée afin de créer des outils personnalisés et d'interagir avec les décorateurs. Mais il faut travailler pour en tirer parti et utiliser leur potentiel.

Apprendre le python

Apprenez Python avec notre guide complet de tutoriel sur Python, que vous soyez débutant ou que vous soyez un codeur chevronné cherchant à acquérir de nouvelles compétences..