146

Given an arbitrary python object, what's the best way to determine whether it is a number? Here is is defined as acts like a number in certain circumstances.

For example, say you are writing a vector class. If given another vector, you want to find the dot product. If given a scalar, you want to scale the whole vector.

Checking if something is int, float, long, bool is annoying and doesn't cover user-defined objects that might act like numbers. But, checking for __mul__, for example, isn't good enough because the vector class I just described would define __mul__, but it wouldn't be the kind of number I want.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Claudiu
  • 224,032
  • 165
  • 485
  • 680

16 Answers16

164

Use Number from the numbers module to test isinstance(n, Number) (available since 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(f'{n!r:>14} {isinstance(n, Number)}')
              2 True
            2.0 True
 Decimal('2.0') True
         (2+0j) True
 Fraction(2, 1) True
            '2' False

This is, of course, contrary to duck typing. If you are more concerned about how an object acts rather than what it is, perform your operations as if you have a number and use exceptions to tell you otherwise.

Steven Rumbalski
  • 44,786
  • 9
  • 89
  • 119
  • 5
    Doing the smart thing, rather than the duck thing, is preferred when you're multiplying a vector by X. In this case you want to do different things based on what X _is_. (It might act as something that multiplies, but the result might be nonsensical.) – Evgeni Sergeev May 02 '15 at 05:34
  • 3
    this answer gives would say True is a number.. which is probably not always what you want. For exlcuding booleans (think validation f.e.), I would say `isinstance(value, Number) and type(value) != bool` – Yo Ludke Aug 01 '17 at 12:26
  • The method in this answer will tell you that `float("-inf")` is a number. Depending on the situation, that might not be appropriate. – Grey Christoforo Oct 11 '22 at 20:39
33

You want to check if some object

acts like a number in certain circumstances

If you're using Python 2.5 or older, the only real way is to check some of those "certain circumstances" and see.

In 2.6 or better, you can use isinstance with numbers.Number -- an abstract base class (ABC) that exists exactly for this purpose (lots more ABCs exist in the collections module for various forms of collections/containers, again starting with 2.6; and, also only in those releases, you can easily add your own abstract base classes if you need to).

Bach to 2.5 and earlier, "can be added to 0 and is not iterable" could be a good definition in some cases. But, you really need to ask yourself, what it is that you're asking that what you want to consider "a number" must definitely be able to do, and what it must absolutely be unable to do -- and check.

This may also be needed in 2.6 or later, perhaps for the purpose of making your own registrations to add types you care about that haven't already be registered onto numbers.Numbers -- if you want to exclude some types that claim they're numbers but you just can't handle, that takes even more care, as ABCs have no unregister method [[for example you could make your own ABC WeirdNum and register there all such weird-for-you types, then first check for isinstance thereof to bail out before you proceed to checking for isinstance of the normal numbers.Number to continue successfully.

BTW, if and when you need to check if x can or cannot do something, you generally have to try something like:

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

The presence of __add__ per se tells you nothing useful, since e.g all sequences have it for the purpose of concatenation with other sequences. This check is equivalent to the definition "a number is something such that a sequence of such things is a valid single argument to the builtin function sum", for example. Totally weird types (e.g. ones that raise the "wrong" exception when summed to 0, such as, say, a ZeroDivisionError or ValueError &c) will propagate exception, but that's OK, let the user know ASAP that such crazy types are just not acceptable in good company;-); but, a "vector" that's summable to a scalar (Python's standard library doesn't have one, but of course they're popular as third party extensions) would also give the wrong result here, so (e.g.) this check should come after the "not allowed to be iterable" one (e.g., check that iter(x) raises TypeError, or for the presence of special method __iter__ -- if you're in 2.5 or earlier and thus need your own checks).

A brief glimpse at such complications may be sufficient to motivate you to rely instead on abstract base classes whenever feasible...;-).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • But there is an ABC for Number in the numbers module. That's what the docs claim: "The numbers module (PEP 3141) defines a hierarchy of numeric abstract base classes which progressively define more operations." – Steven Rumbalski Aug 09 '10 at 15:37
17

