Оглавление
Как указать тип аргументов и возвращаемого значения в функции?
Как указать типы при написании классов?
Ожидать, что функции всегда возвращают значение и не могут вернуть None
Указывать значения по умолчанию для изменяемых типов
Забывать указывать тип внутри generics (List, Dict, Set, Tuple)
Описание
Цель типизации — указать разработчику на ожидаемый тип данных при получении или возврате данных из функции или метода. В свою очередь, это позволяет сократить количество багов, ускорить написание кода и улучшить его качество.
typing (тайпинг)
int, str, float и так далее. Любые системные и пользовательские типы в Python.
Шаблоны или generic
специальные типы, в которых можно указывать внутренние типы. Мы с вами познакомились с шаблонами на примере List, Tuple, Dict, Set. Такие типы можно создаваться самостоятельно, об этом подробнее написано в официальной https://docs.python.org/3/library/typing.html#building-generic-types
mypy
это статический анализатор (утилита, которая выполняет статический анализ кода) для Python 3. Если вы используете типизацию в Python, то mypy сможет проверить код и найти распространенные ошибки. Важно помнить, что типизация является лишь подсказками для mypy, даже если mypy завершается с ошибка, программа на Python всё равно запустится.
Статический анализ
процесс, при котором программа проверяет код другой программы без ее запуска. Этот процесс похож на код-ревью. Программа, как будто другой человек, проводит проверку кода и дает рекомендации, что нужно исправить. Подробнее прочитать можно в wiki.
Тайпинги
Цель типизации — указать разработчику на ожидаемый тип данных при получении или возврате данных из функции или метода. В свою очередь, это позволяет сократить количество багов, ускорить написание кода и улучшить его качество.
Синтаксис
Для обозначения базовых типов переменных используются сами типы:
🎯 str
🎯 int
🎯 float
🎯 bool
🎯 и так далее
Как указать тип переменных?
В функции hello объявили 3 переменных: var1 типа int, var2 типа str, var3 типа dict.
def hello():
var1: int = 0
var2: str = ''
var3: dict = {}
Как указать тип аргументов и возвращаемого значения в функции?
Функция greeting принимает переменную name типа str и возвращает строку.
def greeting(name: str) -> str:
return 'Hello ' + name
Как указать типы при написании классов?
Конструктор класса Person (def __init__) принимает переменную name типа str и ничего не возвращает, в Python это означает, что функция возвращает None.
class Person:
def __init__(self, name: str) -> None:
self.name = name
def get_greeting(self) -> str:
buf: str = 'Hello ' + self.name
return buf
Стандартные типы-классы
Стандартные типы-классы, которые необходимы для написания кода: 🎯 Any — любой тип. Любой системный (int, float, ...) и пользовательский тип (class User: ...) соответствует типу Any. Этот тип стоит использовать только в крайних случаях, когда вы уверены, что не можете однозначно определить тип возвращаемых данных. 🎯 Optional[<type>] — type или None. Используется для пометки, что в переменной может быть значение None. Например:
res = {}
var: Optional[str] = res.get("key")
🎯 Union[<type1>, <type2>, ...] — type1 или type2 или ... Используется, если код умеет работать с несколькими типами данных. Распространенный пример — функция умеет работать с целыми и дробными числами:
def sum_int(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
return a + b
Generic
Для описания списков, множеств, словарей подойдут шаблоны или generic. Такие типы могут принимать параметры, которые указывают, какой тип содержится внутри generic. Так, например, List[int] указывает на то, что список состоит только из целочисленных значений.
from typing import List
def func(n: int) -> List[int]:
return list(range(n))
Функция вернет список чисел от 0, ... , n–1
Примеры generics в Python:
🎯 Set[int] — множество (set) из чисел.
🎯 Tupe[str, str] — кортеж ("hi", "hello") из двух строковых переменных.
🎯 Dict[int, str] — словарь, где ключи будут иметь тип int, а значения — тип str.
🎯 Iterable[str] — объекты, которые реализуют метод __iter__, элементами этого итератора являются числа. Таким типом нужно помечать переменные, созданные генераторами или итераторами.
Mypy
Типизация в Python носит необязательный характер. Код может не соблюдать типы, при этом запускаться и работать. Но возникает вероятность появления неожиданных ошибок. Поэтому лучше соблюдать типы и использовать утилиту mypy для проверки корректности использования типов.
Для установки необходимо вызвать:
`python3 -m pip install mypy`
Команда mypy запускает проверку ваших файлов и распечатывать все найденные ошибки. Mypy проверят ваш код статически: это означает, что он будет проверять наличие ошибок, даже не запуская ваш код, анализируя ваш код, словно человек.
Для запуска проверки файла или папки нужно вызвать:
`mypy program.py`
или
`mypy my_directory/`
Команда mypy запускает проверку ваших файлов и распечатывать все найденные ошибки. Mypy проверят ваш код статически: это означает, что он будет проверять наличие ошибок, даже не запуская ваш код, анализируя ваш код, словно человек.
[mypy]
disallow_untyped_defs = True
Этот параметр запрещает объявлять функции без указания типов принимаемых и возвращаемых значений. Подробнее об этом можно прочитать в официальной документации mypy.
Если никак не получается решить ошибку mypy и вы знаете, что делаете, то можно ее проигнорировать:
err_var: int = "" # type: ignore
Частые ошибки
Забыть установить mypy
python3 -m pip install mypy
Ожидать, что функции всегда возвращают значение и не могут вернуть None
Например, получать из словаря с помощью метода get ключи и ожидать, что он не может быть None.
Плохо
from typing import Dict
d: Dict[str, int] = {}
var1: int = d.get("key")
Хорошо
from typing import Dict, Optional
d: Dict[str, int] = {}
var1: Optional[int] = d.get("key")
Указывать значения по умолчанию для изменяемых типов
Плохо
@dataclass
class Person:
first_name: str
last_name: str
children: list = []
В случае запуска такого кода будет вызвано исключение:
ValueError: mutable default <class 'list'> for field children is not allowed: use default_factory
Хорошо
@dataclass
class Person:
first_name: str
last_name: str
children: list = field(default_factory=list)
Забывать указывать тип внутри generics (List, Dict, Set, Tuple)
При создании схемы на основе датакласса выше будет выведена абсолютно неочевидная ошибка:
Плохо
@dataclass
class Person:
first_name: str
last_name: str
children: List
PersonSchema = marshmallow_dataclass.class_schema(Person)
File ".../python3.9/site-packages/marshmallow_dataclass/__init__.py", line 447, in _field_for_generic_type
child_type = field_for_schema(arguments[0], base_schema=base_schema)
IndexError: tuple index out of range
Хорошо
@dataclass
class Person:
first_name: str
last_name: str
children: List[int]