Типизированный Python для профессиональной разработки [Алексей Голобурдин] (pdf) читать постранично, страница - 16
Книга в формате pdf! Изображения и текст могут не отображаться!
[Настройки текста] [Cбросить фильтры]
молодого юзера должна принимать на вход именно список юзеров? Ведь по сути
главное, чтобы просто можно было проитерироваться по пользователям. Может, мы
захотим потом передать сюда не список пользователей, а кортеж с пользователями,
или еще что-то? Если мы передадим вместо списка кортеж — будет ошибка типов
сейчас:
from datetime import datetime
from dataclasses import dataclass
@dataclass
class User:
birthday: datetime
users = (
# сменили на tuple
User(birthday=datetime.fromisoformat("1988-01-01")),
User(birthday=datetime.fromisoformat("1985-07-29")),
User(birthday=datetime.fromisoformat("2000-10-10"))
)
def get_younger_user(users: list[User]) -> User:
"""Возвращает самого молодого пользователя из списка"""
sorded_users = sorted(users, key=lambda x: x.birthday)
return sorded_users[0]
print(get_younger_user(users))
# тут видна ошибка в pyright!
Код работает (повторимся, что интерпретатор не проверяет типы в type hinting), но
проверка типов в редакторе (и mypy) ругается, это нехорошо.
Если мы посмотрим документацию по функции sorted, то увидим, что первый
элемент там назван iterable, то есть итерируемый, то, по чему можно
проитерироваться. То есть мы можем передать любую итерируемую структуру:
from typing import Iterable
def get_younger_user(users: Iterable[User]) -> User | None:
if not users: return None
sorded_users = sorted(users, key=lambda x: x.birthday)
return sorded_users[0]
И теперь всё в порядке. Мы можем передать любую итерируемую структуру,
элементами которой являются экземпляры User.
А если нам надо обращаться внутри функции по индексу к элементам
последовательности? Подойдёт ли Iterable? Нет, так как Iterable подразумевает
возможность итерироваться по контейнеру, то есть обходить его в цикле, но это не
предполагает обязательной возможности обращаться по индексу. Для этого есть
Sequence:
from typing import Sequence
def get_younger_user(users: Sequence[User]) -> User | None:
"""Возвращает самого молодого пользователя из списка"""
if not users: return None
print(users[0])
sorded_users = sorted(users, key=lambda x: x.birthday)
return sorded_users[0]
Теперь всё в порядке. В Sequence можно обращаться к элементам по индексу.
Ещё один важный вопрос тут. А зачем использовать Iterable или Sequence, если
можно просто перечислить разные типы контейнеров? Ну их же ограниченное
количество — там list, tuple, set, dict. Для чего нам тогда общие типы Iterable и
Sequence?
На самом деле таких типов контейнеров, по которым можно итерироваться, вовсе не
ограниченное число. Например, можно создать свой контейнер, по которому можно
будет итерироваться, но при этом этот тип не будет наследовать ничего из
вышеперечисленного типа list, dict и тп:
from typing import Sequence
class Users:
def __init__(self, users: Sequence[User]):
self._users = users
def __getitem__(self, key: int) -> User:
return self._users[key]
users = Users((
# сменили на tuple
User(birthday=datetime.fromisoformat("1988-01-01")),
User(birthday=datetime.fromisoformat("1985-07-29")),
User(birthday=datetime.fromisoformat("2000-10-10"))
))
for u in users:
print(u)
Способов создать такую структуру, по которой можно итерироваться или
обращаться по индексам, в Python много, это один из способов. Важно просто
понимать, что если вам надо показать структуру, по которой, например, можно
итерироваться, то не стоит ограничивать набор таких структур простым
перечислением списка, кортежа и чего-то ещё. Используйте обобщённые типы,
созданные специально для этого, например, Iterable или Sequence, потому что они
покроют действительно всё, в том числе и свои кастомные (самописные)
реализации контейнеров.
Ну и напоследок — как определить тип словаря, ключами которого являются строки,
а значениями, например, объекты типа User:
some_users_dict: dict[str, User] = {
"alex": User(birthday=datetime.fromisoformat("1990-01-01")),
"petr": User(birthday=datetime.fromisoformat("1988-10-23"))
}
И также, если нет смысла ограничиваться именно словарём и подойдёт любая
структура, к которой можно обращаться по ключам — то есть обобщённый тип
Mapping:
from typing import Mapping
def smth(some_users: Mapping[str, User]) -> None:
print(some_users["alex"])
smth({
"alex": User(birthday=datetime.fromisoformat("1990-01-01")),
"petr": User(birthday=datetime.fromisoformat("1988-10-23"))
})
Пару слов стоит сказать про кортежи, если размер кортежа важен и мы хотим его
прямо указать в типе, то это можно сделать так:
three_ints = tuple[int, int, int]
Если количество элементов неизвестно — можно так:
tuple_ints = tuple[int, ...]
Дженерики
Что если мы хотим написать обобщённую функцию, которая принимает на вход
итерируемую структуру, то есть структуру, по которой можно итерироваться, и
возвращает результат первой итерации?
from typing import TypeVar, Iterable
T = TypeVar("T")
def first(iterable: Iterable[T]) -> T | None:
for element in iterable:
return element
print(first(["one", "two"])) # one
print(first((100, 200))) # 200
Как видите, типом данных в этой итерируемой структуре iterable могут быть любые
данные, а наши type hinting в функции first говорят буквально, что функция
принимает на вход итерабельную структуру данных, каждый элемент которой имеет
тип T, и функция возвращает тот же тип T. Тип T при этом может быть любым.
Это так называемые дженерики, то есть обобщённые типы.
Причём имя T здесь это пример, он часто используется именно так, T, от Type, но
название типа может быть и любым
Последние комментарии
18 часов 50 минут назад
19 часов 4 минут назад
20 часов 12 минут назад
1 день 7 часов назад
1 день 7 часов назад
1 день 8 часов назад