This is a good example where exceptions really shine. Just do what you would do with the numeric types and catch the TypeError from everything else.

But obviously, this only checks if a operation works, not whether it makes sense! The only real solution for that is to never mix types and always know exactly what typeclass your values belong to.

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • 1
    +1 for Duck Typing: it doesn't matter what type my data is, just whether or not I can do what I want with it. – systempuntoout Aug 09 '10 at 15:13
  • 12
    This was the traditional approach, but ABCs have been introduced in good part to get *away* from pure duck typing and shift some distance towards a world where `isinstance` can actually be _useful_ in many cases (=="check it makes sense" as well as formal applicability of operations). Difficult shift for long-time Python-only people, but a very important subtle trend in Python's philosophy that it would be a serious error to ignore. – Alex Martelli Aug 09 '10 at 15:25
  • @Alex: True and I love typeclasses ( mostly `collections.Sequence` and friends). But afaik, there are no such classes for numbers, vectors or any other mathematical objects. – Jochen Ritzel Aug 09 '10 at 15:33
  • 1
    Nothing against duck typing. It's what i would do. But there is an abstract base class for numbers: numbers.Number. – Steven Rumbalski Aug 09 '10 at 15:47
  • I don't think ducktyping is a good solution in that case. Imagine if I have a nested structure, that might contain numbers, or sequences. I want to add the numbers. Sadly, sequences can be "added" too, but `(1,) + (3,)` is clearly not the same "addition" as `1 + 3`. – Stef Dec 28 '21 at 12:50
4

Multiply the object by zero. Any number times zero is zero. Any other result means that the object is not a number (including exceptions)

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

Using isNumber thusly will give the following output:

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()))

Output:

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)

There probably are some non-number objects in the world that define __mul__ to return zero when multiplied by zero but that is an extreme exception. This solution should cover all normal and sane code that you generate/encouter.

numpy.array example:

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()))

output:

False == isNumber([0 1])
shrewmouse
  • 5,338
  • 3
  • 38
  • 43
3

To rephrase your question, you are trying to determine whether something is a collection or a single value. Trying to compare whether something is a vector or a number is comparing apples to oranges - I can have a vector of strings or numbers, and I can have a single string or single number. You are interested in how many you have (1 or more), not what type you actually have.

my solution for this problem is to check whether the input is a single value or a collection by checking the presence of __len__. For example:

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]

Or, for the duck-typing approach, you can try iterating on foo first:

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]

Ultimately, it is easier to test whether something is vector-like than to test whether something is scalar-like. If you have values of different type (i.e. string, numeric, etc.) coming through, then the logic of your program may need some work - how did you end up trying to multiply a string by a numeric vector in the first place?

Gordon Bean
  • 4,272
  • 1
  • 32
  • 47
3

To summarize / evaluate existing methods:

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

(I came here by this question)

Code

#!/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))
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
2

Probably it's better to just do it the other way around: You check if it's a vector. If it is, you do a dot product and in all other cases you attempt scalar multiplication.

