Введите аннотации для *args и **kwargs
Я пробую аннотации типов Python с абстрактными базовыми классами для написания некоторых интерфейсов. Есть ли способ аннотировать возможные типы *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
?
8 ответов
Для переменных позиционных аргументов (*args
) и переменные ключевые слова аргументы (**kw
) вам нужно только указать ожидаемое значение для одного такого аргумента.
Из Списков произвольных аргументов и раздела значений аргументов по умолчанию в PEP Type Hints:
Списки произвольных аргументов также могут быть аннотированы типом, так что определение:
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 или более, и не может быть ограничено подсказками типов для более конкретного диапазона.
Пока не поддерживается
Хотя вы можете аннотировать вариативные аргументы типом, я не считаю это очень полезным, поскольку предполагает, что все аргументы имеют один и тот же тип.
Правильная аннотация типа *args
а также **kwargs
который позволяет указывать каждый вариативный аргумент отдельно, пока не поддерживается mypy. Есть предложение добавитьExpand
помощник на mypy_extensions
модуль, он будет работать так:
class Options(TypedDict):
timeout: int
alternative: str
on_error: Callable[[int], None]
on_timeout: Callable[[], None]
...
def fun(x: int, *, **options: Expand[Options]) -> None:
...
Проблема GitHub была открыта в январе 2018 года, но до сих пор не закрыта. Обратите внимание, что пока проблема в**kwargs
, то Expand
синтаксис, вероятно, будет использоваться для *args
также.
Правильный способ сделать это с помощью @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 вне заглушки.
Вы также можете использовать это для изменения возвращаемого результата таким образом, чтобы было ясно, какие типы аргументов соответствуют какому возвращаемому типу. например:
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))
В качестве краткого дополнения к предыдущему ответу, если вы пытаетесь использовать 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
Я пытаюсь использовать аннотации типов Python с абстрактными базовыми классами для написания некоторых интерфейсов. Есть ли способ аннотировать возможные типы и ... Как аннотировать разумные типы для и
Когда дело доходит до подсказки типа, есть две основные категории использования:
- Написание собственного кода ( который вы можете редактировать и изменять )
- Использование стороннего кода ( который вы не можете изменить или изменить сложно )
У большинства пользователей есть комбинация обоих.
Ответ зависит от того, есть ли у вас однородные типы (т.е. все одного типа) или гетерогенные типы (т.е. разные типы), а также от того, существует ли их фиксированное количество или переменное/неопределенное количество (используемый термин здесь фиксированная и переменная арность )
и иногда использовались в том, что я условно называю " паттерном проектирования, специфичным для Python " (см. ниже). Важно понимать, когда это делается, потому что это влияет на способ ввода подсказки.
Лучшей практикой всегда будет стоять на плечах гигантов :
- Очень рекомендую прочитать и изучить
typeshed
.pyi
stubs, особенно для стандартной библиотеки, чтобы узнать, как разработчики набирают эти вещи в дикой природе.
Для тех, кто хочет, чтобы HOW-TO воплотился в жизнь, рассмотрите возможность голосования за следующие PR:
Случай 1: (Написание собственного кода)
(a) Работа с переменным числом однородных аргументов
Первая причина использования заключается в том, чтобы написать функцию, которая должна работать с переменным (неопределенным) числом однородных аргументов.
Пример: суммирование чисел, прием аргументов командной строки и т. д.
В этих случаях все однородны (т.е. все одного типа).
Пример: В первом случае все аргументы равны s или
float
с; Во втором случае все аргументы равны s.
Также можно использовать s, s, s и s в качестве типа для .
Я утверждаю (без доказательства), что работа с неопределенным числом однородных аргументов была первой причиной, по которой язык Python был введен.
Следовательно, PEP 484 поддерживает предоставление однородного типа.
Примечание:
Использование используется гораздо реже, чем явное указание параметров (т. е. логически в вашей кодовой базе будет гораздо больше функций, которые не используют, чем делают). Использование для однородных типов обычно используется перед их передачей функции.
По возможности рекомендуется вводить параметры явно .
- Если ни для чего другого, вы бы в любом случае документировали каждый аргумент с его типом в строке документации ( отказ от документирования — это быстрый способ заставить других не хотеть использовать ваш код, включая вас в будущем) .
Также обратите внимание, что это кортеж , потому что оператор распаковки (
*
) , чтобы не требовать от пользователей помещения аргументов в контейнервозвращает кортеж , поэтому обратите внимание, что вы не можете изменять напрямую (вам придется вытащить изменяемый объект изargs
).
(b) Написание декораторов и замыканий
Второе место, где это всплывет, — это декораторы. Для этого с помощьюкак описано в, это путь.
(c) Функции верхнего уровня, вызывающие помощники
Это « паттерн проектирования, специфичный для Python », о котором я упоминал. Для , показывают примеры, где вы можете использовать это, чтобы информация о типе сохранялась между вызовами.
- Использование этого способа обычно делается для уменьшения количества кода для написания, особенно. когда аргументы между несколькими функциями одинаковы
- Он также использовался для «поглощения» переменного количества аргументов посредством распаковки кортежа, которые могут не понадобиться в следующей функции.
Здесь элементы имеют разнородные типы и, возможно, их различное количество, и то, и другое может быть проблематичным .
Экосистема типизации Python не позволяет указывать гетерогенные файлы . 1
До появления проверки типов разработчикам нужно было проверять тип отдельных аргументов в (с , и т. д.), если им нужно было сделать что-то по-другому в зависимости от типа:
Примеры:
- Вам нужно напечатать переданные s, но суммировать переданные
int
с
К счастью, разработчики включили вывод типов и для поддержки таких ситуаций. ( Кроме того, существующие кодовые базы не нужно сильно менять, если они уже использовали
assert
,
isintance
и т. д., чтобы определить типы элементов в )
Следовательно, в этом случае вы должны сделать следующее:
- Укажите тип , чтобы его элементы могли быть любого типа, и
- используйте , где это необходимо, с
assert ... is (not) None
,isinstance
,issubclass
и т. д., чтобы определить типы отдельных элементов в
1 Предупреждение:
Для , можно ввести с , но это предназначено для использования при подсказке типа вариативных дженериков . Его не следует использовать для набора текста в общем случае.
был в первую очередь введен, чтобы помочь ввести подсказку
numpy
массивы,tensorflow
тензоры и подобные структуры данных, но дляPython >= 3.11
, его можно использовать для сохранения информации о типе между вызовами документы pythonфункций верхнего уровня, вызывающих вспомогательные функции, как указано выше.Функции, которые обрабатывают гетерогенные данные (а не просто пропускают их), должны по-прежнему использовать сужение типовузкий типсужение типа , чтобы определять типы отдельных элементов.
Для
Python <3.11
,TypeVarTuple
можно получить черезtyping_extensions
, но на сегодняшний день есть только предварительная поддержка через (не ). Также,PEP 646
включает раздел об использовании в качестве кортежа переменной типа .
(a) Работа с переменным числом однородных аргументов
PEP 484
поддерживает ввод всех значений словаря в виде однородного типа. Все ключи автоматическиstr
с.
Например, также можно использоватьUnion
с,TypeAlias
с,Generic
s и s как тип для*kwargs
.
Я не нашел убедительного варианта использования для обработки однородного набора именованных аргументов с использованием**kwargs
.
(b) Написание декораторов и замыканий
Опять же, я бы указал вам наParamSpec
как описано вPEP 612
.
(c) Функции верхнего уровня, вызывающие помощники
Это также « паттерн проектирования, специфичный для Python », о котором я упоминал.
Для конечного набора разнородных типов ключевых слов вы можете использоватьTypedDict
иUnpack
если PEP 692 одобрен.
Однако те же самые вещи для*args
применяется здесь:
- Лучше всего явно ввести аргументы ключевого слова
- Если ваши типы неоднородны и имеют неизвестный размер, введите подсказку с помощью
object
и введите узкий в теле функции
Вариант 2: (Код третьей стороны)
В конечном итоге это сводится к следованию руководящим принципам для части(c)
с вCase 1
.
Outtro
Статические проверки типов
Ответ на ваш вопрос также зависит от используемой вами статической проверки типов. На сегодняшний день (и, насколько мне известно), ваш выбор для проверки статического типа включает:
- : де-факто статическая проверка типов Python.
- : средство проверки статического типа Microsoft.
-
pyre
: проверка статического типа в Facebook/Instagram. -
pytype
: средство проверки статического типа от Google.
Я лично когда-либо использовал только и . Для нихmypy
детская площадка иpyright
площадка — отличное место для проверки шрифта, намекающего на ваш код.
Интерфейсы
ABC, как дескрипторы и метаклассы, являются инструментами для создания фреймворков (1). Если есть шанс, что вы можете превратить свой API из синтаксиса «согласных взрослых» Python в синтаксис «рабства и дисциплины» (если позаимствовать фразу у Рэймонда Хеттингера ), подумайте о YAGNE.
Тем не менее (кроме проповедей), при написании интерфейсов важно учитывать, следует ли вам использовать s илиABC
с.
Протоколы
В ООП протокол — это неформальный интерфейс, определенный только в документации, а не в коде (см. эту обзорную статью Fluent Python, глава 11, автор Лучано Рамальо ). Python перенял эту концепцию из Smalltalk, где протокол представлял собой интерфейс, рассматриваемый как набор методов для выполнения. В Python это достигается за счет реализации конкретных методов dunder, которые описаны в модели данных Python и кратко затронуты здесь.
Протоколы реализуют так называемое структурное подтипирование . В этой парадигме подтип _a определяется его структурой, т. е. поведением), в отличие от номинального подтипа ( т. е. подтип определяется его деревом наследования). Структурное подтипирование также называется статической утиной типизацией по сравнению с традиционной (динамической) утиной типизацией. (Этот термин принадлежит Алексу Мартелли.)
Другим классам не нужно создавать подклассы, чтобы придерживаться протокола: им просто нужно реализовать определенные методы dunder. С подсказкой типа PEP 544 в Python 3.8 представил способ формализовать концепцию протокола. Теперь вы можете создать класс, который наследуется от него, и определить в нем любые функции, которые вы хотите. Пока другой класс реализует эти функции, считается, что он придерживается этогоProtocol
.
азбука
Абстрактные базовые классы дополняют утиную печать и полезны, когда вы сталкиваетесь с такими ситуациями, как:
class Artist:
def draw(self): ...
class Gunslinger:
def draw(self): ...
class Lottery:
def draw(self): ...
Здесь тот факт, что все эти классы реализуютdraw()
may не обязательно означает, что эти объекты взаимозаменяемы (опять же, см. Fluent Python, Ch. 11, Luciano Ramalho)! ABC дает вам возможность сделать четкое заявление о намерениях. Кроме того, вы можете создать виртуальный подкласс ,register
класс, так что вам не нужно создавать из него подклассы (в этом смысле вы следуете принципу GoF «отдавать предпочтение композиции, а не наследованию», не привязывая себя напрямую к ABC).
Рэймонд Хеттингер выступил с отличным докладом об Азбуке в модуле коллекций в своем выступлении на PyCon 2019 Talk.
Кроме того, Алекс Мартелли назвал азбукой гусиной печати . Вы можете создать подклассы многих классов вcollections.abc
, реализуют только несколько методов, а классы ведут себя как встроенные протоколы Python, реализованные с помощью методов dunder.
Лучано Рамальо отлично рассказал об этом и его связи с экосистемой печати в своем выступлении на PyCon 2021 Talk.
Неправильные подходы
предназначен для имитации функционального полиморфизма .
Python изначально не поддерживает функциональный полиморфизм (поддерживает C++ и некоторые другие языки).
- Если вы используете функцию с несколькими сигнатурами, последняя функция
def
'd переопределяет (переопределяет) предыдущие.
- Если вы используете функцию с несколькими сигнатурами, последняя функция
def func(a: int, b: str, c: bool) -> str:
print(f'{a}, {b}, {c}')
def func(a: int, b: bool) -> str:
print(f'{a}, {b}')
if __name__ == '__main__':
func(1, '2', True) # Error: `func()` takes 2 positional arguments but 3 were given
Python имитирует функциональный полиморфизм с необязательными позиционными/ключевыми аргументами (по совпадению, C++ не поддерживает аргументы ключевого слова).
Перегрузки следует использовать, когда
- (1) ввод портированных полиморфных функций C/C++, или
- (2) должна поддерживаться согласованность типов в зависимости от типов, используемых в вызове функции
См. сообщение в блоге Адама Джонсона «Подсказки типов Python — как использовать
@overload
.
Рекомендации
(1) Рамальо, Лучано. Свободный Python (стр. 320). О'Рейли Медиа. Киндл издание.
В некоторых случаях содержимое **kwargs может быть разных типов.
Мне кажется, это работает:
from typing import Any
def testfunc(**kwargs: Any) -> None:
print(kwargs)
или же
from typing import Any, Optional
def testfunc(**kwargs: Optional[Any]) -> None:
print(kwargs)
Если кто-то хочет описать конкретные именованные аргументы, ожидаемые в kwargs, вместо этого можно передать TypedDict, который определяет обязательные и необязательные параметры. Необязательные параметры - это какие были kwargs:
import typing
class RequiredProps(typing.TypedDict):
# all of these must be present
a: int
b: str
class OptionalProps(typing.TypedDict, total=False):
# these can be included or they can be omitted
c: int
d: int
class ReqAndOptional(RequiredProps, OptionalProps):
pass
def hi(req_and_optional: ReqAndOptional):
print(req_and_optional)
TL;DR
def __init__(self, *args, **kwargs): # type: ignore[no-untyped-def]
Мотивация
Это ответ, данный Крисом в комментариях, я не нашел консенсуса в течение 5 минут после просмотра ответов, и для меня не было важно правильно напечатать этот синтаксис Python по умолчанию. Тем не менее я ценюmypy
на моем собственном коде, так что с точки зрения времени это был приемлемый компромисс для меня. Возможно, это поможет кому-то.