Базовый класс чисел Python ИЛИ как определить, что значение является числом

Python, похоже, не имеет базового класса для «всех чисел», например. грамм. int, float, complex, long (в Python2). Это неприятно и немного неудобно для меня.

Я пишу функцию для сопоставления типов данных друг с другом (алгоритм сопоставления/поиска дерева). Для этого я хотел бы проверить входные значения на наличие lists, dicts, strings или «чисел». Каждый из четырех случаев обрабатывается отдельно, но я не хочу различать int, long, float или complex (хотя последний, вероятно, не появится). Этого было бы довольно легко достичь, если бы все числовые типы производились от базового типа number, но, к сожалению, это не так, AFAICS.

Это обеспечение четкости делает очевидным, что необычный complex включен. Что обычно вызывает вопросы, о которых я не хочу думать. Мой дизайн скорее говорит «все типы чисел», чем этот явный список. Я также не хочу явно перечислять все возможные типы чисел из других библиотек, таких как numpy или подобных.

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

Второй вопрос, более практичный: существует ли рекомендуемый способ проверки значения на то, является ли оно числом, может быть, библиотечный вызов, о котором я не знаю? Прямая версия использования isinstance(x, (int, float, complex, long)) выглядит для меня как сцепление, несовместима с Python3, который больше не знает тип long, и не включает библиотечные числовые типы, такие как numpy.int32.


person Alfe    schedule 26.06.2017    source источник
comment
Кстати, numpy имеет такую ​​цепочку наследования для int32: objectgenericnumberintegersignedintegerint32. Это может быть немного излишним, но показывает, что это, вероятно, неплохая идея в целом.   -  person Alfe    schedule 26.06.2017
comment
Для удобства вы можете скрыть числовую логику внутри метода. Самый простой способ сделать это, который я вижу, - это выполнить isinstance для каждого типа номера. Вы даже можете поместить типы в кортеж/список/что угодно и перебирать их, проверяя каждый. Он является подробным, но только внутри метода, и вы будете уверены, что ваш номер относится к одному из определенных вами типов.   -  person Meloman    schedule 26.06.2017


Ответы (2)


На самом деле существует базовый класс для тех типов, которые вы перечислили.

Если вы не рассматриваете пустые типы, хорошей отправной точкой будет numbers.Complex :

>>> import numbers
>>> isinstance(1+9j, numbers.Complex)
True
>>> isinstance(1L, numbers.Complex)
True
>>> isinstance(1., numbers.Complex)
True
>>> isinstance(1, numbers.Complex)
True

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

person Moses Koledoye    schedule 26.06.2017
comment
Я не знал об этом модуле numbers и до сих пор не понимаю, как он работает. Почему isinstance(4, numbers.Number) возвращает True, когда type(4).__bases__ перечисляет только object?? Я посмотрю на этот PEP 3141, упомянутый в документации numbers - person Alfe; 26.06.2017
comment
ABC могут реализовать __isinstancecheck__, что позволяет им выступать в качестве виртуальной базы. классы для например. встроенные классы. Они не могут быть перечислены в mro. - person Moses Koledoye; 26.06.2017
comment
Ах. Спасибо за это! Я думаю, вы полностью решили мою проблему. И, кстати, типы данных numpy также корректно обрабатываются isinstance(x, numbers.Number). - person Alfe; 26.06.2017
comment
@Alfe Будьте осторожны, когда используете это с numpy. Насколько я знаю, это работает не для всех версий numpy. - person Moses Koledoye; 26.06.2017
comment
Остается только вопрос, почему разработчики языка не включили это во встроенные функции. Любое обоснование по этому поводу? - person Alfe; 26.06.2017
comment
@Alfe Добавление этого во встроенный приводит к снижению производительности, которое намного перевешивает его использование: редко используемая проверка isinstance. - person Moses Koledoye; 26.06.2017

Извините, немного опоздал на вечеринку. Но это может быть полезно для будущих читателей этой страницы. Я предлагаю вам использовать характер утиного ввода Python и его философию EAFP (Легче попросить прощения, чем разрешения): другими словами, просто попробуйте использовать рассматриваемый объект как число. Напишите что-то вроде этого:

def isnumber(thing):
    try:
        thing + 0
        return True
    except TypeError:
        return False

Он должен работать для любого типа числа, включая пользовательские классы.

person Jason Johnston    schedule 27.04.2021
comment
Добро пожаловать, Джейсон, и спасибо за ваш ответ! Да, просто попытаться использовать его как число было бы идеей. К сожалению, добавление нуля к значению может работать для других типов объектов, которые на самом деле не являются числами (в конце концов, оператор плюса может быть перегружен любым классом путем реализации __add__()). Также не очень чистый код использовать сложение, чтобы узнать, является ли что-то числом, поскольку это не очень очевидно. Я также некоторое время рассматривал, нет ли допустимых чисел, которые не позволяют добавлять 0 (например, inf, -inf, NaN); оказалось, что нет, но это не делает этот подход хорошим. - person Alfe; 29.04.2021
comment
@Alfe, я не согласен. Я считаю, что мое решение - это питоновский способ сделать это: не спрашивайте, что что-то есть, а что оно делает. И хотя ни один подход в Python не может быть надежным (любой может написать класс с извращенным поведением), мой подход действительно работает во всех известных мне случаях. В то время как модуль numbers этого не делает. Попробуйте создать экземпляр типа x = decimal.Decimal('3.4'), протестировать его с помощью isinstance(x, numbers.Complex) (это даст неправильный результат) и с x + 0 (это даст правильный результат). - person Jason Johnston; 03.05.2021
comment
Экземпляр decimal.Decimal не является numbers.Complex (используя isinstance). Однако это numbers.Number. Если бы любое число было бы комплексным числом (поскольку математически ℝ является подклассом ℂ), то это различие между numbers.Number и numbers.Complex не имело бы никакого смысла. Итак, я полагаю, что numbers.Complex скорее что-то говорит об используемом типе данных, и что (decimal.Decimal) просто не может хранить комплексные числа. Пожалуйста, объясните, почему вы считаете ответ isinstance неправильным. - person Alfe; 03.05.2021
comment
Я думаю, вы упустили мою мысль о том, что успех запроса numbers зависит от случайного факта того, учли ли его разработчики конкретно тип интересующего вас объекта. Согласны ли вы, что 'decimal.Decimal' может содержать реальный числа? Попробуйте isinstance(decimal.Decimal('3.4'), numbers.Real). Возвращаемый ответ — False, что, безусловно, неверно или, по крайней мере, не имеет отношения к тому, как вы можете на самом деле использовать этот конкретный объект. - person Jason Johnston; 03.05.2021
comment
Я думаю, что не упустил вашу мысль, но вы продолжаете подкреплять ее плохими примерами. Десятичное число не является ни вещественным, ни комплексным. Он может содержать только некоторые рациональные числа, причем очень особым образом (в десятичном виде, который является особенным для компьютеров). Поэтому разработчики модуля чисел решили, что он не должен объявляться вещественным или сложным, а просто числом. Вы можете не согласиться с этим решением, но это явно не ошибка. С другой стороны, добавление нуля позволяет создать впечатление, будто все, к чему я могу добавить ноль, должно быть числом. Это просто не тот случай, учитывая, что мы находимся во вселенной различных классов. - person Alfe; 04.05.2021