Checking for the vector is easy, since it should of your vector class type (or inherited from it). You could also just try first to do a dot-product, and if that fails (= it wasn't really a vector), then fall back to scalar multiplication.

sth
  • 222,467
  • 53
  • 283
  • 367
1

Just to add upon. Perhaps we can use a combination of isinstance and isdigit as follows to find whether a value is a number (int, float, etc)

if isinstance(num1, int) or isinstance(num1 , float) or num1.isdigit():

shadab.tughlaq
  • 444
  • 6
  • 15
1

can be implemented in a simple try exception block

def check_if_number(str1):
    try:
        int(float(str1))
        return 'number'
    except:
        return 'not a number'

a = check_if_number('32322')
print (a)
# number
SuperNova
  • 25,512
  • 7
  • 93
  • 64
0

I had a similar issue, when implementing a sort of vector class. One way to check for a number is to just convert to one, i.e. by using

float(x)

This should reject cases where x cannot be converted to a number; but may also reject other kinds of number-like structures that could be valid, for example complex numbers.

Ant6n
  • 1,887
  • 1
  • 20
  • 26
0

For the hypothetical vector class:

Suppose v is a vector, and we are multiplying it by x. If it makes sense to multiply each component of v by x, we probably meant that, so try that first. If not, maybe we can dot? Otherwise it's a type error.

EDIT -- the below code doesn't work, because 2*[0]==[0,0] instead of raising a TypeError. I leave it because it was commented-upon.

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 )
Katriel
  • 120,462
  • 19
  • 136
  • 170
  • if `x` is a vector then `[comp * x for comp in self]` will yield the outer product of `x` an `v`. This is a rank 2 tensor, not a scalar. – aaronasterling Aug 09 '10 at 20:13
  • change "not a scalar" to "not a vector". at least not in the original vector space. – aaronasterling Aug 09 '10 at 20:27
  • Heh, actually we're both wrong. You're assuming that `comp*x` will scale `x` by `comp`, I was assuming that it would raise a TypeError. Unfortunately, it will actually concatenate `x` with itself `comp` times. Oops. – Katriel Aug 09 '10 at 20:46
  • meh. if `x` is a vector then it should have a `__rmul__` method (`__rmul__ = __mul__`) so that `comp * x` should scale `x` in the same way that `x * comp` is apparently intended to. – aaronasterling Aug 09 '10 at 22:15
0

If you want to call different methods depending on the argument type(s), look into multipledispatch.

For example, say you are writing a vector class. If given another vector, you want to find the dot product. If given a scalar, you want to scale the whole vector.

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]

Unfortunately, (to my knowledge) we can't write @dispatch(Vector) since we are still defining the type Vector, so that type name is not yet defined. Instead, I'm using the base type list, which allows you to even find the dot product of a Vector and a list.

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
0

Short and simple way :

obj = 12345
print(isinstance(obj,int))

Output :

True

If the object is a string, 'False' will be returned :

obj = 'some string'
print(isinstance(obj,int))

Output :

False
Shekhar
  • 33
  • 8
0

You have a data item, say rec_day that when written to a file will be a float. But during program processing it can be either float, int or str type (the str is used when initializing a new record and contains a dummy flag value).

You can then check to see if you have a number with this

                type(rec_day) != str 

I've structured a python program this way and just put in 'maintenance patch' using this as a numeric check. Is it the Pythonic way? Most likely not since I used to program in COBOL.

CopyPasteIt
  • 532
  • 1
  • 8
  • 22
0

You can use numbers.Number to check if an object is a number.

For numbers, Python 3 supports 3 types int, float and complex types so if checking the 3 types of values with numbers.Number as shown below:

import numbers

print(type(100), isinstance(100, numbers.Number))
print(type(100.23), isinstance(100.23, numbers.Number))
print(type(100 + 2j), isinstance(100 + 2j, numbers.Number))

All return True as shown below:

<class 'int'> True
<class 'float'> True
<class 'complex'> True

And, for numbers, Python 2 supperts 4 types int, long, float and complex types so if checking the 4 types of values with numbers.Number as shown below::

import numbers

print(type(100), isinstance(100, numbers.Number))
print(type(10000000000000000000), isinstance(10000000000000000000, numbers.Number))
print(type(100.23), isinstance(100.23, numbers.Number))
print(type(100 + 2j), isinstance(100 + 2j, numbers.Number))

All return True as shown below:

(<type 'int'>, True)
(<type 'long'>, True)
(<type 'float'>, True)
(<type 'complex'>, True)
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
-1

You could use the isdigit() function.

>>> x = "01234"
>>> a.isdigit()
True
>>> y = "1234abcd"
>>> y.isdigit()
False
rsy
  • 192
  • 2
  • 10