Digita le annotazioni per * args e ** kwargs


159

Sto provando le annotazioni di tipo Python con classi base astratte per scrivere alcune interfacce. C'è un modo per annotare i possibili tipi di *argse **kwargs?

Ad esempio, come si potrebbe esprimere che gli argomenti sensibili di una funzione sono uno into due ints? type(args)Tuplequindi la mia ipotesi era di annotare il tipo come Union[Tuple[int, int], Tuple[int]], ma questo non funziona.

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))

Messaggi di errore da 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]]"

Ha senso che a Mypy non piaccia questo per la chiamata di funzione perché si aspetta che ci sia un tuplenella chiamata stessa. L'aggiunta dopo il disimballaggio dà anche un errore di battitura che non capisco.

Come si annotano i tipi sensibili per *argse **kwargs?

Risposte:


168

Per argomenti posizionali variabili ( *args) e argomenti con parole chiave variabili ( **kw) è necessario specificare solo il valore atteso per uno di questi argomenti.

Dagli elenchi degli argomenti arbitrari e dalla sezione dei valori degli argomenti predefiniti del PEP dei suggerimenti sul tipo :

Gli elenchi di argomenti arbitrari possono anche essere annotati per tipo, in modo che la definizione:

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

è accettabile e significa che, ad esempio, tutte le seguenti rappresentano chiamate di funzione con tipi di argomenti validi:

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

Quindi vorresti specificare il tuo metodo in questo modo:

def foo(*args: int):

Tuttavia, se la tua funzione può accettare solo uno o due valori interi, non dovresti usare *argsaffatto, usa un argomento di posizione esplicito e un secondo argomento di parola chiave:

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

Ora la tua funzione è in realtà limitata a uno o due argomenti ed entrambi devono essere numeri interi se specificati. significa *args sempre 0 o più e non può essere limitato dai suggerimenti di tipo a un intervallo più specifico.


1
Solo curioso, perché aggiungere il Optional? Qualcosa è cambiato su Python o hai cambiato idea? Non è ancora strettamente necessario a causa del Nonedefault?
Prassolitico

10
@Praxeolitic sì, in pratica l' Optionalannotazione automatica, implicita, quando si utilizza Nonecome valore predefinito, ha reso alcune operazioni più difficili e che ora viene rimosso dal PEP.
Martijn Pieters

5
Ecco un link che ne discute per chi è interessato. Sembra certamente che Optionalin futuro sarà richiesto un esplicito .
Rick supporta Monica il

Questo in realtà non è supportato per Callable: github.com/python/mypy/issues/5876
Shital Shah,

1
@ShitalShah: non è proprio questo il problema. Callablenon supporta alcuna menzione di un suggerimento per il tipo *argso di **kwargs un punto . Quel problema specifico riguarda il markup di callable che accettano argomenti specifici più un numero arbitrario di altri , e quindi usano *args: Any, **kwargs: Any, un suggerimento di tipo molto specifico per i due catch-alls. Per i casi in cui si imposta *argse / o **kwargssu qualcosa di più specifico è possibile utilizzare a Protocol.
Martijn Pieters

26

Il modo corretto per farlo è usare @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))

Si noti che non si aggiungono @overloado si digitano annotazioni per l'implementazione effettiva, che deve arrivare per ultima.

typingPer ottenere supporto per @overload al di fuori dei file stub avrai bisogno di una versione nuova di entrambi e di Mypy .

Puoi anche usarlo per variare il risultato restituito in modo da rendere esplicito quali tipi di argomenti corrispondono a quale tipo restituito. per esempio:

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))

2
Mi piace questa risposta perché affronta il caso più generale. Guardando indietro, non avrei dovuto usare chiamate di funzione (type1)vs (type1, type1)come mio esempio. Forse (type1)vs (type2, type1)sarebbe stato un esempio migliore e mostra perché mi piace questa risposta. Ciò consente anche diversi tipi di restituzione. Tuttavia, nel caso speciale in cui hai solo un tipo di ritorno e il tuo *argse *kwargssei lo stesso tipo, la tecnica nella risposta di Martjin ha più senso, quindi entrambe le risposte sono utili.
Prassolitico,

4
L'utilizzo di *argsun numero massimo di argomenti (2 qui) è comunque errato .
Martijn Pieters

1
@MartijnPieters Perché è *argsnecessariamente sbagliato qui? Se le chiamate previste erano (type1)vs (type2, type1), il numero di argomenti è variabile e non esiste un valore predefinito appropriato per l'argomento finale. Perché è importante che ci sia un massimo?
Prassolitico,

1
*argsè davvero lì per zero o più argomenti non coperti, omogenei o per "passare questi intatti". Hai un argomento richiesto e uno opzionale. È totalmente diverso e viene normalmente gestito assegnando al secondo argomento un valore predefinito sentinella per rilevare che è stato omesso.
Martijn Pieters

3
Dopo aver esaminato il PEP, questo chiaramente non è l'uso previsto di @overload. Mentre questa risposta mostra un modo interessante per annotare individualmente i tipi di *args, una risposta ancora migliore alla domanda è che questo non è qualcosa che dovrebbe essere fatto affatto.
Prassolitico il

20

Come breve aggiunta alla risposta precedente, se stai provando a usare mypy su file Python 2 e devi usare commenti per aggiungere tipi invece di annotazioni, devi aggiungere un prefisso ai tipi per argse kwargscon *e **rispettivamente:

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

Questo è trattato da Mypy come lo stesso di seguito, versione Python 3.5 di foo:

def foo(param: bool, *args: str, **kwargs: int) -> None:
    pass
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.