Чистыйкод дляпродолжающи х
Скачать 7.85 Mb.
|
: int) -> str: if number % 2 == 1: return 'An odd number. ' elif number == 42: return 'The answer. ' else: return 'Yes, that is a number. ' myLuckyNumber: int = 42 print(describeNumber(myLuckyNumber)) Как видите, для параметров или переменных аннотация типа использует двоеточие для отделения имени от типа, тогда как для возвращаемых значений закрывающая круглая скобка команды def отделяется от типа стрелкой ( -> ). Аннотации типов Аннотации типов 227 функции describeNumber() показывают, что функция получает целое число в па- раметре number и возвращает строковое значение. Если вы используете аннотации типов, вам не нужно применять их к каждому зна- чению данных в программе. Вместо этого можно воспользоваться методом посте- пенной типизации, сочетающим гибкость динамической типизации с безо пасностью статической типизации: аннотации типов включаются только для некоторых пере- менных, параметров и возвращаемых значений. Но чем больше аннотаций типов появляется в вашей программе, тем больше информации получат средства стати- ческого анализа кода для выявления потенциальных ошибок в вашей программе. Обратите внимание: в этом примере имена заданных типов совпадают с именами функций-конструкторов int() и str() . В Python термины «класс», «тип» и «тип данных» имеют одинаковый смысл. Для любых экземпляров, созданных на основе классов, имя класса используется как его тип: import datetime noon: datetime.time = datetime.time(12, 0, 0) ❶ class CatTail: def __init__(self, length: int, color: str) -> None: self.length = length self.color = color zophieTail: CatTail = CatTail(29, 'grey') ❷ Переменная noon снабжена аннотацией типа datetime.time ❶ , потому что это объект time (определенный в модуле datetime ). Аналогичным образом объект zophieTail снабжен аннотацией типа CatTail ❷ , потому что это объект класса CatTail , создан- ного командой class . Аннотации типов автоматически распространяются на все субклассы заданного типа. Например, переменной с аннотацией типа dict можно присвоить не только любое значение словаря, но и любое из значений collections. OrderedDict и collections.defaultdict , потому что эти классы являются субклас- сами dict . О субклассах мы более подробно поговорим в главе 16. Средствам статической проверки типов не обязательно нужны аннотации типов для переменных. Дело в том, что средства статической проверки типов выполняют автоматическое определение типа на основании первой команды присваивания переменной. Например, по строке spam = 42 система проверки типов может автома- тически определить, что переменная spam должна иметь аннотацию типа int . Тем не менее я все равно рекомендую включать аннотации типов. Будущий переход к типу float , как в команде spam = 42.0 , также изменит автоматически определяемый тип, что может противоречить вашим намерениям. Лучше заставить программиста изменить аннотацию типа при изменении значения, чтобы он подтвердил, что из- менение было внесено намеренно, а не случайно. 228 Глава 11.Комментарии, doc-строки и аннотации типов Статические анализаторы Хотя Python поддерживает синтаксис аннотаций типов, интерпретатор Python полностью их игнорирует. Если вы запустите программу Python, которая передает функции переменную с неправильным типом, Python будет вести себя так, словно аннотации типа не существует. Иначе говоря, аннотации типов не заставляют ин- терпретатор Python выполнять какую-либо проверку типов на стадии выполнения. Аннотации типов существуют только для средств статической проверки типов, которые анализируют код до запуска программы, а не во время выполнения. Эти средства называются средствами статического анализа, потому что они ана- лизируют исходный код до запуска программы, тогда как средства динамического анализа работают с уже запущенными программами. (В данном случае термины «статический» и «динамический» относятся к тому, выполняется ли программа, но под терминами «статическая типизация» и «динамическая типизация» понимается способ объявления типов данных переменных и функций. Python является языком с динамической типизацией, для которого были написаны средства статического анализа — такие как Mypy.) Установка и запуск Mypy Хотя у Python нет официальных средств проверки типов, из сторонних разработок в настоящее время наибольшей популярностью пользуется Mypy. Чтобы установить Mypy с помощью pip , выполните следующую команду: python –m pip install –user mypy В macOS и Linux выполните команду python3 вместо python . Среди других популяр- ных систем проверки типов можно выделить Pyright (Microsoft), Pyre (Facebook) и Pytype (Google). Чтобы запустить программу проверки типов, откройте окно командной строки или терминала и выполните команду python -m mypy (чтобы запустить модуль как приложение) с именем проверяемого файла с кодом Python. В следующем примере проверяется код программы из файла с именем example.py : C:\Users\Al\Desktop>python –m mypy example.py Incompatible types in assignment (expression has type "float", variable has type "int") Found 1 error in 1 file (checked 1 source file) Программа не выводит ничего, если ошибки не найдены, и выводит сообщения об ошибках в противном случае. В файле example.py проблема обнаруживается в строке 171, потому что переменная с именем spam имеет аннотацию типа int , но Аннотации типов 229 ей присваивается значение float . Это присваивание способно привести к ошибке, и к нему стоит присмотреться. Некоторые сообщения об ошибках трудно понять с первого взгляда. В сообщениях Mypy могут упоминаться многочисленные воз- можные ошибки — слишком многочисленные, чтобы перечислять их здесь. Чтобы понять, что означает та или иная ошибка, проще всего поискать информацию в интернете. Запускать Mypy из командной строки при каждом изменении кода неэффективно. Чтобы пользоваться средствами проверки типов было удобнее, необходимо настро- ить IDE или текстовый редактор так, чтобы эти средства выполнялись в фоновом режиме. В этом случае редактор будет постоянно запускать Mypy при вводе кода, а затем выводить найденные ошибки в редакторе. На рис. 11.1 изображена ошибка из предыдущего примера в текстовом редакторе Sublime Text. Рис. 11.1. Ошибки Mypy в текстовом редакторе Sublime Text Конкретная последовательность действий по настройке IDE или текстового редак- тора для работы с Mypy зависит от того, в каком редакторе или IDE вы работаете. Инструкции можно найти в интернете — используйте условие поиска «<ваша IDE> Mypy настройка» (« Подавление обработки кода в Mypy Представьте, что вы написали блок кода, для которого по какой-то причине вы не получаете предупреждения, относящиеся к аннотациям типов. С точки зрения средств статического анализа в строке вроде бы используется неправильный тип, но вы уверены, что во время выполнения эта строка будет работать правильно. Чтобы подавить любые предупреждения о типах, добавьте комментарий # type: ignore в конец строки. Пример: 230 Глава 11.Комментарии, doc-строки и аннотации типов def removeThreesAndFives(number: int) -> int: number = str(number) # type: ignore number = number.replace('3', '').replace('5', '') # type: ignore return int(number) Чтобы удалить из целого числа, передаваемого removeThreesAndFives() , все циф- ры 3 и 5, мы временно преобразуем целочисленную переменную в строку. Из-за этого программа проверки типов выдает предупреждения о двух первых строках функции, поэтому в эти строки добавляются аннотации типов # type: ignore для подавления предупреждений. Пользуйтесь директивами # type: ignore осмотрительно. Игнорирование преду- преждений от средств проверки типов открывает возможности для проникновения ошибок в ваш код. Код почти всегда можно переписать так, чтобы предупреждения не выдавались. Например, если создать новую переменную командой numberAsStr = str(number) или заменить все три строки кода одной строкой return int(str(number. replace('3', '').replace('5', ''))) , можно избежать повторного использования переменной number для разных типов. Не следует подавлять предупреждения, из- меняя аннотацию типа для этого параметра на Union[int, str] , потому что этот параметр предназначен только для целых чисел. Аннотации типов для набора типов Переменные, параметры и возвращаемые значения Python могут принимать разные типы данных. Чтобы учесть эту возможность в программе, следует задать аннотации типов с несколькими типами; для этого надо импортировать Union из встроенного модуля typing . Набор допустимых типов задается в квадратных скобках после имени класса Union : from typing import Union spam: Union[int, str, float] = 42 spam = 'hello' spam = 3.14 В этом примере аннотация Union[int, str, float] указывает, что переменной spam может быть присвоено целое число, строка или число с плавающей точкой. Обра- тите внимание: лучше использовать форму команды импортирования from typing import X вместо import typing , а затем последовательно использовать развернутую форму typing. X для аннотаций типов во всей программе. Можно указать несколько типов данных в ситуациях, когда переменная или воз- вращаемое значение могут принимать значение None в дополнение к другому типу. Чтобы включить NoneType (тип значения None ) в аннотацию типа, укажите в квадратных скобках None вместо NoneType . (Формально NoneType не является встроенным идентификатором, каким является int или str .) Аннотации типов 231 Еще лучше вместо Union[str, None] импортировать Optional из модуля typing и использовать запись Optional[str] . Эта аннотация типа означает, что функция или метод может вернуть None вместо значения ожидаемого типа. Пример: from typing import Optional lastName: Optional[str] = None lastName = 'Sweigart' Здесь переменной lastName может быть присвоено значение str или None . Тем не менее к использованию Union и Optional стоит подходить осмотрительно. Чем меньше типов допускают ваши переменные и функции, тем проще будет ваш код, а в простом коде реже возникают ошибки, чем в сложном. Вспомните тезис «Дзен Python»: простое лучше, чем сложное. Если функция возвращает None как признак ошибки, рассмотрите возможность замены кода ошибки исключением (см. раздел «Выдача исключений и возвращение кодов ошибок», с. 214). Чтобы указать, что переменная, параметр или возвращаемое значение может иметь любой тип данных, используйте аннотацию типа Any (также из модуля typing ): from typing import Any import datetime spam: Any = 42 spam = datetime.date.today() spam = True В этом примере аннотация типа Any позволяет вам присвоить переменной spam значение любого типа данных — например, int , datetime.date или bool . Также в качестве аннотации типа можно использовать object , потому что этот класс яв- ляется базовым для всех типов данных Python. Тем не менее аннотация типа Any лучше читается, чем object Any , как и Union и Optional , следует использовать осмотрительно. Назначив всем пере- менным, параметрам и возвращаемым значениям аннотацию типа Any , вы лишитесь преимуществ проверки типов, которые предоставляет статическая типизация. Разница между аннотацией типа Any и отсутствием аннотации состоит в том, что Any явно со- общает, что переменная или функция принимает значения любого типа, тогда как от- сутствие аннотации означает, что переменная или функция пока еще не аннотированы. Аннотации типов для списков, словарей и т. д. Списки, словари, кортежи, множества и другие контейнерные типы данных могут содержать другие значения. Если вы укажете список ( list ) как аннотацию типа для переменной, эта переменная должна содержать список, но тот в свою очередь может содержать значения произвольного типа. Следующий код не вызовет никаких протестов у программы проверки типов: spam: list = [42, 'hello', 3.14, True] 232 Глава 11.Комментарии, doc-строки и аннотации типов Чтобы объявить типы данных значений, хранящихся в списке, используйте анно- тацию типа List модуля typing . Обратите внимание: List начинается с буквы L в верхнем регистре, что отличает ее от типа данных list : from typing import List, Union catNames: List[str] = ['Zophie', 'Simon', 'Pooka', 'Theodore'] ❶ numbers: List[Union[int, float]] = [42, 3.14, 99.9, 86] ❷ В этом примере переменная catNames содержит список строк, поэтому после импортирования List из модуля typing мы задаем аннотацию типа List[str] ❶ Система проверки типов перехватывает все вызовы методов append() или insert() или любого другого кода, который помещает в список значения, не являющиеся строками. Если список должен содержать данные разных типов, используйте Union в аннотации типов. Например, список numbers может содержать целые числа и чис- ла с плавающей точкой, поэтому для него задается аннотация List[Union[int, float]] ❷ Модуль typing имеет отдельный псевдоним типа (type alias) для каждой разновид- ности контейнеров. Ниже перечислены псевдонимы для всех распространенных контейнерных типов Python: List для типа данных list ; Tuple для типа данных tuple ; Dict для типа данных словаря ( dict ); Set для типа данных set ; FrozenSet для типа данных frozenset ; Sequence для list , tuple и любых других типов данных последователь- ностей; Mapping для словарей ( dict ), set , frozenset и любых других типов данных отображений; ByteString для типов данных bytes , bytearray и memoryview Полный перечень этих типов доступен по адресу https://docs.python.org/3/library/ typing.html#classes-functions-and-decorators. Обратное портирование аннотаций типов Обратным портированием (backporting) называется процесс выделения некоторой функциональности из новой версии программного продукта и портирование ее Аннотации типов 233 (то есть адаптация и добавление) в более раннюю версию. Аннотации типов Python появились только в версии 3.5. Но в коде Python, который должен выполняться в версиях интерпретатора ниже 3.5, все равно можно использовать аннотации типов, размещая информацию о типах в комментариях. Для переменных используется встроенный комментарий после команды присваивания. Для функций и методов аннотации типов записываются в строке после команды def . Комментарий начи- нается с type: , а затем указывается тип данных. Пример кода с аннотациями типов в комментариях: from typing import List ❶ spam = 42 # type: int ❷ def sayHello(): # type: () -> None ❸ """Doc-строка следует за комментарием с аннотацией типа.""" print('Hello!') def addTwoNumbers(listOfNumbers, doubleTheSum): # type: (List[float], bool) -> float ❹ total = listOfNumbers[0] + listOfNumbers[1] if doubleTheSum: total *= 2 return total Обратите внимание: даже если вы используете стиль аннотаций типов в ком- ментариях, все равно необходимо импортировать модуль typing ❶ , а также все псевдонимы типов, которые вы будете использовать в комментариях. В версиях до 3.5 стандартная библиотека не включала модуль typing , его следует установить отдельно следующей командой: python -m pip install --user typing В macOS и Linux используйте команду python3 вместо python Чтобы связать переменную spam с целым типом, мы добавляем # type: int в ком- ментарий в конце строки ❷ . Для функций комментарий должен включать круглые скобки со списком аннотаций типов, разделенных запятыми, порядок которых соответствует порядку параметров. Функции без параметров должны иметь пу- стой набор круглых скобок ❸ . Если параметров несколько, разделите их запятыми в круглых скобках ❹ Аннотации типов в комментариях читаются хуже, чем просто аннотации типов, поэтому они используются только для кода, который может выполняться версиями Python до 3.5. 234 Глава 11.Комментарии, doc-строки и аннотации типов Итоги Программисты часто забывают о документировании своего кода. Но потратив не- много времени на добавление комментариев, doc-строк и аннотаций типов в ваш код, вы избежите временных затрат в будущем. Хорошо документированный код также проще сопровождать. Соблазнительно принять точку зрения, что комментарии и документация не важны или даже приносят вред при написании программ. (Такая позиция также избавляет программистов от работы по написанию документации — очень удобно.) Не обма- нывайте себя: хорошо написанная документация всегда экономит гораздо больше времени и усилий, чем требуется на ее создание. Программистам намного чаще приходится иметь дело со страницами невразумительного кода без комментариев, чем с избытком полезной информации. Хорошие комментарии предоставляют полезную, краткую и точную информацию тем, кому придется читать код позднее и разбираться в том, что код делает. Коммен- тарии должны объяснять намерения программиста и обобщать смысл небольших блоков кода, а не пересказывать очевидный смысл одной строки кода. Иногда в ком- ментариях содержится подробное описание информации, полученной и усвоенной программистом во время написания кода. В будущем эти ценные сведения избавят тех, кто занимается сопровождением кода, от горькой участи заново добывать ее, тратя время и силы. Doc-строки — разновидность комментариев, специфическая для Python, — пред- ставляют собой многострочные тексты, следующие непосредственно за командами class или def или расположенные в начале модуля. Средства документирования (например, встроенная функция Python help() ) могут извлекать doc-строки из кода, чтобы предоставить конкретные сведения о предназначении класса, функции или модуля. Аннотации типов, появившиеся в Python 3.5, дают Python механизм постепенной типизации. Постепенная типизация позволяет программисту пользоваться пре- имуществами статической типизации по определению типов без потери гибкости динамической типизации. Интерпретатор Python игнорирует аннотации типов, потому что в Python отсутствует проверка типов на стадии выполнения. Тем не менее средства статической разработки типов используют аннотации типов для анализа исходного кода до его выполнения. Средства проверки типов — такие как Mypy — следят за тем, чтобы переменным, передаваемым функциям, не присваива- лись недопустимые значения. Это экономит время и усилия за счет предотвращения самых разнообразных ошибок. |