web-dev-qa-db-fra.com

Python 3 et typage statique

Je n'ai pas vraiment prêté autant d'attention au développement de Python 3 que je l'aurais souhaité, et j'ai seulement remarqué quelques changements de syntaxe intéressants. Plus précisément à partir de cette SO réponse annotation de paramètre de fonction:

def digits(x:'nonnegative number') -> "yields number's digits":
    # ...

Ne sachant rien à ce sujet, j'ai pensé qu'il pourrait peut-être être utilisé pour implémenter le typage statique en Python!

Après quelques recherches, il semble y avoir beaucoup de discussions sur le typage statique (entièrement optionnel) en Python, comme celles mentionnées dans PEP 3107 , et "Ajout du typage statique optionnel à Python" (et partie 2 )

..mais, je ne suis pas clair à quel point cela a progressé. Existe-t-il des implémentations de typage statique utilisant l'annotation de paramètre? Est-ce que des idées de type paramétrées ont été intégrées à Python 3?

57
dbr

Merci d'avoir lu mon code!

En effet, il n'est pas difficile de créer un applicateur d'annotation générique en Python. Voici ma prise:

'''Very simple enforcer of type annotations.

This toy super-decorator can decorate all functions in a given module that have 
annotations so that the type of input and output is enforced; an AssertionError is
raised on mismatch.

This module also has a test function func() which should fail and logging facility 
log which defaults to print. 

Since this is a test module, I cut corners by only checking *keyword* arguments.

'''

import sys

log = print


def func(x:'int' = 0) -> 'str':
    '''An example function that fails type checking.'''
    return x


# For simplicity, I only do keyword args.
def check_type(*args):
    param, value, assert_type = args
    log('Checking {0} = {1} of {2}.'.format(*args))
    if not isinstance(value, assert_type):
        raise AssertionError(
            'Check failed - parameter {0} = {1} not {2}.'
            .format(*args))
    return value

def decorate_func(func):    
    def newf(*args, **kwargs):
        for k, v in kwargs.items():
            check_type(k, v, ann[k])
        return check_type('<return_value>', func(*args, **kwargs), ann['return'])

    ann = {k: eval(v) for k, v in func.__annotations__.items()}
    newf.__doc__ = func.__doc__
    newf.__type_checked = True
    return newf

def decorate_module(module = '__main__'):
    '''Enforces type from annotation for all functions in module.'''
    d = sys.modules[module].__dict__
    for k, f in d.items():
        if getattr(f, '__annotations__', {}) and not getattr(f, '__type_checked', False):
            log('Decorated {0!r}.'.format(f.__name__))
            d[k] = decorate_func(f)


if __== '__main__':
    decorate_module()

    # This will raise AssertionError.
    func(x = 5)

Compte tenu de cette simplicité, il est étrange à première vue que cette chose ne soit pas grand public. Cependant, j'estime qu'il y a de bonnes raisons pour que ce soit pas aussi utile que cela puisse paraître. En règle générale, la vérification de type aide car si vous ajoutez un entier et un dictionnaire, il est probable que vous ayez commis une erreur évidente (et si vous vouliez dire quelque chose de raisonnable, cela reste toujours mieux vaut être explicite qu'implicite).

Mais dans la vie réelle, vous mélangez souvent des quantités du même type type d'ordinateur telles qu'elles sont vues par le compilateur mais clairement différentes types humain, par exemple l'extrait suivant contient une erreur évidente:

height = 1.75 # Bob's height in meters.
length = len(sys.modules) # Number of modules imported by program.
area = height * length # What's that supposed to mean???

Tout être humain devrait immédiatement voir une erreur dans la ligne ci-dessus à condition de connaître le «type humain» des variables height et length, bien qu'il semble à l'ordinateur que la multiplication parfaitement légale de int et float.

On peut en dire plus sur les solutions possibles à ce problème, mais appliquer des «types d’ordinateur» est apparemment une demi-solution, donc, du moins à mon avis, c’est pire qu’aucune solution du tout. C’est la même raison pour laquelle Systems Hungarian est une idée terrible, alors que Apps Hungarian en est une excellente. Il y a plus dans le très informatif post de Joel Spolsky.

Maintenant, si quelqu'un implémentait une sorte de bibliothèque tierce Pythonic qui affecterait automatiquement aux données du monde réel son type human et prendrait ensuite soin de transformer ce type comme width * height -> area et d'appliquer cette vérification avec des annotations de fonction, je pense ce serait un type de vérificateur que les gens pourraient vraiment utiliser!

33
ilya n.

Comme indiqué dans ce PEP, la vérification de type statique est l'une des applications pour lesquelles des annotations de fonction peuvent être utilisées, mais elles laissent à des bibliothèques tierces le soin de décider de la manière de le faire. En d’autres termes, il n’y aura pas d’implémentation officielle dans le noyau de python.

En ce qui concerne les implémentations tierces, certains extraits (tels que http://code.activestate.com/recipes/572161/ ) semblent bien fonctionner.

MODIFIER:

En guise de remarque, je tiens à mentionner que le comportement de vérification est préférable à la vérification de type. Par conséquent, je pense que la vérification de type statique n’est pas une si bonne idée. Ma réponse ci-dessus vise à répondre à la question, non pas parce que je ferais moi-même une vérification de frappe de cette manière.

14
sykora

Ce n’est pas une réponse directe à une question, mais j’ai découvert un fork pour Python qui ajoute du typage statique: mypy-lang.org , bien sûr, on ne peut pas s’y fier car c’est encore peu, mais intéressant.

13
Ciantic

Le "typage statique" en Python ne peut être implémenté que si la vérification du type est effectuée au moment de l'exécution, ce qui signifie que l'application est ralentie. Par conséquent, vous ne voulez pas que cela soit généralisé. Au lieu de cela, vous voulez que certaines de vos méthodes vérifient ses entrées. Cela peut être facilement fait avec des affirmations simples ou avec des décorateurs si vous pensez (à tort) que vous en avez besoin beaucoup.

Il existe également une alternative à la vérification de type statique, qui consiste à utiliser une architecture de composant orientée aspect telle que The Zope Component Architecture. Au lieu de vérifier le type, vous l’adaptez. Donc au lieu de:

assert isinstance(theobject, myclass)

tu fais cela:

theobject = IMyClass(theobject)

Si l'objet implémente déjà IMyClass, rien ne se passe. Si ce n'est pas le cas, un adaptateur qui englobera tout ce que l'objet est destiné à IMyClass sera recherché et utilisé à la place de l'objet. Si aucun adaptateur n'est trouvé, vous obtenez une erreur.

Cela combinait le dynamisme de Python avec le désir d’avoir un type spécifique d’une manière spécifique.

12
Lennart Regebro

Bien sûr, le typage statique semble un peu "unpythonic" et je ne l'utilise pas tout le temps. Mais il existe des cas (par exemple des classes imbriquées, comme dans l'analyse de langage spécifique à un domaine) où cela peut vraiment accélérer votre développement. 

Ensuite, je préfère utiliser beartype expliqué dans ce post *. Il vient avec un repo git, des tests et une explication de ce qu’il peut et ce qu’il ne peut pas faire ... et j’aime bien le nom;) 

* S'il vous plaît, ne faites pas attention au discours de Cecil sur la raison pour laquelle Python ne vient pas avec les piles incluses dans ce cas.

0
Iwan LD