2016-05-04 19 views
24

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

Ad esempio, come si potrebbe esprimere che gli argomenti sensibili di una funzione sono o int o due int s? type(args)Tuple quindi 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)) 

I 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 mypy non così per la chiamata di funzione perché si aspetta che ci sia un tuple nella chiamata stessa. L'aggiunta dopo il disimballaggio fornisce anche un errore di digitazione che non capisco.

Come si annota i tipi sensibili per *args e **kwargs?

risposta

19

Per argomenti variabili posizionali (*args) e argomenti chiave variabili (**kw) si solo bisogno di specificare il valore atteso per un tale argomento.

Dal Arbitrary argument lists and default argument values section del tipo Hints PEP:

liste di argomenti arbitrari possono anche essere di tipo annotati, in modo che la definizione:

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

è accettabile e ciò significa che, Ad esempio, tutti i seguenti rappresentano chiamate di funzione con tipi di argomenti validi:

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

Così ci si vuole specificare il metodo come questo:

def foo(*args: int): 

Tuttavia, se la funzione può accettare solo uno o due valori interi, non si dovrebbe usare *args a tutti, utilizzare uno esplicito posizionale discussione e un secondo argomento parola chiave:

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

Ora la vostra funzione è in realtà limitato a uno o due argomenti, ed entrambi devono essere interi se specificato. *argssempre significa 0 o più e non può essere limitato da suggerimenti tipo a un intervallo più specifico.

+0

Solo curioso, perché aggiungere l'opzione? Qualcosa è cambiato in Python o hai cambiato idea? Non è ancora strettamente necessario a causa dell'impostazione 'None'? – Praxeolitic

+3

@Praxeolitic sì, in pratica l'annotazione "Optional" automatica implicita quando si utilizza 'None' come valore predefinito reso più sicuro alcuni casi d'uso e che ora viene rimosso dal PEP. –

+0

[Ecco un collegamento che discute di questo] (https://github.com/python/typing/issues/275) per gli interessati. Sicuramente suonerà come esplicito che "Optional" sarà richiesto in futuro. –

8

Come una breve aggiunta alla risposta precedente, se si sta cercando di utilizzare mypy su Python 2 file e la necessità di utilizzare i commenti per aggiungere tipi invece di annotazioni, è necessario anteporre i tipi per args e kwargs con * e ** rispettivamente:

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

Questo è trattata da mypy come essendo uguale al seguito, Python 3.5 versione di foo:

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

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 aggiunge @overload o annotazioni di tipo alla realizzazione vera e propria, che deve venire scorso.

Avrete bisogno di una versione nuova di entrambi typing e di mypy per ottenere supporto per @overload outside of stub files.

Si può anche usare questo per variare il risultato restituito in un modo che rende espliciti quali tipi di argomenti corrispondono a quale tipo di ritorno. ad 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)) 
+1

Mi piace questa risposta perché affronta il caso più generale. Guardando indietro, non avrei dovuto usare le chiamate di funzione '(tipo1)' vs '(tipo1, tipo1)' come esempio. Forse '(type1)' vs '(type2, type1)' sarebbe stato un esempio migliore e mostra perché mi piace questa risposta. Questo consente anche diversi tipi di ritorno. Tuttavia, nel caso particolare in cui si ha solo un tipo di ritorno e il proprio * * args' e '* kwargs' sono tutti dello stesso tipo, la tecnica nella risposta di Martjin ha più senso in modo che entrambe le risposte siano utili. – Praxeolitic

+1

L'uso di '* args' dove c'è un numero massimo di argomenti (2 qui) è * ancora sbagliato * comunque. –

+0

Quindi, sì, è bene sapere su '@ overload', ma è lo strumento sbagliato * per questo specifico lavoro *. –

Problemi correlati