Point rapide sur les types hints


Avec la 3.5 arrive les types hints.

Pour remettre ça dans le contexte, Python 3 avait introduit les functions annotations, c’est à dire la possibilité de rajouter une information aux paramètres et valeur de retour d’une fonction. Cette information pouvait être n’importe quel objet Python :

def add(a: "n'importe quel objet", b: 1) -> ImportError:
    return a + b

Ces annotations ne faisaient absolument rien à part être accessibles :

>>> add(1, 2)
3
>>> add.__annotations__
{'b': 1, 'a': "n'importe quel objet", 'return': <class 'ImportError'>}

Le but étant de permettre à la communauté de faire des propositions sur leur utilisation jusqu’à ce que la préférée soit retenue.

Aucune proposition n’a néanmoins autant de succès que de permettre optionnellement de déclarer le typage, et c’est donc ce processus qui est amorcé avec la prochaine version où l’on pourra faire :

def add(a: int, b: int) -> int:
    return a + b

Pour les cas plus complexes, un module contient des interfaces courantes. Par exemple, si votre fonction fait la somme d’un itérable :

from typing import Iterable
 
def total(l: Iterable):

Si vous voulez limiter l’itérable à un itérable d’entiers :

def total(l: Iterable[int]):

Pour les fonctions très complexes, ça peut vite devenir très, très moche :

def truc(l: Iterable[Tuple(Any(Bidule, Machin))], oui=True: bool, **kwargs) -> FuckIt:
    return FuckIt(l, oui, **kwargs)

Par ailleurs, le module typing va devenir une des rares bonnes raisons d’utiliser import * :)

La spec prévoit qu’on puisse mettre ces annotations dans un fichier séparé du code avec juste la signature des fonctions. D’un côté, ça a le bénéfice de permettre d’annoter un code inaltérable, ou en Python 2.7. D’un autre, le DRY va du coup aller se faire foutre et franchement, je code pas en Python pour avoir des headers comme en C.

L’implémentation originale ne fera strictement rien. Elle est juste là pour voir ce que les outils vont en faire : un IDE pourra facilement trouver une erreur de type, on pourra faire des hooks git qui vérifient qu’on n’a pas niqué notre code en balançant un mauvais type et pypy/cython/nuikta/numba vont pouvoir se rouler dans la boue des optimisations.

Cela veut dire que si j’ignore mon IDE et que je passe des arguments d’un type qui n’est pas annoté, Python l’exécutera quand même. Ouf.

Le bilan ?

Globalement positif.

C’est vrai que c’est méga moche. Et les propositions alternatives ne sont pas mieux.

Je rêve que les annotations dans les docstrings soient utilisées, mais Guido a dit niet pour des raisons que je trouve faiblement fondées : les dosctrings sont un format libre dur à parser et alors que la syntaxe Python est structurée, avec un bon lexer. L’argument avancé dans le PEP est carrément faible : les docstrings ça se fold dans tous les éditeurs, c’est tout l’avantage.

Perso, je pense que normaliser enfin le format des docstrings ne me parait pas une mauvaise chose. Et puis si on arrive à parser le HTML pourri qu’on trouve sur le Web, 3 lignes de docstrings, c’est pas la mer à boire.

Néanmoins c’est optionnel, souple, et la première implémentation ne se mouille pas trop, ce qui laisse de la marge pour corriger les problèmes qui vont forcément venir.

Le bénéfice potentiel est énorme.

D’un côté, on aura toujours un langage facile à apprendre et enseigner, le duck typing, l’exploration de code, le typage dynamique par défaut.

De l’autre, quand un surcroit de rigueur sera nécessaire, on pourra rajouter un filet de sécurité. Et dans quelques temps, peut-être même gagner sérieusement en perf.

Mais quand même, putain c’est moche.

