2016-05-04 4 views
24

Я пытаюсь аннотации типов Python с абстрактными базовыми классами писать некоторые интерфейсы. Есть ли способ аннотировать возможные типы *args и **kwargs?Аннотации типов для * args и ** kwargs

Например, как можно выразить, что разумные аргументы функции являются либо int, либо двумя int s? type(args) дает Tuple, поэтому я предполагал, что аннотировать этот тип как Union[Tuple[int, int], Tuple[int]], но это не сработает.

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

Сообщения об ошибках 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]]" 

Это имеет смысл, что mypy не нравится это для вызова функции, потому что он ожидает, что там будет в самом вызову tuple. Добавление после распаковки также дает ошибку ввода, которую я не понимаю.

Как аннотировать чувствительные типы для *args и **kwargs?

ответ

19

Для переменных позиционных аргументов (*args) и аргументов переменного ключевого слова (**kw) вам нужно только указать ожидаемое значение для один такого аргумента.

Из Arbitrary argument lists and default argument values section из Типа подсказка PEP:

списков Произвольного аргумента могут также быть типа аннотированных, так что определение:

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

является приемлемым, и это означает, что, например, все следующие представляют вызовы функций с допустимыми типами аргументов:

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

Так вы хотите указать свой метод как это:

def foo(*args: int): 

Однако, если ваша функция может принимать только один или два целочисленных значений, вы не должны использовать *args вообще, используйте один явный позиционная аргумент и второй аргумент ключевое слово:

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

Теперь ваша функция фактически ограничивается одним или двумя аргументами, и оба должны быть целыми числами, если это указано. *argsвсегда означает 0 или более и не может быть ограничен подсказками типа для более конкретного диапазона.

+0

Любопытно, зачем добавлять 'Факультативный'? Что-то изменилось в Python или вы передумали? Это по-прежнему не является строго необходимым из-за «Нет» по умолчанию? – Praxeolitic

+3

@Praxeolitic да, на практике автоматическая подразумеваемая аннотация «Необязательный», когда вы используете значение «Нет» в качестве значения по умолчанию, усложняло некоторые неудобства и теперь удаляется из PEP. –

+0

[Вот ссылка, обсуждающая это] (https://github.com/python/typing/issues/275) для заинтересованных. В будущем это, безусловно, звучит как явный «Факультативный». –

8

В коротком дополнение к предыдущему ответу, если вы пытаетесь использовать mypy на Python 2 файлов и нужно использовать комментарии для добавления типов вместо аннотации, вам нужно префикс типа для args и kwargs с * и ** соответственно:

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

Это лечится mypy как к тому же, как ниже, Python 3.5 версия foo:

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

Правильный способ сделать это с помощью @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)) 

Обратите внимание, что вы не добавить @overload или ввести аннотации к фактической реализации, которые должны прийти в последнюю очередь.

Вам понадобится новая версия typing и mypy, чтобы получить поддержку для @overload outside of stub files.

Вы также можете использовать это, чтобы изменить возвращаемый результат таким образом, чтобы явные типы аргументов соответствовали типу возвращаемого типа. например:

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

Мне нравится этот ответ, потому что он касается более общего случая. Оглядываясь назад, я не должен был использовать '(type1)' vs '(type1, type1)' вызовы функций в качестве моего примера. Может быть, '(type1)' vs '(type2, type1)' был бы лучшим примером и показывает, почему мне нравится этот ответ. Это также позволяет использовать разные типы возвращаемых данных. Однако в специальном случае, когда у вас есть только один тип возвращаемого значения, и ваши '* args' и' * kwargs' имеют одинаковый тип, метод в ответе Марджина имеет больше смысла, поэтому оба ответа полезны. – Praxeolitic

+1

Использование '* args', где максимальное количество аргументов (2 здесь), * все еще неверно. –

+0

Итак, да, хорошо знать о '@ overload', но это неправильный инструмент * для этого конкретного задания *. –

Смежные вопросы