Estou tentando anotações de tipo do Python com classes base abstratas para escrever algumas interfaces. Existe uma maneira de anotar os possíveis tipos de *args
e **kwargs
?
Por exemplo, como alguém expressaria que os argumentos sensíveis a uma função são um int
ou dois int
s? type(args)
dá, Tuple
então meu palpite era anotar o tipo como Union[Tuple[int, int], Tuple[int]]
, mas isso não funciona.
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))
Mensagens de erro 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]]"
Faz sentido que mypy não goste disso na chamada de função porque espera que exista um tuple
na própria chamada. A adição após a descompactação também gera um erro de digitação que eu não entendo.
Como se anota os tipos sensíveis para *args
e **kwargs
?
fonte
Optional
? Algo mudou no Python ou você mudou de idéia? Ainda não é estritamente necessário devido aoNone
padrão?Optional
anotação automática implícita quando você usaNone
como valor padrão tornou certos casos de uso mais difíceis e que agora estão sendo removidos do PEP.Optional
que será necessário no futuro.Callable
não suporta qualquer menção de uma dica de tipo para*args
ou**kwargs
ponto final . Essa questão específica é sobre a marcação de chamadas que aceitam argumentos específicos mais um número arbitrário de outras pessoas e, portanto*args: Any, **kwargs: Any
, usam uma dica de tipo muito específica para os dois exemplos. Nos casos em que você define*args
e / ou**kwargs
algo mais específico, você pode usar aProtocol
.A maneira correta de fazer isso é usando
@overload
Observe que você não adiciona
@overload
ou digita anotações na implementação real, que deve vir por último.Você precisará de uma versão nova
typing
e de mypy para obter suporte para @overload fora dos arquivos stub .Você também pode usar isso para variar o resultado retornado de uma maneira que explique quais tipos de argumento correspondem a qual tipo de retorno. por exemplo:
fonte
(type1)
vs(type1, type1)
como meu exemplo. Talvez(type1)
vs(type2, type1)
teria sido um exemplo melhor e mostra por que eu gosto dessa resposta. Isso também permite diferentes tipos de retorno. No entanto, no caso especial em que você só tem um tipo de retorno e seu*args
e*kwargs
são todos do mesmo tipo, a técnica na resposta de Martjin faz mais sentido, portanto as duas respostas são úteis.*args
onde há um número máximo de argumentos (2 aqui) ainda está errado .*args
necessariamente errado aqui? Se as chamadas esperadas foram(type1)
vs(type2, type1)
, o número de argumentos é variável e não há um padrão apropriado para o argumento à direita. Por que é importante que haja um máximo?*args
existe realmente para zero ou mais argumentos não limitados e homogêneos ou para "transmitir esses argumentos intactos". Você tem um argumento necessário e um opcional. Isso é totalmente diferente e normalmente é tratado, atribuindo ao segundo argumento um valor padrão sentinela para detectar que foi omitido.*args
, uma resposta ainda melhor para a pergunta é que isso não é algo que deva ser feito.Como uma pequena adição à resposta anterior, se você estiver tentando usar o mypy em arquivos Python 2 e precisar usar comentários para adicionar tipos em vez de anotações, precisará prefixar os tipos para
args
ekwargs
com*
e**
respectivamente:Isso é tratado por mypy como sendo o mesmo da versão Python 3.5 abaixo
foo
:fonte