Python

Если вы работаете над большим проектом на Python или предпочитаете лаконичную и понятную базу кода, то Pytype станет для вас отличным помощником.

Python — это отличный язык программирования для создания скриптов и прототипов. Емкий синтаксис, гибкая система типов и хорошая интерпретируемость помогают быстро опробовать идею, скорректировать ее и попробовать еще раз.

В процессе роста проекта гибкость, которая когда-то обеспечивала быстроту работы, становится тяжким бременем, замедляющим разработку. Когда к проекту подключаются другие разработчики, пишется больше кода. А нехватка информации по типам осложняет чтение и понимание кода. Без системы проверки типов легко допустить ошибки. Но вот отловить их будет не так-то просто.

Pytype спешит на помощь! Pytype — это open-source инструмент для проверки и вывода типов в Python. К тому же, это уже готовое решение — установите и приступайте к работе!

Pytype может:

  1. Статистически выводить информацию о типах и проверять код на ошибки в типах.
  2. Проверять согласованность аннотаций типов PEP 484.
  3. При необходимости возвращать выведенную информацию о типах.

Если вы уже заинтересовались, то установите Pytype и следуйте инструкциям. Ниже приведены полезные примеры использования.

Пример 1: Вывод и проверка типа

Это самый популярный сценарий. Вы пишите код и хотите проверить его на наличие ошибок. Присмотритесь к этой функции:

import re
def GetUsername(email_address):
  match = re.match(r'([^@]+)@example\.com', email_address)
  return match.group(1)

Тут все понятно. Она использует регулярное выражение для извлечения части адреса электронной почты до @ и возвращает его. А вы заметили ошибку?

Давайте посмотрим, что нам скажет pytype :

% pytype get_username.py
Analyzing 1 sources with 0 dependencies
File "/.../get_username.py", line 5, in GetUsername: No attribute 'group' on None [attribute-error]
  In Optional[Match[str]]

Pytype говорит нам, что group не является допустимым вызовом функции для match. При отсутствии совпадений re.match() возвращает None. И действительно, в данных случаях match.group(1) выбросит исключение.

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

import re
def GetUsername(email_address):
  match = re.match(r'([^@]+)@example\.com', email_address)
  if match is None:
    return None
  return match.group(1)  # <-- Here, match can't be None

Теперь, когда мы вновь запустим pytype, ошибка не обнаружится. Pytype предполагает, что при выполнении кода после if, match точно не окажется None.

Пример 2: Проверка аннотации типов

В Python 3 вам доступна аннотация типов (PEP 484). Так вы сможете лучше донести свою мысль до средств проверки типов и других разработчиков. Pytype предупреждает об ошибках в аннотации типов:

import re
from typing import Match
def GetEmailMatch(email) -> Match:
  return re.match(r'([^@]+)@example\.com', email)

Давайте проверим этот фрагмент кода через pytype:

% pytype example.py
Analyzing 1 sources with 0 dependencies
File "/.../example.py", line 5, in GetEmailMatch: bad option in return type [bad-return-type]
  Expected: Match
  Actually returned: None

Pytype сообщает, что GetEmailMatch может вернуть None. Но мы задали его возвращаемый тип как Match. Для исправления ошибки можно прибегнуть к аннотации типа Optional из модуля typing:

import re
from typing import Match, Optional
def GetEmailMatch(email) -> Optional[Match]:
  return re.match(r'([^@]+)@example\.com', email)

Optional означает, что возвращаемое значение может быть объектом Match или None.

Пример 3: Возвращение информации о выведенных типах

Чтобы лучше освоиться в аннотациях типов, Pytype может добавлять их в сам код. Давайте рассмотрим этот фрагмент:

import re
  
def GetEmailMatch(email):
  return re.match(r'([^@]+)@example\.com', email)
def GetUsername(email_address):
  match = GetEmailMatch(email_address)
  if match is None:
    return None
  return match.group(1)

Перед добавлением аннотации типов сначала запускаем pytype для файла. pytype сохраняет информацию о выведенном типе в файле.pyi. Затем мы можем вернуть аннотации типов в код через запуск merge-pyi:

% pytype email.py
% merge-pyi -i email.py pytype_output/email.pyi

Вуаля!

import re
from typing import Match
from typing import Optional
def GetEmailMatch(email) -> Optional[Match[str]]:
  return re.match(r'([^@]+)@example\.com', email)
def GetUsername(email_address) -> Optional[str]:
  match = GetEmailMatch(email_address)
  if match is None:
    return None
  return match.group(1)

Аннотации типов, включая выражения import, теперь присутствуют в исходном файле.

Перевод статьи Ehud Tamir: How to quickly find type-issues in your Python code with Pytype