Каков самый pythonic способ проверить, является ли объект числом?

Учитывая произвольный объект python, какой лучший способ определить, является ли это числом? Здесь is определяется как acts like a number in certain circumstances.

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

Проверка того, что что-то есть int, float, long, bool раздражает и не охватывает определенные пользователем объекты, которые могут действовать как числа. Но, например, проверка на __mul__ недостаточно хороша, потому что только что описанный векторный класс определит __mul__, но это не будет число, которое я хочу.

Ответ 1

Используйте Number из модуля numbers для тестирования isinstance(n, Number) (доступно с версии 2.6).

>>> from numbers import Number
... from decimal import Decimal
... from fractions import Fraction
... for n in [2, 2.0, Decimal('2.0'), complex(2,0), Fraction(2,1), '2']:
...     print '%15s %s' % (n.__repr__(), isinstance(n, Number))
              2 True
            2.0 True
 Decimal('2.0') True
         (2+0j) True
 Fraction(2, 1) True
            '2' False

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

Ответ 2

Вы хотите проверить, есть ли какой-нибудь объект

действует как число в определенных обстоятельства

Если вы используете Python 2.5 или старше, единственный реальный способ - проверить некоторые из этих "определенных обстоятельств" и увидеть.

В версии 2.6 или выше вы можете использовать isinstance с numbers.Number - абстрактным базовым классом (ABC), который существует точно для эта цель (в модуле collections существует множество ABC для различных форм коллекций/контейнеров, опять же начиная с 2.6, а также только в тех выпусках вы можете легко добавить свои собственные базовые базовые классы, если вам нужно).

Бах до 2,5 и более ранних, "может быть добавлено в 0 и не может быть итерируемым", может быть хорошим определением в некоторых случаях. Но, вам действительно нужно спросить себя, что это такое, что вы спрашиваете, что то, что вы хотите считать "числом", должно обязательно иметь возможность делать, и что это должно быть абсолютно невозможно сделать - и проверить.

