Annotations de fonctions: PEP-3107
Je suis tombé sur un extrait de code présentant les annotations de fonctions de Python3. Le concept est simple, mais je ne vois pas pourquoi ces applications ont été implémentées dans Python3, ni aucune bonne utilisation. Peut-être que SO peut m'éclairer?
Comment ça marche:
def foo(a: 'x', b: 5 + 6, c: list) -> max(2, 9):
... function body ...
Tout ce qui suit les deux points après un argument est une "annotation" et les informations qui suivent le ->
est une annotation pour la valeur de retour de la fonction.
foo.func_annotations renverrait un dictionnaire:
{'a': 'x',
'b': 11,
'c': list,
'return': 9}
Quelle est la signification d'avoir cela disponible?
Je pense que c'est vraiment génial.
Venant d'universitaire, je peux vous affirmer que les annotations se sont révélées inestimables pour permettre des analyseurs statiques intelligents pour des langages tels que Java. Par exemple, vous pouvez définir des sémantiques telles que des restrictions d’état, des threads autorisés à accéder, des limitations d’architecture, etc. De nombreux outils permettent ensuite de les lire et de les traiter pour fournir des assurances au-delà de ce que vous obtenez des compilateurs. Vous pouvez même écrire des choses qui vérifient les préconditions/post-conditions.
Je pense que quelque chose comme ceci est particulièrement nécessaire dans Python en raison de son typage plus faible, mais il n'y avait vraiment aucune construction qui rendait cela simple et faisait partie de la syntaxe officielle.
Il existe d'autres utilisations des annotations au-delà de l'assurance. Je peux voir comment appliquer mes outils Java à Python. Par exemple, j’ai un outil qui vous permet d’attribuer des avertissements spéciaux aux méthodes et vous indique lorsque vous les appelez que vous devez lire leur documentation (par exemple, imaginez que vous ayez une méthode qui ne doit pas être appelée avec une valeur négative, mais c’est pas intuitif du nom). Avec les annotations, je pourrais technicall écrire quelque chose comme ceci pour Python. De même, un outil organisant des méthodes dans une grande classe en fonction de balises peut être écrit s'il existe une syntaxe officielle.
Les annotations de fonction sont ce que vous en faites.
Ils peuvent être utilisés pour la documentation:
def kinetic_energy(mass: 'in kilograms', velocity: 'in meters per second'):
...
Ils peuvent être utilisés pour la vérification des conditions préalables:
def validate(func, locals):
for var, test in func.__annotations__.items():
value = locals[var]
msg = 'Var: {0}\tValue: {1}\tTest: {2.__name__}'.format(var, value, test)
assert test(value), msg
def is_int(x):
return isinstance(x, int)
def between(lo, hi):
def _between(x):
return lo <= x <= hi
return _between
def f(x: between(3, 10), y: is_int):
validate(f, locals())
print(x, y)
>>> f(0, 31.1)
Traceback (most recent call last):
...
AssertionError: Var: y Value: 31.1 Test: is_int
Voir aussi http://www.python.org/dev/peps/pep-0362/ pour un moyen d'implémenter la vérification de type.
C'est une réponse tardive, mais AFAICT, la meilleure utilisation actuelle des annotations de fonctions est PEP-0484 et MyPy .
Mypy est un vérificateur de type statique optionnel pour Python. Vous pouvez ajouter des indicateurs de type à vos programmes Python en utilisant la norme à venir pour les annotations de type introduites dans Python 3.5 beta 1 (PEP 484), etc.), et utiliser mypy pour taper vérifiez-les statiquement.
Utilisé comme tel:
from typing import Iterator
def fib(n: int) -> Iterator[int]:
a, b = 0, 1
while a < n:
yield a
a, b = b, a + b
Juste pour ajouter un exemple spécifique d’un bon usage de ma réponse ici , associé à des décorateurs, un mécanisme simple pour plusieurs méthodes peut être utilisé.
# This is in the 'mm' module
registry = {}
import inspect
class MultiMethod(object):
def __init__(self, name):
self.name = name
self.typemap = {}
def __call__(self, *args):
types = Tuple(arg.__class__ for arg in args) # a generator expression!
function = self.typemap.get(types)
if function is None:
raise TypeError("no match")
return function(*args)
def register(self, types, function):
if types in self.typemap:
raise TypeError("duplicate registration")
self.typemap[types] = function
def multimethod(function):
name = function.__name__
mm = registry.get(name)
if mm is None:
mm = registry[name] = MultiMethod(name)
spec = inspect.getfullargspec(function)
types = Tuple(spec.annotations[x] for x in spec.args)
mm.register(types, function)
return mm
et un exemple d'utilisation:
from mm import multimethod
@multimethod
def foo(a: int):
return "an int"
@multimethod
def foo(a: int, b: str):
return "an int and a string"
if __== '__main__':
print("foo(1,'a') = {}".format(foo(1,'a')))
print("foo(7) = {}".format(foo(7)))
Cela peut être fait en ajoutant les types au décorateur comme le post original de Guido , mais annoter les paramètres eux-mêmes est préférable car cela évite la possibilité d'une mauvaise correspondance des paramètres et des types.
Remarque : Dans Python, vous pouvez accéder aux annotations sous la forme function.__annotations__
Plutôt que function.func_annotations
car le style func_*
a été supprimé le Python 3.
Uri a déjà donné une réponse correcte, alors voici une réponse moins sérieuse: vous pouvez donc raccourcir vos docstrings.
La première fois que j'ai vu des annotations, je me suis dit "Génial! Enfin, je peux accepter une vérification de type!" Bien sûr, je n'avais pas remarqué que les annotations n'étaient pas réellement appliquées.
J'ai donc décidé de écrire un simple décorateur de fonctions pour les appliquer :
def ensure_annotations(f):
from functools import wraps
from inspect import getcallargs
@wraps(f)
def wrapper(*args, **kwargs):
for arg, val in getcallargs(f, *args, **kwargs).items():
if arg in f.__annotations__:
templ = f.__annotations__[arg]
msg = "Argument {arg} to {f} does not match annotation type {t}"
Check(val).is_a(templ).or_raise(EnsureError, msg.format(arg=arg, f=f, t=templ))
return_val = f(*args, **kwargs)
if 'return' in f.__annotations__:
templ = f.__annotations__['return']
msg = "Return value of {f} does not match annotation type {t}"
Check(return_val).is_a(templ).or_raise(EnsureError, msg.format(f=f, t=templ))
return return_val
return wrapper
@ensure_annotations
def f(x: int, y: float) -> float:
return x+y
print(f(1, y=2.2))
>>> 3.2
print(f(1, y=2))
>>> ensure.EnsureError: Argument y to <function f at 0x109b7c710> does not match annotation type <class 'float'>
Je l'ai ajouté à la bibliothèque Ensure .
Cela fait longtemps que cela n’a pas été demandé, mais l’extrait de code donné dans la question provient (comme indiqué également) du PEP 3107 et à la fin de cet exemple de PEP, des cas d’utilisation pouvant également répondre à la question posée par le PEP sont: vue ;)
Ce qui suit est tiré de PEP3107
Cas d'utilisation
Au cours des discussions sur les annotations, un certain nombre de cas d'utilisation ont été évoqués. Certaines d’entre elles sont présentées ici, regroupées par type d’information qu’elles véhiculent. Vous trouverez également des exemples de produits et de packages existants pouvant utiliser des annotations.
Voir le PEP pour plus d'informations sur des points spécifiques (ainsi que leurs références)
Python 3.X (uniquement) généralise également la définition de la fonction pour permettre aux arguments et aux valeurs renvoyées d'être annotés avec des valeurs d'objet à utiliser dans les extensions .
Ses données META à expliquer, pour être plus explicite sur les valeurs de la fonction.
Les annotations sont codées comme :value
après le nom de l’argument et avant une valeur par défaut, et comme ->value
après la liste des arguments.
Ils sont rassemblés dans un __annotations__
attribut de la fonction, mais ne sont pas autrement traités comme spéciaux par Python lui-même:
>>> def f(a:99, b:'spam'=None) -> float:
... print(a, b)
...
>>> f(88)
88 None
>>> f.__annotations__
{'a': 99, 'b': 'spam', 'return': <class 'float'>}
Source: Python Référence de poche, cinquième édition
EXEMPLE:
Le module typeannotations
fournit un ensemble d'outils pour la vérification de type et l'inférence de type de Python). Il fournit également un ensemble de types utiles pour l'annotation de fonctions et d'objets.
Ces outils sont principalement conçus pour être utilisés par des analyseurs statiques tels que les linters, les bibliothèques de complétion de code et les IDE. De plus, des décorateurs sont disponibles pour effectuer les vérifications d'exécution. La vérification de type à l'exécution n'est pas toujours une bonne idée en Python, mais dans certains cas, elle peut être très utile.
https://github.com/ceronman/typeannotations
Comment la frappe aide-t-elle à écrire un meilleur code
La saisie peut vous aider à effectuer une analyse de code statique afin d’attraper les erreurs de type avant d’envoyer votre code à la production et de vous éviter certains bogues évidents. Il existe des outils comme mypy, que vous pouvez ajouter à votre boîte à outils dans le cadre du cycle de vie de votre logiciel. mypy peut vérifier que les types sont corrects en s’exécutant partiellement ou totalement sur votre base de code. mypy vous aide également à détecter des bogues, tels que la vérification du type Aucun lorsque la valeur est renvoyée par une fonction. Taper aide à rendre votre code plus propre. Au lieu de documenter votre code à l'aide de commentaires, lorsque vous spécifiez des types dans une docstring, vous pouvez utiliser des types sans aucun coût de performance.
Clean Python: Codage élégant dans Python ISBN: ISBN-13 (pbk): 978-1-4842-4877-5
PEP 526 - Syntaxe pour les annotations variables
En guise de réponse tardive, plusieurs de mes paquets (marrow.script, WebCore, etc.) utilisent des annotations pour déclarer le transtypage (c'est-à-dire transformer les valeurs entrantes du Web, détecter les arguments qui sont des commutateurs booléens, etc.). comme pour effectuer un balisage supplémentaire des arguments.
Marrow Script construit une interface de ligne de commande complète pour les fonctions et les classes arbitraires et permet de définir des valeurs par défaut pour la documentation, le transtypage et le rappel par callback, avec un décorateur prenant en charge les environnements d'exécution plus anciens. Toutes mes bibliothèques qui utilisent des annotations prennent en charge les formulaires:
any_string # documentation
any_callable # typecast / callback, not called if defaulting
(any_callable, any_string) # combination
AnnotationClass() # package-specific rich annotation object
[AnnotationClass(), AnnotationClass(), …] # cooperative annotation
La prise en charge "nue" des docstrings ou des fonctions de conversion de type facilite le mélange avec d’autres bibliothèques prenant en charge les annotations. (C'est-à-dire que vous avez un contrôleur Web utilisant la conversion de type qui est également exposé en tant que script de ligne de commande.)
Édité pour ajouter: J'ai également commencé à utiliser le paquet TypeGuard en utilisant des assertions de développement pour la validation. Avantage: lorsqu’il est exécuté avec "optimisations" activé (-O
/PYTHONOPTIMIZE
env var) les vérifications, qui peuvent être coûteuses (par exemple, récursives), sont omises, avec l'idée que vous avez correctement testé votre application en développement, de sorte que les vérifications ne devraient pas être nécessaires en production.
En dépit de toutes les utilisations décrites ici, la seule utilisation exécutable et, probablement, forcée des annotations sera pour indications de type .
Cela n’est actuellement appliqué en aucune façon, mais, à en juger par PEP 484, les versions futures de Python autorisera uniquement les types comme valeur pour les annotations.
Citant Qu'en est-il des utilisations existantes des annotations? :
Nous espérons que les indications de type deviendront éventuellement le seul usage des annotations, mais cela nécessitera une discussion supplémentaire et une période de désapprobation après le déploiement initial du module de frappe avec Python 3.5. PEP aura un statut provisoire (voir PEP 411) jusqu'au Python 3.6 est publié. Le schéma le plus rapide concevable introduirait une dépréciation silencieuse des annotations sans indication de type dans 3.6, une dépréciation complète en 3.7 et la déclaration Les indications de type sont la seule utilisation autorisée des annotations dans Python 3.8.
Bien que je n’aie encore vu aucune dépréciation silencieuse dans la version 3.6, elle pourrait très bien être remplacée par la version 3.7.
Ainsi, même s'il peut y avoir d'autres bons cas d'utilisation, il est préférable de les garder uniquement pour les conseils de type si vous ne voulez pas changer tout dans un avenir où cette restriction est en place.