web-dev-qa-db-fra.com

Annotations de type pour * args et ** kwargs

J'essaie les annotations de types de Python avec des classes de base abstraites pour écrire des interfaces. Existe-t-il un moyen d'annoter les types possibles de *args Et **kwargs?

Par exemple, comment pourrait-on exprimer que les arguments sensibles d'une fonction sont soit un int ou deux ints? type(args) donne Tuple donc je devais annoter le type avec Union[Tuple[int, int], Tuple[int]], mais cela ne fonctionne 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 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'ajout après le déballage donne également une erreur de frappe que je ne comprends pas.

Comment annoter les types sensibles pour *args Et **kwargs?

85
Praxeolitic

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 un tel argument.

À partir de la listes d'arguments arbitraires et des valeurs d'argument par défaut de astuces de type PEP:

Les listes d'arguments arbitraires peuvent également ê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 éléments suivants représentent des appels de fonction avec des types d'arguments valides:

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

Donc, 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 du tout, utilisez un argument de position explicite et un second argument de mot clé:

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

Maintenant, votre fonction est en réalité limitée à un ou deux arguments, et les deux doivent être des entiers s'ils sont spécifiés. *args toujours signifie 0 ou plus et ne peut pas être limité par des indications de type à une plage plus spécifique.

87
Martijn Pieters

En bref ajout à la réponse précédente, si vous essayez d’utiliser mypy sur les fichiers Python 2 et que vous devez utiliser les commentaires pour ajouter des types à la place des annotations, vous devez préfixer les types pour args et kwargs avec * et ** respectivement:

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

Ceci est traité par mypy comme étant le même que celui ci-dessous, Python 3.5 de foo:

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

Pour ce faire, utilisez @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 saisissez des annotations dans l'implémentation réelle, qui doit venir en dernier.

Vous aurez besoin d’une nouvelle version de typing et de mypy pour pouvoir prendre en charge @ overload en dehors des fichiers de raccord .

Vous pouvez également l'utiliser pour faire varier le résultat renvoyé de manière à rendre explicite le type d'argument correspondant au 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))
12
chadrik