web-dev-qa-db-fra.com

Quelles sont les bonnes utilisations pour les "annotations de fonctions" de Python3

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?

151
agscala

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.

87
Uri

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.

86
Raymond Hettinger

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
40
Dustin Wyatt

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.

23

Uri a déjà donné une réponse correcte, alors voici une réponse moins sérieuse: vous pouvez donc raccourcir vos docstrings.

20
JAB

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 .

13
weaver

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.

  • Fournir des informations de frappe
    • Vérification de type ([3], [4])
    • Laisser les IDE montrer quels types une fonction attend et retourne ([17])
    • Surcharge de fonction/fonctions génériques ([22])
    • Ponts en langues étrangères ([18], [19])
    • Adaptation ([21], [20])
    • Fonctions logiques des prédicats
    • Mappage de requête de base de données
    • Marshaling de paramètre RPC ([23])
  • Les autres informations
    • Documentation sur les paramètres et les valeurs de retour ([24])

Voir le PEP pour plus d'informations sur des points spécifiques (ainsi que leurs références)

3
klaas

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

https://www.python.org/dev/peps/pep-0526/

https://www.attrs.org/fr/stable/types.html

2
The Demz

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.

1
amcgregor

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.

1