Python typing list of lists

Аннотации типов коллекций

На предыдущем занятии мы с вами увидели, как можно аннотировать отдельные переменные и функции простыми базовыми типами:

Давайте теперь посмотрим, как делать аннотацию для коллекций языка Python:

Аннотация списков

Казалось бы, нет ничего проще. Достаточно также указать нужный нам тип, например, у переменной:

и аннотация готова. И в целом да. В самом простом варианте мы так вполне можем сделать. Но обратите внимание, в этой аннотации нет никаких указаний на тип элементов списка. Поэтому можно прописать и числа, и строки:

и вообще любые типы данных. Интегрированная среда нам не выдаст никаких предупреждений. А вот если вместо списка записать, например, кортеж:

то тогда уже увидим подсветку с предупреждением. То есть, тип list в аннотации отвечает только за определение списка. Типы элементов, при этом, могут быть любыми.

Но что если нам нужно дополнительно в аннотации указать и тип элементов списка? Начиная с версии Python 3.9, это можно сделать с помощью все того же базового типа list следующим образом:

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

Если указать другой тип, например, str:

Увидим подсветку кода. Но, как только в списке оказывается хотя бы один строковый элемент:

подсветка пропадает. Вот это особенность работы данной аннотации в PyCharm. Но, забегая вперед, скажу, что для языка Python есть довольно полезный модуль mypy, с помощью которого можно строже оценивать корректность программы для приведенной аннотации типов. И он находит все несоответствия. О модуле mypy речь пойдет в следующем занятии.

Вернемся к нашей нотации списка list[str]. Записывать ее в таком виде можно только, начиная с версии Python 3.9 и выше. Если версия вашего интерпретатора ниже, то для достижения того же результата придется импортировать из модуля typing следующие типы:

from typing import List, Tuple, Dict, Set

а, затем, вместо list прописать List:

Эта конструкция работает абсолютно так же, как и типизация через list. Но List и другие подобные типы считаются устаревшими и в новых версиях лучше избегать такого способа обозначения.

Аннотация кортежей

С кортежами все в целом так же, как и со списками. Единственное ключевое отличие – это независимое указание типов для всех элементов кортежа. Например:

address: tuple[int, str] = (1, 'proproprogs.ru')

Возможно, здесь у вас возникает вопрос: почему у списка мы указываем один тип для всех элементов, а у кортежа для каждого? Как вы помните, кортеж – это неизменяемый тип данных. В частности, мы не можем добавлять или удалять элементы. Поэтому их число полагается известным и фиксированным. Часто кортежи в Python используются для группировки каких-либо данных в единый объект. Например, информация по книге может быть представлена в виде кортежа:

book: tuple[str, str, int] book = ('Балакирев С.М.', 'Аннотация типов', 2022)

И мы наперед знаем какие данные и в каком порядке предполагается хранить. Поэтому прописать аннотацию для всех его элементов не представляет особого труда. И в дальнейшем она будет служить хорошей подсказкой для программиста.

Конечно, если вам понадобится аннотировать кортеж с произвольным числом элементов, то это можно сделать так:

И тогда все следующие строчки:

elems = (1,) elems = (1, 2) elems = (1, 2, 3)

будут корректны для этой аннотации.

Я думаю, вы уже догадались, что для версий языка Python ниже 3.9 следует вместо tuple использовать тип Tuple, если мы хотим дополнительно указывать тип данных:

book: Tuple[str, str, int] book = ('Балакирев С.М.', 'Аннотация типов', 2022)

Но с версии 3.9 лучше использовать стандартный тип tuple с квадратными скобками.

Аннотация словарей и множеств

Аналогичным образом выполняется аннотация словарей. Так как словари – изменяемый тип данных и число их элементов наперед не определено, то при аннотировании указываются лишь тип ключа и тип значения. Например:

words: dict[str, int] = {'one': 1, 'two': 2}

Или, для версий Python ниже 3.9:

