# Анализ Python 1. Проверка типов проводится [динамически](https://www.python.org/doc/essays/blurb/). В Python 3.5 были введены аннотации типов (см. [PEP-484](https://www.python.org/dev/peps/pep-0484)), которые не влияют на процесс исполнения программы, а лишь выполняют документирующую и превентирующую ошибки роли. Сторонние разработчики также разрабатывают инструменты для статического анализа. Примером таких инструментов является mypy. Простой пример: ![](https://i.imgur.com/6pgsnV7.png) Вызов mypy выведет в консоль ``` test.py:9: error: Incompatible return value type (got "None", expected "str") Found 1 error in 1 file (checked 1 source file) ``` 2. Не всегда с корректным вердиктом ![](https://i.imgur.com/YSuUpK2.png) 3. Python имеет довольно сильную систему типов. Для произведения операций между двумя объектами разных типов необходимо преобразовывать их явно. К примеру, сложение числа, представленного в виде строки, и числа, имеющим целочисленное значение, недопустимо без предварительной явной конвертации одного в другое. Python не пытается автоматически подогнать тип объекта под требуемый для выполнения операции и не позволяет это делать пользователю неявным образом. Для этого нужно использовать заранее определённый метод, который создаёт объект требуемого типа. К примеру, `__str__` для перевода в строку или `__bool__` для перевода в булевую переменную. 4. Да, можно использовать вывод типов. С использованием функции `type()` или с помощью прямого обращения к аттрибуту `__class__` объекта. ``` type([]) >>> <class 'list'> [].__class__ >>> <class 'list'> [].__class__ is list >>> True ``` 5. В целом в Python система типов является номинативной: ![](https://i.imgur.com/JvK15ga.png) И утиной! В рантайме Python определяет наличие требуемого аттрибута объекта. А также определён протокол, по которому статические анализаторы могут вывести тип схожего по поведению объекта по его аттрибутам без явного наследования/имплементации. Пример из [PEP-544](https://www.python.org/dev/peps/pep-0544/): ``` from typing import Iterator, Iterable class Bucket: ... def __len__(self) -> int: ... def __iter__(self) -> Iterator[int]: ... def collect(items: Iterable[int]) -> int: ... result: int = collect(Bucket()) # Passes type check ``` Так же проверка типа при вызове функции `isinstance(Bucket(), Iterable)` в рантайме тоже выведет `True`. 6. В Python возможно описать произвольный алгебраический тип данных при помощи типа `Union`, определённого в модуле `typing`. К примеру, можно создать новый тип данных, описывающий все базовые типы данных в Python так: ``` from typing import Union BasicDataType = [str, int, float, complex, bool] ``` 7. В Python об ошибках и исключительных ситуациях принято сообщать при помощи исключений. Для вызова исключений используется ключевое слово `raise`, e.g. `raise Exception('You Reposted in the Wrong Neighborhood')`. Для их обработки - конструкция `try-except-else`. 8. В Python имеется [обширная иерархия исключений ](https://docs.python.org/3/library/exceptions.html). Для объявления своего типа исключения нужно создать класс-наследник наиболее близкого по смыслу. К примеру, если нам нужно вывести деньги со счёта пользователя, мы должны создать предусловие на положительную сумму, невыполнение которого повлечёт за собой вызов кастомного исключения: ``` class NegativeAmountException(ValueError): def __init__(self, *args, **kwargs): super(NegativeAmountException, self).__init__(*args, **kwargs) class Account: ... def withdraw(self, amount: int): ''' ... :raises NegativeAmountException: if amount is negative ... ''' if amount < 0: raise NegativeAmountException() 10. _Пример кода, в котором система типов позволяет найти и предотвратить ошибку._ Предположим, мы хотим загрузить страницу с новостью. Для этого хотим вернуть заголовок, дату и текст новости. По какой-то причине не подходящим для этого объектом, а кортежем. (воспользуемся библиотекой [newspaper3k](https://newspaper.readthedocs.io/en/latest/)). ``` from datetime import datetime from typing import Optional from newspaper.article import Article, ArticleDownloadState def get_news_article(url: str) -> Optional[tuple[str, datetime, str]]: article = Article(url) article.download() if article.download_state != ArticleDownloadState.SUCCESS: return None article.parse() return article.title, article.publish_date, article.text def process_news(urls: list[str]): for url in urls: title, date, text = get_news_article(url) print( f'Url: {url}', f'Title: "{title}"', f'Date: {date}', f'Text:\n{text}', sep='\n' ) if __name__ == '__main__': urls = ['https://www.rbc.ru/politics/24/11/2021/619e4e949a79472a5fba4f47'] process_news(urls) ``` Вызов `mypy 10.py` выдаст `10.py:17: error: "None" object is not iterable`, что позволит нам заметить, что в функции `process_news` мы не обработали случай, когда `get_news_article` возвращает `None`. 11. Предположим, у нас есть функция, принимающая переменное кол-во аргументов. А пользователь функции оказался невнимателен: ``` class Robot: def go_up(self): pass def move_robots_up(*robots: Robot): for robot in robots: robot.go_up() if __name__ == '__main__': robots = [Robot() for _ in range(10)] move_robots_up(robots) ``` Вызов `mypy 11.py` выдаст `11.py:12: error: Argument 1 to "move_robots_up" has incompatible type "List[Robot]"; expected "Robot"`, что позволит заблаговременно обратить внимание на эту ошибку. P.S. я так ошибался :) # Общие вопросы 1. _Что требует принцип подстановки Лисков применительно к исключениям, которые могут выбрасывать методы?_ Метод дочернего класса не должен выбрасывать исключения, которые не ожидаются от метода родительского класса. При этом, учитывая иерархию исключений, из дочернего класса можно выбрасывать более специфичные версии исключения, которое может быть выброшено родительским. Так, чтобы конструкция, которая обрабатывает родительское исключение, могла обработать и исключения дочерние. К примеру, исключения из пункта 8): ``` try: ... except ValueError: pass ``` Может обрабатывать как ValueError, так и NegativeAmountException, не давая ему улететь дальше по стеку. Так что, если родительский класс выкидывает ValueError, в дочернем можно кидать NegativeAmountException.