Если вы писали код на Python, то весьма высока вероятность того, что вы, хотя бы в одной из своих программ, пользовались числами. Например, это могли быть целые числа для указания индекса значения в списке, или числа с плавающей точкой, представляющие суммы в некоей валюте. Но числа в Python — это гораздо больше, чем, собственно, их числовые значения. Поговорим о трёх особенностях чисел в Python, с которыми вы, возможно, не знакомы. В Python практически всё — это объект. Один из первых объектов, о котором узнаёт тот, кто начинает изучать Python — это str, используемый для представления строк. Возможно, вы сталкивались с использованием методов строк, вроде .lower(), который возвращает новую строку, все символы которой приведены к нижнему регистру: Числа в Python тоже, как и строки, являются объектами. У них тоже есть методы. Например, целое число можно преобразовать в байтовую строку с помощью метода .to_bytes(): Параметр length указывает на количество байт, которые нужно использовать при составлении байтовой строки, а параметр byteorder определяет порядок байт. Например, установка параметра byteorder в значение «big» приводит к возврату байтовой строки, в которой старший байт расположен первым, а установка этого параметра в значение «little» приводит к тому, что первым идёт младший байт. 255 — это максимальное значение, которое может принимать 8-битное целое число. Поэтому в нашем случае при вызове метода .to_bytes() можно без проблем воспользоваться параметром length=1: А вот если записать в n число 256 и вызвать для него .to_bytes() с параметром length=1, будет выдана ошибка OverflowError: Преобразовать байтовую строку в целое число можно, воспользовавшись методом .from_bytes() класса int: Методы класса вызывают, используя имя класса, а не его экземпляр. Именно поэтому в предыдущем примере метод .from_bytes() вызывают, обращаясь к int. Любопытный факт: 1729 — это самое маленькое положительное число, которое можно представить в виде суммы кубов двух положительных чисел двумя способами. Исторический анекдот связывает это число с индийским математиком Сринивасой Рамануджаном, который рассказал о нём своему наставнику Готфри Харолду Харди. Харди часто навещал Рамануджана, когда тот, умирая, находился в больнице в Патни. Именно в одно из таких посещений произошёл «инцидент» с номером такси. Харди приехал в Патни на такси, воспользовавшись своим излюбленным транспортным средством. Он вошёл в палату, где лежал Рамануджан. Начинать разговор Харди всегда было мучительно трудно, и он произнёс свою первую фразу: «Если не ошибаюсь, то номер такси, на котором я приехал, 1729. Мне кажется, это скучное число». На что Рамануджан тотчас же ответил: «Нет, Харди! О нет! Это очень интересное число. Это самое малое из чисел, представимых в виде суммы двух кубов двумя различными способами». Один из способов представления числа 1729 в виде суммы двух кубов — это 13 + 123. Можете отыскать второй способ? У чисел с плавающей точкой тоже есть методы. Возможно, самый полезный из них — это .is_integer(). Его используют для проверки того, есть ли у числа с плавающей точкой дробная часть: Вот — интересный метод .as_integer_ratio(). Он, вызванный для числа с плавающей точкой, возвращает кортеж, содержащий числитель и знаменатель дроби, представляющей это число: Правда, из-за ошибки представления чисел с плавающей точкой, иногда этот метод возвращает неожиданные результаты: Если надо — можно вызывать методы на числовых литералах, заключённых в круглые скобки: Если обойтись без скобок — при попытке вызова метода на целочисленном литерале будет выдана ошибка SyntaxError. А вот при вызове метода числового литерала с плавающей точкой отсутствие скобок, что странно, не приведёт к ошибке: Полный список методов числовых Python-типов можно найти в документации. В математике числа обладают естественной иерархией. Например, все натуральные числа являются целыми, а все целые числа — рациональными. Все рациональные числа — это вещественные числа, а все вещественные числа — это комплексные числа. Похожие рассуждения применимы и к представлению чисел в Python. Здесь «числовая башня» выражается через абстрактные типы, содержащиеся в модуле numbers. Все числа в Python являются экземплярами класса Number: Если нужно узнать о том, является ли некое Python-значение числовым, но при этом неважно то, каким именно числовым типом оно представлено, воспользуйтесь конструкцией isinstance(value, Number). В Python имеется четыре дополнительных абстрактных типа, иерархия которых, начиная с наиболее общего числового типа, выглядит так: Класс Complex используется для представления комплексных чисел. Тут имеется один встроенный конкретный тип — complex. Класс Real — это представление вещественных чисел. Его единственный встроенный конкретный тип — float. Класс Rational представляет рациональные числа. Его единственным встроенным конкретным типом является Fraction. Класс Integral применяют для представления целых чисел. В нём имеется два встроенных конкретных типа — int и bool. Так, погодите, а значения типа bool — это разве числа? Да — числа. Можете это проверить, воспользовавшись REPL: Всё это, на первый взгляд, выглядит вполне нормально. Правда, порядок несколько нарушает то, что значения типа bool являются числами. Странность Python: так как тип bool относится к классу Integral (на самом деле он — прямой наследник int), со значениями True и False можно вытворять довольно необычные вещи. Например, True можно использовать в роли индекса для того чтобы получить второй элемент итерируемого объекта. А если поделить число на False — будет выдана ошибка ZeroDivisionError. Попробуйте выполнить «False»[True] и 1 / False в REPL! Но если присмотреться к числовым типам поближе, окажется, что в иерархии Python-чисел имеется пара своеобразных моментов. Как уже было сказано, в «числовой башне» Python есть 4 конкретных числовых типа, соответствующих четырём абстрактным типам: complex, float, Fraction и int. Но в Python имеется и пятый числовой тип, представленный классом Decimal. Этот тип используется для точного представления десятичных чисел и для преодоления ограничений арифметических операций с плавающей точкой. Можно предположить, что числа типа Decimal являются наследниками Real, но это, на самом деле, не так: Единственный класс, наследником которого является класс Decimal — это Number: Логично то, что класс Decimal не является наследником Integral. В некоторой степени смысл есть и в том, что Decimal не является наследником Rational. Но почему Decimal не является наследником Real или Complex? Ответ кроется в исходном коде CPython: Объекты Decimal обладают всеми методами, определёнными в классе Real, но эти объекты не должны регистрироваться в виде наследников Real, так как Decimal-числа не взаимодействуют с двоичными числами с плавающей точкой (например, результат операции Decimal('3.14') + 2.71828 не определён). Но ожидается, что числа, классы которых являются наследниками абстрактного класса Real, способны взаимодействовать друг с другом (то есть — R1+R2 должно вычисляться в том случае, если числа R1 и R2 представлены типами, являющимися наследниками Real). Получается, что объяснение странностей сводится к особенностям реализации. А вот числа с плавающей точкой, с другой стороны, реализуют абстрактный базовый класс Real. Они используются для представления вещественных чисел. Но, из-за того, что компьютерная память не является неограниченным ресурсом, числа с плавающей точкой — это лишь конечные аппроксимации вещественных чисел. Это приводит к возможности написания «ненормальных» образцов кода вроде такого: Числа с плавающей точкой хранятся в памяти в виде двоичных дробей. Это приводит к появлению некоторых проблем. Например, у дроби 1/3 нет конечного десятичного представления (после десятичной точки идёт бесконечное множество троек). А у дроби 1/10 нет конечного представления в виде двоичной дроби. Другими словами, в компьютере нельзя совершенно точно представить число 0,1 — если только этот компьютер не обладает бесконечной памятью. Со строго математической точки зрения все числа с плавающей точкой — это рациональные числа, за исключением float(«inf») и float(«nan»). Но программисты используют их в роли аппроксимаций вещественных чисел и воспринимают их, по большей части, как вещественные числа. Странность Python: float(«nan») — это особое значение с плавающей точкой, представляющее собой «не число». Такие значения часто обозначают как NaN. Но, так как float — это числовой тип, isinstance(float(«nan»), Number) возвращает True. Получается, что «не числа» — это числа. В общем, числа с плавающей точкой — странные создания. Абстрактный числовой базовый тип Python позволяет программисту создавать собственные абстрактные и конкретные числовые типы. В качестве примера рассмотрим класс ExtendedInteger, который реализует числа в форме a+b√p, где a и b — целые числа, а p — простое число (обратите внимание: класс не обеспечивает то, что число p является простым): Для того чтобы обеспечить правильность реализации интерфейса Real конкретным типом — нужно создать реализации множества методов, в именах которых есть два символа подчёркивания. Ещё нужно поразмыслить о том, как методы вроде .add() и .mul() взаимодействуют с другими типами, являющимися наследниками Real. Обратите внимание: вышеприведённый пример не создавался в расчёте на его полноту или абсолютную правильность. Его цель — продемонстрировать читателю возможности работы с числами. При наличии реализации ExtendedInteger можно заниматься следующими вычислениями: Иерархия числовых типов в Python — довольно гибкая структура. Но, конечно, всегда стоит очень внимательно относиться к реализации типов, являющихся наследниками встроенных абстрактных базовых типов Python. Нужно обеспечить их корректную работу друг с другом. В документации по Python можно найти несколько советов по реализации собственных типов, которые стоит прочесть тому, кто решит заняться созданием собственных числовых типов. Такому человеку ещё полезно будет ознакомиться с реализацией Fraction. Вот — те три особенности Python-чисел, которые мы здесь обсуждали: У чисел есть методы, как и у практически всех остальных объектов в Python. Числа обладают иерархией, даже несмотря на то, что их чёткие взаимоотношения несколько портит наличие типов Decimal и float. Программисты могут создавать собственные числовые типы, которые вписываются в иерархию числовых типов Python. Может быть, вы узнали из этого материала не только об этих особенностях чисел, но и ещё о чём-нибудь, что вам пригодится. Мы в wunderfund.io занимаемся высокочастотной алготорговлей с 2014 года. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки. Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь. Сейчас мы ищем плюсовиков, питонистов, дата-инженеров и мл-рисерчеров. Присоединяйтесь к нашей команде.