Annotations de Type pour * args et * * kwargs

j'essaie les annotations de type de Python avec des classes de base abstraites pour écrire quelques interfaces. Est-il possible d'annoter les types possibles de *args et **kwargs?

par exemple, comment pourrait-on exprimer que les arguments sensés d'une fonction sont soit un int ou deux int s? type(args) donne Tuple donc à mon avis était d'Annoter le type comme Union[Tuple[int, int], Tuple[int]], mais ça ne marche pas.

from typing import Union, Tuple

def foo(*args: Union[Tuple[int, int], Tuple[int]]):
    try:
        i, j = args
        return i + j
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

# ok
print(foo((1,)))
print(foo((1, 2)))
# mypy does not like this
print(foo(1))
print(foo(1, 2))

messages D'erreur de mypy:

t.py: note: In function "foo":
t.py:6: error: Unsupported operand types for + ("tuple" and "Union[Tuple[int, int], Tuple[int]]")
t.py: note: At top level:
t.py:12: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:14: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 1 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"
t.py:15: error: Argument 2 to "foo" has incompatible type "int"; expected "Union[Tuple[int, int], Tuple[int]]"

Il il est logique que mypy n'aime pas cela pour l'appel de fonction car il s'attend à ce qu'il y ait un tuple dans l'appel lui-même. L'addition après déballage donne aussi une erreur de frappe que je ne comprends pas.

comment annoter les types sensibles pour *args et **kwargs?

43
demandé sur ndmeiri 2016-05-04 18:21:26

3 réponses

pour les arguments de position variables (*args) et les arguments de mots-clés variables (**kw) il vous suffit de spécifier la valeur attendue pour tel argument.

listes D'arguments arbitraires et valeurs d'arguments par défautType De Conseils PEP:

les listes D'arguments arbitraires peuvent aussi bien être annotées, de sorte que la définition:

def foo(*args: str, **kwds: int): ...

est acceptable et cela signifie que, par exemple, tous les appels de fonctions suivants représentent des types d'arguments valides:

foo('a', 'b', 'c')
foo(x=1, y=2)
foo('', z=0)

alors vous voudriez spécifier votre méthode comme ceci:

def foo(*args: int):

cependant, si votre fonction ne peut accepter qu'une ou deux valeurs entières, vous ne devez pas utiliser *args utilisez un seul argument de position explicite et un second argument de mot clé:

def foo(first: int, second: Optional[int] = None):

Maintenant, votre fonction est en fait limitée à un ou deux arguments, et les deux doit être entier si spécifié. *argstoujours signifie 0 ou plus, et ne peut pas être limité par des indices de type à une plage plus spécifique.

46
répondu Martijn Pieters 2018-01-16 20:21:24

en bref ajout à la réponse précédente, si vous essayez d'utiliser mypy sur des fichiers Python 2 et que vous avez besoin d'utiliser des commentaires pour ajouter des types au lieu d'annotations, vous devez préfixer les types pour args et kwargs* et ** respectivement:

def foo(param, *args, **kwargs):
    # type: (bool, *str, **int) -> None
    pass

ceci est traité par mypy comme étant la même que la version Python 3.5 de foo:

def foo(param: bool, *args: str, **kwargs: int) -> None:
    pass
13
répondu Michael0x2a 2016-06-28 04:15:02

La bonne façon de le faire est d'utiliser @overload

from typing import overload

@overload
def foo(arg1: int, arg2: int) -> int:
    ...

@overload
def foo(arg: int) -> int:
    ...

def foo(*args):
    try:
        i, j = args
        return i + j
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

print(foo(1))
print(foo(1, 2))

notez que vous n'ajoutez pas @overload ou tapez les annotations à la mise en œuvre réelle, qui doit venir en dernier.

Vous aurez besoin d'un newish version des deux typing et mypy pour obtenir de l'aide pour @surcharge en dehors de fichiers stub.

vous pouvez également utiliser ceci pour modifier le résultat retourné d'une manière qui rend explicite quels types d'arguments correspondent avec quel type de retour. par exemple:

from typing import Tuple, overload

@overload
def foo(arg1: int, arg2: int) -> Tuple[int, int]:
    ...

@overload
def foo(arg: int) -> int:
    ...

def foo(*args):
    try:
        i, j = args
        return j, i
    except ValueError:
        assert len(args) == 1
        i = args[0]
        return i

print(foo(1))
print(foo(1, 2))
6
répondu chadrik 2017-08-24 18:56:04