Что такое подсказки типа в Python 3.5?
Одна из наиболее обсуждаемых функций Python 3.5 - это подсказки типа.
Пример подсказок типа упоминается в этой статье и этой, а также упоминается ответственное использование подсказок типа. Кто-нибудь может подробнее рассказать о них и когда их следует использовать, а когда нет?
Переведено автоматически
Ответ 1
Я бы посоветовал прочитать PEP 483 и PEP 484 и посмотреть эту презентацию от Гвидо о подсказках по типу.
В двух словах: подсказки по типу - это буквально то, что означают эти слова. Вы указываете тип объекта (ов), который (которые) вы используете.
Из-за динамической природы Python, определить или проверить тип используемого объекта особенно сложно. Этот факт затрудняет понимание разработчиками того, что именно происходит в коде, который они не писали, и, что наиболее важно, для инструментов проверки типов, найденных во многих IDE (на ум приходятPyCharm и PyDev), которые ограничены из-за того, что у них нет какого-либо индикатора того, к какому типу относятся объекты. В результате они прибегают к попытке определить тип с вероятностью успеха около 50% (как упоминалось в презентации).
Возьмем два важных слайда из презентации с подсказками типа:
Зачем вводить подсказки?
- Помогает при проверке типов: указывая, каким типом вы хотите видеть объект, программа проверки типов может легко определить, например, передаете ли вы объект с типом, который не ожидается.
- Помогает с документацией: третий человек, просматривающий ваш код, будет знать, что где ожидается, следовательно, как его использовать, не получая их
TypeErrors
. - Помогает IDE разрабатывать более точные и надежные инструменты: среды разработки будут лучше подходить для предложения соответствующих методов, когда будут знать, к какому типу относится ваш объект. Вероятно, в какой-то момент вы сталкивались с этим с какой-либо IDE, нажимая на
.
и всплывая методы / атрибуты, которые не определены для объекта.
Зачем использовать статические средства проверки типов?
- Быстрее находите ошибки: я полагаю, это самоочевидно.
- Чем больше ваш проект, тем больше он вам нужен: Опять же, имеет смысл. Статические языки обеспечивают надежность и контроль, которых нет в динамических языках. Чем больше и сложнее становится ваше приложение, тем больше контроля и предсказуемости (с точки зрения поведения) вам требуется.
- Большие команды уже проводят статический анализ: я предполагаю, что это подтверждает первые два пункта.
В качестве заключительного замечания к этому небольшому введению: это необязательная функция, и, насколько я понимаю, она была введена для того, чтобы воспользоваться некоторыми преимуществами статической типизации.
Как правило, вам не нужно беспокоиться об этом и определенно не нужно его использовать (особенно в случаях, когда вы используете Python в качестве вспомогательного языка сценариев). Это должно быть полезно при разработке больших проектов, поскольку оно предлагает столь необходимую надежность, контроль и дополнительные возможности отладки.
Подсказки типа с помощью mypy:
Чтобы сделать этот ответ более полным, я думаю, подойдет небольшая демонстрация. Я буду использовать mypy
, библиотеку, которая вдохновила подсказки по типу, поскольку они представлены в PEP. В основном это написано для всех, кто сталкивается с этим вопросом и задается вопросом, с чего начать.
Прежде чем я это сделаю, позвольте мне повторить следующее: PEP 484 ничего не навязывает; он просто задает направление для аннотаций функций и предлагает рекомендации относительно того, как может / должна выполняться проверка типов. Вы можете аннотировать свои функции и намекать на столько вещей, сколько захотите; ваши скрипты все равно будут выполняться независимо от наличия аннотаций, потому что сам Python их не использует.
В любом случае, как отмечено в PEP, типы подсказок обычно должны принимать три формы:
- Аннотации функций (PEP 3107).
- Файлы-заглушки для встроенных / пользовательских модулей.
- Специальные
# type: type
комментарии, дополняющие первые две формы. (Смотрите: Что такое аннотации переменных? для получения# type: type
комментариев к обновлению Python 3.6)
Кроме того, вы захотите использовать подсказки по типу в сочетании с новым typing
модулем, представленным в Py3.5
. В нем определено множество (дополнительных) азбук (абстрактных базовых классов) вместе со вспомогательными функциями и декораторами для использования при статической проверке. Большинство азбук в collections.abc
включены, но в общей форме, чтобы разрешить подписку (путем определения __getitem__()
метода).
For anyone interested in a more in-depth explanation of these, the mypy documentation
is written very nicely and has a lot of code samples demonstrating/describing the functionality of their checker; it is definitely worth a read.
Function annotations and special comments:
First, it's interesting to observe some of the behavior we can get when using special comments. Special # type: type
comments
can be added during variable assignments to indicate the type of an object if one cannot be directly inferred. Simple assignments are
generally easily inferred but others, like lists (with regard to their contents), cannot.
Note: If we want to use any derivative of containers and need to specify the contents for that container we must use the generic types from the typing
module. These support indexing.
# Generic List, supports indexing.
from typing import List
# In this case, the type is easily inferred as type: int.
i = 0
# Even though the type can be inferred as of type list
# there is no way to know the contents of this list.
# By using type: List[str] we indicate we want to use a list of strings.
a = [] # type: List[str]
# Appending an int to our list
# is statically not correct.
a.append(i)
# Appending a string is fine.
a.append("i")
print(a) # [0, 'i']
If we add these commands to a file and execute them with our interpreter, everything works just fine and print(a)
just prints
the contents of list a
. The # type
comments have been discarded, treated as plain comments which have no additional semantic meaning.
By running this with mypy
, on the other hand, we get the following response:
(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"
Указывает на то, что список str
объектов не может содержать int
, который, статически говоря, является звуковым. Это можно исправить, либо придерживаясь типа a
и добавляя str
только объекты, либо изменяя тип содержимого a
, чтобы указать, что любое значение приемлемо (интуитивно выполняется с помощью List[Any]
after Any
has been imported from typing
).
Аннотации функций добавляются в форме param_name : type
после каждого параметра в сигнатуре вашей функции, а возвращаемый тип указывается с помощью -> type
обозначения перед завершающим двоеточием функции; все аннотации хранятся в __annotations__
атрибуте для этой функции в удобной форме словаря. Используя тривиальный пример (который не требует дополнительных типов из typing
модуля):
def annotated(x: int, y: str) -> bool:
return x < y
Атрибут annotated.__annotations__
теперь имеет следующие значения:
{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}
Если мы полный новичок или знакомы с концепциями Python 2.7 и, следовательно, не знаем о TypeError
скрытом в сравнении annotated
, мы можем выполнить другую статическую проверку, перехватить ошибку и избавить нас от некоторых проблем:
(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")
Помимо прочего, вызов функции с недопустимыми аргументами также будет пойман:
annotated(20, 20)
# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"
Они могут быть расширены практически до любого варианта использования, а обнаруживаемые ошибки выходят за рамки базовых вызовов и операций. Типы, которые вы
можете проверить, действительно гибкие, и я просто дал небольшое представление о их потенциале. Загляните в typing
модуль, в
PEPS или в mypy
документацию, которые дадут вам более полное представление о предлагаемых возможностях.
Файлы-заглушки:
Файлы-заглушки могут использоваться в двух разных, не исключающих друг друга случаях:
- Вам нужно ввести check модуль, для которого вы не хотите напрямую изменять сигнатуры функций
- Вы хотите писать модули и проверять типы, но дополнительно хотите отделить аннотации от содержимого.
Файлы-заглушки (с расширением .pyi
) представляют собой аннотированный интерфейс модуля, который вы создаете / хотите использовать. Они содержат сигнатуры функций, которые вы хотите ввести-проверьте, отбросив тело функций. Чтобы получить представление об этом, учитывая набор из трех случайных функций в модуле с именем randfunc.py
:
def message(s):
print(s)
def alterContents(myIterable):
return [i for i in myIterable if i % 2 == 0]
def combine(messageFunc, itFunc):
messageFunc("Printing the Iterable")
a = alterContents(range(1, 20))
return set(a)
Мы можем создать файл-заглушку randfunc.pyi
, в котором мы можем установить некоторые ограничения, если захотим. Недостатком является то, что
кто-то, просматривающий исходный код без заглушки, на самом деле не получит помощи с аннотациями при попытке понять, что должно быть передано
куда.
В любом случае, структура файла-заглушки довольно упрощена: добавьте все определения функций с пустыми телами (pass
заполненными) и предоставьте аннотации на основе ваших требований. Здесь давайте предположим, что мы хотим работать только с int
типами для наших контейнеров.
# Stub for randfucn.py
from typing import Iterable, List, Set, Callable
def message(s: str) -> None: pass
def alterContents(myIterable: Iterable[int])-> List[int]: pass
def combine(
messageFunc: Callable[[str], Any],
itFunc: Callable[[Iterable[int]], List[int]]
)-> Set[int]: pass
В combine
функцию дает представление о том, почему вы можете захотеть использовать аннотации в другом файле, они несколько раз загромождать
код и уменьшить читаемость (большой нет-нет для Python). Вы, конечно, могли бы использовать псевдонимы типов, но иногда это больше сбивает с толку, чем помогает
(поэтому используйте их с умом).
Это должно познакомить вас с основными понятиями подсказок типа в Python. Несмотря на то, что использовалась проверка типов mypy
, вы должны постепенно начать видеть их все чаще во всплывающих окнах, некоторые внутри IDEs (PyCharm,), а другие в качестве стандартных модулей Python.
Я постараюсь добавить дополнительные средства проверки / связанные пакеты в следующий список, когда и если я их найду (или если будет предложено).
Известные мне средства проверки:
- Mypy: как описано здесь.
- PyType: В Google используются обозначения, отличные от того, что я понял, вероятно, стоит посмотреть.
Связанные пакеты / проекты:
- typeshed: Официальный репозиторий Python, содержащий набор файлов-заглушек для стандартной библиотеки.
Проект typeshed
на самом деле является одним из лучших мест, куда вы можете заглянуть, чтобы увидеть, как подсказки типа могут быть использованы в вашем собственном проекте. Давайте возьмем в качестве примера __init__
параметры Counter
класса в соответствующем .pyi
файле:
class Counter(Dict[_T, int], Generic[_T]):
@overload
def __init__(self) -> None: ...
@overload
def __init__(self, Mapping: Mapping[_T, int]) -> None: ...
@overload
def __init__(self, iterable: Iterable[_T]) -> None: ...
Где _T = TypeVar('_T')
используется для определения универсальных классов. Для Counter
класса мы можем видеть, что он может либо не принимать аргументов в своем инициализаторе, либо получать единицу Mapping
из любого типа в an int
или принимать an Iterable
любого типа.
Примечание: Одна вещь, которую я забыл упомянуть, заключалась в том, что typing
модуль был представлен на предварительной основе. Из PEP 411:
API предварительного пакета может быть изменен до "перевода" в "стабильное" состояние. С одной стороны, это состояние предоставляет пакету преимущества формальной принадлежности к дистрибутиву Python. С другой стороны, основная команда разработчиков явно заявляет, что не дает никаких обещаний относительно стабильности API пакета, которая может измениться в следующем выпуске. Хотя это считается маловероятным результатом, такие пакеты могут даже быть удалены из стандартной библиотеки без срока устаревания, если опасения по поводу их API или обслуживания окажутся обоснованными.
Итак, отнеситесь к происходящему со щепоткой соли; я сомневаюсь, что это будет удалено или существенно изменено, но никогда нельзя знать наверняка.
** Совсем другая тема, но допустимая в области подсказок типа: PEP 526
: Синтаксис для аннотаций переменных является попыткой заменить # type
комментарии введением нового синтаксиса, который позволяет пользователям аннотировать тип переменных в простых varname: type
операторах.
Смотрите Что такое аннотации переменных?, как упоминалось ранее, для небольшого ознакомления с ними.
Ответ 2
Дополнение к подробному ответу Джима:
Проверьте typing
модуль - этот модуль поддерживает подсказки типа, как указано в PEP 484.
Например, приведенная ниже функция принимает и возвращает значения типа str
и аннотируется следующим образом:
def greeting(name: str) -> str:
return 'Hello ' + name
Модуль typing
также поддерживает:
- Псевдонимирование типов.
- Подсказки типа для функций обратного вызова.
- Generics - Абстрактные базовые классы были расширены для поддержки подписки на обозначение ожидаемых типов для элементов контейнера.
- Пользовательские универсальные типы - Пользовательский класс может быть определен как универсальный класс.
- Любой тип - Каждый тип является подтипом Any.
Ответ 3
Недавно выпущенный PyCharm 5 поддерживает подсказки типа. В своем сообщении в блоге об этом (см. Подсказки типа Python 3.5 в PyCharm 5) они предлагают отличное объяснение того, какие подсказки типа есть, а какие нет, вместе с несколькими примерами и иллюстрациями того, как использовать их в вашем коде.
Кроме того, он поддерживается в Python 2.7, как описано в этом комментарии:
PyCharm поддерживает модуль ввода из PyPI для Python 2.7, Python 3.2-3.4. Для 2.7 вы должны помещать подсказки типа в заглушки *.pyi, поскольку аннотации функций были добавлены в Python 3.0.
Ответ 4
Подсказки типа предназначены для удобства сопровождения и не интерпретируются Python. В приведенном ниже коде строка def add(self, ic:int)
не приводит к ошибке до следующей return...
строки:
class C1:
def __init__(self):
self.idn = 1
def add(self, ic: int):
return self.idn + ic
c1 = C1()
c1.add(2)
c1.add(c1)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'