6 thoughts on “Point rapide sur les types hints

  • Romain

    Mais quand même, putain c’est moche.

    Ben, ça ressemble à du Scala quoi ^-^.

    Ça a un aspect documentaire indéniable. Par contre, ça ouvre la porte à une étape de build qui va effectuer les contrôles, non ?

    Ce qui est amusant c’est que ça arrive à un moment ou je lis pas mal de chose contre le typage dynamique…

    Au final, pourquoi pas. C’est bien de laisser le choix au développeur de les utiliser ou pas.

  • Eric

    Il y avait une session interessante sur le sujet a l’europython 2014 par Bob Ippolito.

    Il en ressortait que MyPy etait la “moins pire” des options pour verifier les types.

  • LeMeteore

    Je pense que cette fonctionnalité sera surtout utilisée par de grosses boites ayant besoin de contraintes fortes dans leurs pipelines de builds automatisés, etc. A mon tout petit niveau, j’attends, qui sait? Je m’en servirai peut etre :\

    Je pense aussi que la syntaxe c’est pr ne pas trop s’eloigner de ce que font “les autres langages”, tu lis, et tu piges plus ou moins vite ce qui se passe (scala, java, haskell, …)

    Mais oui, je trouve moi aussi que c’est moche :\ on verra bien où ça nous mene.

  • Zanguu

    En ce moment j’ai un combo Symfony2 PhpStorm, j’use et abuse des @param et @var pour forcer l’autocomplétion.

    Je peux toujours passer une string au lieu d’un int à ma fonction mais la signature fait pas trop lignes quand j’ai plus de 4 paramètres.

    Du coup j’ai des comm tout le temps repliés qui prennent presque plus de place que le code

    /**

    * @param \Chemin\vers\entity $entity

    * @param int $un_id

    * @return JsonResponse

    */

    function truc($entity, $un_id) {

    /**

    * @var \Chemin\vers\entityDeux $autre_entity

    * @var \Chemin\vers\mon\objet\aLaCon $a_la_con

    */

    return new JsonResponse([$autre_entity, $a_la_con]);

    }

    C’est moche, ça prend de la place, mais ça m’évite de taper n’importe quoi et quand je veux lire ma fonction pour voir si j’ai raté un truc, je les replie et c’est aussi lisible qu’avant.

    J’aime bien aussi les annotation en C# aka “le triple slash” qui sert à documenter l’auto-complétion de VS et/ou sortir une doc complete.

    Mais là en python, pour un truc censé être juste indicatif c’est vraiment moche.

  • matthieu

    J’utilise déjà ce système (PyCharm le prend en compte avec Python 3.4, même si ce n’est pas aussi complet).

    L’utiliser dans l’annotation a l’avantage d’avoir la complétion active et donc nettement plus pratique à utiliser. En revanche, ça pose des problèmes quand on a des références croisées (module1.fonction est utilisé dans module2, et prend des arguments de type module2.classe).

    Je ne sais pas trop comment les résoudre proprement (ce qui m’a poussé à abandonner le truc).

    En type hiniting, sachant que le import se fait automatiquement :

    from mon.super.projet import maclasse

    def mafonction(arg: maclasse):

    [blabla]

    Avec les docstring, tout doit être tapé à la main :

    def mafonction(arg):

    """ :type arg: :class:mon.super.projet.maclasse  (le :class: permet à sphinx de faire les liens)

    """

    [balbla]

  • Ludovic Gasc (GMLudo)

    Salut Sam,

    En ce qui concerne l’amélioration des performances, j’avais les mêmes espoirs que toi.

    J’en avais discuté avec Guido au moment où il avait sorti ça, il m’avait dit que c’était peu probable.

    J’en ai rediscuté avec des gens de PyPy pendant la PyCON-US, ils m’ont confirmé cet état de fait.

    En fait, si j’ai bien tout suivi, un des problèmes, c’est que les types fournis dans ce module sont trop “high-level” par rapport aux types de base qu’utilisent PyPy, Cython & cie, tu n’as pas un matching 1-1, et donc soit tu dois quand même fournir des types + précis avec Cython, soit PyPy doit quand même faire du prédictive pour le JIT.

    Après, ça ne serait pas la première fois que quelqu’un trouve une manière pour que ça serve à booster du code, alors que tout le monde pensait que c’était impossible. La communauté Python est quand même spécialisé dans la course à la + grosse, il n’y a qu’à voir toutes les pistes pour pouvoir optimiser du code en Python pour s’en convaincre ;-) Par exemple, ce n’est pas en Ruby que tu as tout cet arsenal.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.