Часть 1: Основы подсказок типов в Python.

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

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

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

Цель подсказок типов – позволить программисту добавлять аннотации типов в ту часть системы, где это требуется, и позволить остальному коду работать, как раньше. .

Следует также подчеркнуть, что Python останется языком с динамической типизацией, и у авторов нет желания когда-либо делать подсказки типов обязательными, даже по соглашению.

Гвидо ван Россум, Юкка Лехтосало и Лукаш Ланга, PEP 484 — подсказки по типам

Проблема

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

def sum(a, b):
  '''
  args:
    a: number type (float, complex, int)
    b: number type (float, complex, int)
  return:
    number that is the sum of a and b
  '''
  return a + b

Строка документации — это строковый литерал, который появляется как первое выражение в классе, функции или модуле. Несмотря на то, что пакет игнорируется при выполнении, он распознается компилятором и помещается в атрибут __doc__ окружающего класса, функции или модуля. Поскольку он доступен для самоанализа, он является каноническим местом для документации объекта.

Глоссарий Python: определение строки документации

Примечание. Интересно отметить, что строки документации Python по умолчанию хранятся в памяти неограниченное время, поскольку они доступны через атрибут __doc__ функции или модуля. Параметр -OO для интерпретатора заставляет его удалять строки документации из сгенерированного .pyo и избегать такого поведения.

Это соглашение, которое позволяет как пользователям, так и создателям функции знать ожидаемый тип для аргументов функции, но у этого есть одна дополнительная проблема, эта строка документации не интерпретируется многими IDE, затем некоторые интересные функции, такие как методы автозаполнения, линтер ошибки и т.п. не всегда доступны.

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

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

В конце концов, эта стратегия предполагает, что пользователь функции сможет быстро получить строку документации функции. Это может быть фактом жизни в современных IDE, но не существует автоматизированной программы, которая может запускать вашу программу и утверждать, что вы передаете правильные типы переменных в соответствии с этой строкой документации, это всего лишь «виртуальное соглашение» между пользователем и создателем. функции.

Почему бы не указать правильный тип?

Другой вариант — попытаться защитить тип аргументов в начале функции, но это медленный и утомительный процесс.

def f(n):
  if not isinstance(n, int):
    raise ValueError(f'the n parameter must to be a int, but instead is {type(n)}')

Итак, какое решение?

— Решение — Подсказки по типу.

Утиный набор текста

— Если он ходит как утка и крякает как утка, значит, это утка

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

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

PEP 483: теория подсказок типов

Согласно глоссарию Python, утиная типизация — это:

Стиль программирования, при котором не учитывается тип объекта, чтобы определить, имеет ли он правильный интерфейс. вместо этого метод или атрибут просто вызывается или используется (Если он выглядит как утка и крякает, как утка, значит, это и есть утка). полиморфная замена. Утиный тип позволяет избежать тестов с использованием type() или isinstance(). (Обратите внимание, однако, что утиный тип может быть дополнен абстрактными базовыми классами.) Вместо этого обычно используются тесты hasattr() или программирование EAFP.

Глоссарий Python: определение утиной печати

Утиная типизация — одна из основных идей Python, речь идет о концепции использования структуры класса (методов), а не типа класса, иногда это называется скорее дизайном по возможностям. чем дизайн по контракту.

Например, представьте себе ситуацию, когда у вас есть система Game, в сцене которой много объектов. Некоторые из этих объектов относятся к типу Person, а другие — к типу Duck.

В этом случае система каждую секунду генерирует случайные (или интеллектуальные) движения, чтобы создать более интерактивную среду.

class Person:
  def walk(self, new_position):
    ...

class Duck:
  def walk(self, new_position):
    ...

class GameSystem:
    # ...
    def register_obj(self, obj):
       self.scene.append(obj)
    
    def refresh_environment(self):
        # Executed each second
        for obj in self.scene:
            random_position = ...
            obj.walk(random_position)

Если вы обратите внимание на классы Person и Duck, они не имеют общего родительского класса и не реализуют общий интерфейс явным образом, однако оба имеют одинаковую возможность обхода.

После этого в GameSystem есть метод refresh_environment, отвечающий за вызов метода walk во всех объектах системы. И может возникнуть вопрос, какие у вас есть объекты? , реальность такова, что это не имеет значения, хотя объекты могут ходить, эти объекты могут быть любого типа.

Системы типов

— Python — это не только динамическая типизация

До этого момента Python можно было считать языком программирования с динамической и структурной системой типов, но что это значит?

Существует много способов определить систему типов, например, вы можете определить систему в соответствии с тем, когда выполняется проверка типа, в этом случае существует две основные категории:

  1. Динамическая типизация. Проверка типа осуществляется во время выполнения. Вам нужно выполнить выполнение инструкции, чтобы выполнить проверку, и это поведение Python по умолчанию.
  2. Статическая типизация. Проверка типов осуществляется во время компиляции с помощью типов, аннотированных в исходном коде. В этом случае нет необходимости запускать программу для проверки, программа проверки типов выполняет проверку в соответствии со статическим состоянием программы (исходный код).

В других случаях вы можете определить систему типов в соответствии с тем, что проверяет система типов:

  1. Номинальная. Поддерживается ли система типов традиционными языками, такими как Java, C++ и т. д., и выполняется ли проверка в соответствии с типом или классом объекта, например: вы можете передавать только объект типа Person или какой-либо подкласс (с полиморфизмом), если функция ожидает аргумент типа Person .
  2. Структурный. Выполните проверку в соответствии с методами, которые имеют объект, а не тип. Если объект реализует все необходимые методы, то это допустимый объект. Обратите внимание, что не требуется делать явную реализацию какого-либо интерфейса, единственным требованием является реализация методов.

Первоначально Python использовал подход динамической типизации и мотивирует использование структурной типизации, но с введением подсказок типа и протоколов теперь можно использовать Структурную + Статическую типизацию.

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

Статические проверки типов

—Когда и как проверять типы в моем коде?

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

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

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

Некоторые примеры статических средств проверки типов:

  • Питайп от Google.
  • Авторское право от Microsoft.
  • Пир от Facebook.
  • Mypy.

В этой серии постов я собираюсь использовать средство проверки типов Mypy, потому что это самое известное средство проверки в экосистеме, и это был проект, который представил типы в экосистеме Python.

Вот некоторые важные моменты:

  • Когда вы выполняете свою программу с помощью интерпретатора Python, аннотации типов игнорируются. Для проверки аннотаций типов необходимо выполнить проверку типов с исходным кодом, например: mypy my_program.py
  • По умолчанию средство проверки типов не должно выдавать предупреждения для блоков кода, в которых есть подсказки типов. Если какая-то переменная не аннотирована и не имеет присвоенного значения в своем объявлении, то тип переменной — typing.Any.
  • Подсказки типа не дают какого-то преимущества в производительности, но теоретически в будущем можно реализовать такое улучшение.

Как правило, проверки типов не вызываются вручную, обычно они вызываются IDE автоматически при сохранении файла или в конвейере CI.

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

Выводы

  • Предпочитайте утиную печать.
  • Утиная типизация — это возможности, а не контракты.
  • Типовые аннотации в Python позволяют создавать структурный + статический анализ.
  • Существует множество средств проверки типов, например, Mypy, PyRight и т.д.

Рекомендации

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.