Это также может потребоваться в версии 2.6 или более поздней версии, возможно, для того, чтобы ваши собственные регистрации добавили типы, которые вам не нужны, которые еще не зарегистрированы на numbers.Numbers - если вы хотите исключить некоторые типы, которые требуют они являются цифрами, но вы просто не можете справиться, это требует еще большей осторожности, поскольку ABC не имеют метода unregister [[например, вы можете создать свой собственный ABC WeirdNum и зарегистрировать там все такие странные для вас типы, затем сначала проверьте на isinstance, чтобы он вылетел, прежде чем перейти к проверке isinstance нормального numbers.Number, чтобы продолжить успешно.

Кстати, если и когда вам нужно проверить, может ли x что-то или что-то сделать, вам обычно нужно попробовать что-то вроде:

try: 0 + x
except TypeError: canadd=False
else: canadd=True

Присутствие __add__ само по себе не говорит вам ничего полезного, поскольку, например, все последовательности имеют его для конкатенации с другими последовательностями. Эта проверка эквивалентна определению "число такое, что последовательность таких вещей является допустимым единственным аргументом встроенной функции sum", например. Полностью странные типы (например, те, которые поднимают "неправильное" исключение при суммировании до 0, например, ZeroDivisionError или ValueError & c) будут распространять исключение, но это нормально, сообщите пользователю ASAP, что такое сумасшедшие типы просто неприемлемы в хорошей компании;-); но "вектор", суммируемый для скаляра (стандартная библиотека Python не имеет одного, но, разумеется, они популярны в качестве сторонних расширений) также даст неверный результат, поэтому (например, эта проверка должна появиться после "не разрешено быть итерируемым" (например, проверьте, что iter(x) вызывает TypeError, или для наличия специального метода __iter__ - если вы в 2.5 или более раннем периоде и, следовательно, вам нужны ваши собственные проверки).

Краткий обзор таких осложнений может быть достаточным, чтобы мотивировать вас полагаться вместо этого на абстрактные базовые классы, когда это возможно...; -).

Ответ 3

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

Но, очевидно, это проверяет только, работает ли операция, а не имеет ли смысл! Единственное реальное решение для этого - никогда не смешивать типы и всегда точно знать, к каким типам относятся ваши значения.

Ответ 4

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

def isNumber(x):
    try:
        return bool(0 == x*0)
    except:
        return False

Таким образом, использование isNumber даст следующий вывод:

class A: pass 

def foo(): return 1

for x in [1,1.4, A(), range(10), foo, foo()]:
    answer = isNumber(x)
    print('{answer} == isNumber({x})'.format(**locals()))

Выход:

True == isNumber(1)
True == isNumber(1.4)
False == isNumber(<__main__.A instance at 0x7ff52c15d878>)
False == isNumber([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
False == isNumber(<function foo at 0x7ff52c121488>)
True == isNumber(1)

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

Пример numpy.array:

import numpy as np

def isNumber(x):
    try:
        return bool(x*0 == 0)
    except:
        return False

x = np.array([0,1])

answer = isNumber(x)
print('{answer} == isNumber({x})'.format(**locals()))

выход:

False == isNumber([0 1])

Ответ 5

Чтобы перефразировать ваш вопрос, вы пытаетесь определить, является ли что-то коллекцией или одним значением. Попытка сравнить, что-то есть вектор или число, сравнивает яблоки с апельсинами - у меня может быть вектор строк или чисел, и я могу иметь одну строку или одиночное число. Вас интересует, сколько у вас (1 или более), а не то, что вы на самом деле имеете.

Мое решение для этой проблемы состоит в том, чтобы проверить, является ли вход одним значением или коллекцией, проверяя наличие __len__. Например:

def do_mult(foo, a_vector):
    if hasattr(foo, '__len__'):
        return sum([a*b for a,b in zip(foo, a_vector)])
    else:
        return [foo*b for b in a_vector]

Или, для подхода с утиной печатью, сначала попробуйте выполнить повторение на foo:

def do_mult(foo, a_vector):
    try:
        return sum([a*b for a,b in zip(foo, a_vector)])
    except TypeError:
        return [foo*b for b in a_vector]

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

Ответ 6

Наверное, лучше просто сделать это наоборот: вы проверяете, является ли это вектором. Если это так, вы делаете точечный продукт, и во всех остальных случаях вы пытаетесь скалярное умножение.

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

Ответ 7

Подведение итогов/оценка существующих методов:

Candidate    | type                      | delnan | mat | shrewmouse | ant6n
-------------------------------------------------------------------------
0            | <type 'int'>              |      1 |   1 |          1 |     1
0.0          | <type 'float'>            |      1 |   1 |          1 |     1
0j           | <type 'complex'>          |      1 |   1 |          1 |     0
Decimal('0') | <class 'decimal.Decimal'> |      1 |   0 |          1 |     1
True         | <type 'bool'>             |      1 |   1 |          1 |     1
False        | <type 'bool'>             |      1 |   1 |          1 |     1
''           | <type 'str'>              |      0 |   0 |          0 |     0
None         | <type 'NoneType'>         |      0 |   0 |          0 |     0
'0'          | <type 'str'>              |      0 |   0 |          0 |     1
'1'          | <type 'str'>              |      0 |   0 |          0 |     1
[]           | <type 'list'>             |      0 |   0 |          0 |     0
[1]          | <type 'list'>             |      0 |   0 |          0 |     0
[1, 2]       | <type 'list'>             |      0 |   0 |          0 |     0
(1,)         | <type 'tuple'>            |      0 |   0 |          0 |     0
(1, 2)       | <type 'tuple'>            |      0 |   0 |          0 |     0

(Я пришел сюда этот вопрос)

код

#!/usr/bin/env python

"""Check if a variable is a number."""

import decimal


def delnan_is_number(candidate):
    import numbers
    return isinstance(candidate, numbers.Number)


def mat_is_number(candidate):
    return isinstance(candidate, (int, long, float, complex))


def shrewmouse_is_number(candidate):
    try:
        return 0 == candidate * 0
    except:
        return False


def ant6n_is_number(candidate):
    try:
        float(candidate)
        return True
    except:
        return False

# Test
candidates = (0, 0.0, 0j, decimal.Decimal(0),
              True, False, '', None, '0', '1', [], [1], [1, 2], (1, ), (1, 2))

methods = [delnan_is_number, mat_is_number, shrewmouse_is_number, ant6n_is_number]

print("Candidate    | type                      | delnan | mat | shrewmouse | ant6n")
print("-------------------------------------------------------------------------")
for candidate in candidates:
    results = [m(candidate) for m in methods]
    print("{:<12} | {:<25} | {:>6} | {:>3} | {:>10} | {:>5}"
          .format(repr(candidate), type(candidate), *results))

Ответ 8

Просто добавьте. Возможно, мы можем использовать комбинацию isinstance и isdigit следующим образом, чтобы определить, является ли значение числом (int, float и т.д.)

если isinstance (num1, int) или isinstance (num1, float) или num1.isdigit():

Ответ 9

Для гипотетического векторного класса:

Предположим, что v - вектор, и мы его умножаем на x. Если имеет смысл умножить каждый компонент v на x, мы, вероятно, имели в виду это, поэтому сначала попробуйте это. Если нет, может быть, мы можем остановиться? В противном случае это ошибка типа.

EDIT - приведенный ниже код не работает, потому что 2*[0]==[0,0] вместо того, чтобы поднимать TypeError. Я оставляю его, потому что он был прокомментирован.

def __mul__( self, x ):
    try:
        return [ comp * x for comp in self ]
    except TypeError:
        return [ x * y for x, y in itertools.zip_longest( self, x, fillvalue = 0 )

Ответ 10

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

float(x)

Это должно отклонять случаи, когда x не может быть преобразован в число; но может также отклонять другие типы числообразных структур, которые могут быть действительными, например комплексные числа.

Ответ 11

Если вы хотите вызывать разные методы в зависимости от типа (ов) аргумента, посмотрите на multipledispatch.

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

from multipledispatch import dispatch

class Vector(list):

    @dispatch(object)
    def __mul__(self, scalar):
        return Vector( x*scalar for x in self)

    @dispatch(list)
    def __mul__(self, other):
        return sum(x*y for x,y in zip(self, other))


>>> Vector([1,2,3]) * Vector([2,4,5])   # Vector time Vector is dot product
25
>>> Vector([1,2,3]) * 2                 # Vector times scalar is scaling
[2, 4, 6]

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

Ответ 12

Вы можете использовать функцию isdigit().

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False