words: Dict[str, int] = {'one': 1, 'two': 2}

То есть, эта аннотация ведет себя так же, как и при аннотировании списков.

И то же самое для множеств:

persons: set[str] = {'Сергей', 'Михаил', 'Наталья'}
persons: Set[str] = {'Сергей', 'Михаил', 'Наталья'}

Здесь также указывается единый тип для всех элементов множества. Хотя подсветка будет появляться только тогда, когда все элементы множества или словаря не соответствуют указанному типу. Но модуль mypy находит все эти несоответствия.

Комбинирование типов

Разумеется, все приведенные способы типизации можно использовать не только для отдельных переменных, но и при объявлении функций. Например, так:

def get_positive(digits: list[int]) -> list[int]: return list(filter(lambda x: x > 0, digits)) print(get_positive([1, -2, 3, 4, -5]))

И, смотрите, в аннотации указан один тип данных int, который ожидается увидеть у элементов списка. А что если мы собираемся хранить в списке и целые и вещественные числа. Как тогда определить аннотацию? Все очень просто. Для этого нам понадобится определить составной тип, используя Union из модуля typing:

def get_positive(digits: list[Union[int, float]]) -> list[Union[int, float]]: return list(filter(lambda x: x > 0, digits))

То есть, внутри квадратных скобок можно прописывать любые типы, образуя вложенные конструкции. Я напомню, что начиная с версии Python 3.10, эту же нотацию можно определить и так:

Такой вариант рекомендуемый, если позволяет версия.

Давайте еще немного усложним аннотацию. Предположим, что по умолчанию параметр digits должен принимать значение None. Мы уже знаем, что для этого следует воспользоваться типом Optional модуля typing. Получим:

def get_positive(digits: Optional[list[Union[int, float]]] = None) -> list[Union[int, float]]: return list(filter(lambda x: x > 0, digits))

И такие комбинации можно делать сколь угодно глубокие. Только здесь возникает проблема с визуальным восприятием таких конструкций. Чтобы было нагляднее можно воспользоваться алиасами (переменными на те или иные типы). Например, вначале объявить алиас:

А, затем, прописать его при аннотировании:

def get_positive(digits: Optional[list[Digit]] = None) -> list[Digit]: return list(filter(lambda x: x > 0, digits))

Так воспринимается гораздо лучше. Разумеется, алиасы можно было бы и дальше определять, например:

def get_positive(digits: Optional[ListDigits] = None) -> ListDigits: return list(filter(lambda x: x > 0, digits))

Но увлекаться этим не стоит. Иначе, программисту постоянно придется искать определения алиасов и смотреть, что за типы в них прописаны. Во всем должна быть мера.

Аннотации вызываемых объектов (Callable)

В заключение этого занятия отмечу еще один тип Callable из модуля typing, который позволяет аннотировать вызываемые объекты. Часто это обычные функции, которые передаются как параметры. Давайте я приведу следующий пример. Пусть у нас имеется функция, которая из ряда целых чисел указанного диапазона выбирает числа по заданному критерию и формирует список:

def get_digits(flt: Callable[[int], bool], lst: Optional[list[int]] = None) -> list[int]: if lst is None: return [] return list(filter(flt, lst))

Здесь первый параметр отмечен как вызываемый тип (Callable), у которого ожидается целочисленный аргумент и булево возвращаемое значение. Затем, мы можем воспользоваться этой функцией следующим образом:

def even(x): return bool(x % 2 == 0) print(get_digits(even, [1, 2, 3, 4, 5, 6, 7]))

Увидим отображение списка только из четных значений. Но если поменять функцию even так, чтобы она возвращала не булево значение, а строку:

def even(x): return str(x % 2 == 0)

То появится подсветка кода из-за несоответствия возвращаемого типа и того, что указан в аннотации.

В общем случае тип Callable описывается по синтаксису:

Callable[[TypeArg1, TypeArg2, …], ReturnType]

