Если вы когда-нибудь чувствовали, что вы погрязли в совещаниях и обсуждениях, которые всё длятся и длятся, а решения проблемы всё нет, знайте: в mypy есть 5-летний issue, о том что целое число не является числом.

Подсказки типов в python являются интересной темой. Люди из статически типизированных языков не понимают, как можно было создавать язык без них, а потом зачем-то их прикручивать, любители динамики, не понимают, зачем тратить время на добавление типов, если код и так работает, а анализаторы только на всякую ерунду ругаются. В то время как разработчики на python продолжают ковырять и дебажить код, пытаясь понять, что же имел ввиду автор и докидывая типы, если понять удалось.
Однако, прикручивание типов и их проверки где-то сбоку действительно имеет некоторые проблемы, для примера можно привести такую, вроде бы, простую тему как числа.
В python есть следующие встроенные типы для чисел: целые, вещественные с плавающей и фиксированной точкой, рациональные дроби и даже комплексные числа. Данные типы реализуют определённые интерфейсы (ABCs), организованные в numeric tower (Number, Complex, Real, Rational и Integral). И вот здесь, начинаются проблемы. Некоторые решения выглядят понятными и допустимыми, например, в функцию, принимающую float всегда можно передать int, а в функцию, принимающую complex всегда можно передать и float, и int.
def sin(a: float): print(isinstance(a, float)) def cos(a: complex): print(isinstance(a, complex)) sin(1) # выведет False cos(4.5) # тоже выведет False
Проверка mypy выведет Success: no issues found in 1 source file. И с математической точки зрения в этом есть смысл, любое вещественное число является комплексным, а целое — вещественным. Но есть пуристы, которые такой код не пропустят, например, в rust, нельзя передавать целые числа в функции, ожидающее вещественное число.
fn f(a: f32) { } fn main() { f(4) }
Compiling playground v0.0.1 (/playground) error[E0308]: mismatched types --> src/main.rs:6:7 | 6 | f(4) | - ^ | | | | | expected `f32`, found integer | | help: use a float literal: `4.0` | arguments to this function are incorrect
Да и в python красивая математическая абстракция начинает течь, как можно заметить проверки типов проходят в mypy, но проверки isinstance возвращают False. То есть тип в сигнатуре и в isinstance не одно и то же, впрочем, это даже понятно, учитывая __subclasshook__ и динамизм языка.
Так же в python decimal и float, не являются взаимозаменяемыми и совместимыми, хотя с точки зрения математики, и то, и другое — вещественные числа, но создатели языка решили, что смешивать в одной операции два типа не стоит, так как это может привести к потере точности.
>>> Decimal(1) + 2.5 TypeError: unsupported operand type(s) for +: 'decimal.Decimal' and 'float'
mypy об этом знает и такие операции не разрешает:
ws.py:15: error: Unsupported operand types for + ("Decimal" and "float")
Так же, Decimal нельзя передать в функцию, ожидающую комплексную переменную, а целое число нельзя передать в функцию, ожидающую Decimal. Хотя математическая абстракция, по идее, не должна переставать работать от того, то мы поменяли вещественные числа с плавающей точкой на числа с фиксированной, они всё равно остаются вещественными и математические операции, допустимые над Decimal должны выполнять и над int. Но такой код mypy не пропустит.
from decimal import Decimal from numbers import Number def sin(a: Decimal): ... def cos(a: complex): ... sin(1) cos(Decimal(1))
ws.py:13: error: Argument 1 to "sin" has incompatible type "int"; expected "Decimal" ws.py:14: error: Argument 1 to "cos" has incompatible type "Decimal"; expected "complex"
Отдельный забавный момент состоит в том, что в python bool наследуется от int.
>>> int.__subclasses__() [bool, ...
from decimal import Decimal def sin(x: float): pass sin(Decimal(1)) # ws.py:7: error: Argument 1 to "sin" has incompatible type "Decimal"; expected "float" sin(1==0) # А эти строчки sin(True) # проверку проходят
То есть вычислить «синус» от 1 нельзя, а от истины — возможно.
Теперь вернёмся к КДПВ. int, Decimal и float являются Number.
isinstance(1, Number) # True isinstance(2.5, Number) # True isinstance(Decimal(2.5), Number) # True
Но, mypy так не считает.
from decimal import Decimal from numbers import Number def sin(x: Number): pass sin(Decimal(1)) sin(1) sin(2.5) sin(True)
ws.py:8: error: Argument 1 to "sin" has incompatible type "Decimal"; expected "Number" ws.py:9: error: Argument 1 to "sin" has incompatible type "int"; expected "Number" ws.py:10: error: Argument 1 to "sin" has incompatible type "float"; expected "Number" ws.py:11: error: Argument 1 to "sin" has incompatible type "bool"; expected "Number"
sin(Number(1)) тоже не пройдёт, так как Number — абстрактный класс.
Python достаточно приятный в использовании язык, недаром он стал одним из самых популярных (а по некоторым подсчётам — самым популярным). И многие архитектурные решения, принятые при реализации языка и интерпретатора достойны изучения. Но, иногда даже такие простые сущности как числа приводят к необходимости принимать сложные и неоднозначные решения, допустив в процессе несколько багов и неочевидностей. Так что, если вдруг ваш начальник будет недоволен вашей архитектурой, можете попытаться отмазаться тем, что не только у вас не получается создать идеальную иерархию типов, даже у Гвидо не всегда получается.
ссылка на оригинал статьи https://habr.com/ru/post/682272/
Добавить комментарий