Например, если нужно аннотировать функцию, у которой нет параметров и она ничего не возвращает:

def hello_callable(): print("Hello Callable")

То это будет выглядеть так:

Надеюсь, из этого занятия вы поняли, как выполнять аннотирование различных коллекций языка Python.

Видео по теме

#1. Первое знакомство с Python Установка на компьютер

#2. Варианты исполнения команд. Переходим в PyCharm

#3. Переменные, оператор присваивания, функции type и id

#4. Числовые типы, арифметические операции

#5. Математические функции и работа с модулем math

#6. Функции print() и input(). Преобразование строк в числа int() и float()

#7. Логический тип bool. Операторы сравнения и операторы and, or, not

#8. Введение в строки. Базовые операции над строками

#9. Знакомство с индексами и срезами строк

#11. Спецсимволы, экранирование символов, row-строки

#12. Форматирование строк: метод format и F-строки

#13. Списки — операторы и функции работы с ними

#14. Срезы списков и сравнение списков

#15. Основные методы списков

#16. Вложенные списки, многомерные списки

#17. Условный оператор if. Конструкция if-else

#18. Вложенные условия и множественный выбор. Конструкция if-elif-else

#19. Тернарный условный оператор. Вложенное тернарное условие

#21. Операторы циклов break, continue и else

#22. Оператор цикла for. Функция range()

#23. Примеры работы оператора цикла for. Функция enumerate()

#24. Итератор и итерируемые объекты. Функции iter() и next()

#25. Вложенные циклы. Примеры задач с вложенными циклами

#26. Треугольник Паскаля как пример работы вложенных циклов

#27. Генераторы списков (List comprehensions)

#28. Вложенные генераторы списков

#29. Введение в словари (dict). Базовые операции над словарями

#30. Методы словаря, перебор элементов словаря в цикле

#31. Кортежи (tuple) и их методы

#32. Множества (set) и их методы

#33. Операции над множествами, сравнение множеств

#34. Генераторы множеств и генераторы словарей

#35. Функции: первое знакомство, определение def и их вызов

#36. Оператор return в функциях. Функциональное программирование

#37. Алгоритм Евклида для нахождения НОД

#38. Именованные аргументы. Фактические и формальные параметры

#39. Функции с произвольным числом параметров *args и **kwargs

#40. Операторы * и ** для упаковки и распаковки коллекций

#42. Анонимные (lambda) функции

#43. Области видимости переменных. Ключевые слова global и nonlocal

#45. Введение в декораторы функций

#46. Декораторы с параметрами. Сохранение свойств декорируемых функций

#47. Импорт стандартных модулей. Команды import и from

#48. Импорт собственных модулей

#49. Установка сторонних модулей (pip install). Пакетная установка

#50. Пакеты (package) в Python. Вложенные пакеты

#51. Функция open. Чтение данных из файла

#52. Исключение FileNotFoundError и менеджер контекста (with) для файлов

#53. Запись данных в файл в текстовом и бинарном режимах

#55. Функция-генератор. Оператор yield

#56. Функция map. Примеры ее использования

#57. Функция filter для отбора значений итерируемых объектов

#58. Функция zip. Примеры использования

#59. Сортировка с помощью метода sort и функции sorted

#60. Аргумент key для сортировки коллекций по ключу

#61. Функции isinstance и type для проверки типов данных

#62. Функции all и any. Примеры их использования

#63. Расширенное представление чисел. Системы счисления

#64. Битовые операции И, ИЛИ, НЕ, XOR. Сдвиговые операторы

#65. Модуль random стандартной библиотеки

#66. Аннотация базовыми типами

#67. Аннотации типов коллекций

#68. Аннотации типов на уровне классов

#69. Конструкция match/case. Первое знакомство

#70. Конструкция match/case с кортежами и списками

#71. Конструкция match/case со словарями и множествами

#72. Конструкция match/case. Примеры и особенности использования

© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта

Источник

Читайте также:  Sqlite fetch array php
Оцените статью