check if object is number

S

Steven Bethard

Is there a good way to determine if an object is a numeric type?
Generally, I avoid type-checks in favor of try/except blocks, but I'm
not sure what to do in this case:

def f(i):
...
if x < i:
...

The problem is, no error will be thrown if 'i' is, say, a string:

py> 1 < 'a'
True
py> 10000000000 < 'a'
True

But for my code, passing a string is bad, so I'd like to provide an
appropriate error.

I thought about calling int() on the value, but this will also allow
some strings (e.g. '1'). I guess this isn't horrible, but it seems
somewhat suboptimal...

Ideas?

Steve
 
D

Dan Bishop

Steven said:
Is there a good way to determine if an object is a numeric type?
Generally, I avoid type-checks in favor of try/except blocks, but I'm
not sure what to do in this case:

def f(i):
...
if x < i:
...

The problem is, no error will be thrown if 'i' is, say, a string:

py> 1 < 'a'
True
py> 10000000000 < 'a'
True

But for my code, passing a string is bad, so I'd like to provide an
appropriate error.

I thought about calling int() on the value, but this will also allow
some strings (e.g. '1'). I guess this isn't horrible, but it seems
somewhat suboptimal...

How about this?

.... def is_number(x):
.... try:
.... x + 1
.... return True
.... except TypeError:
.... return False
 
B

Bill Mill

Is there a good way to determine if an object is a numeric type?
Generally, I avoid type-checks in favor of try/except blocks, but I'm
not sure what to do in this case:

def f(i):
...
if x < i:
...

The problem is, no error will be thrown if 'i' is, say, a string:

py> 1 < 'a'
True
py> 10000000000 < 'a'
True

But for my code, passing a string is bad, so I'd like to provide an
appropriate error.

How about:
if type(variable) == type(1):
print "is an integer"
else:
print "please input an integer"
 
S

Steven Bethard

Bill said:
How about:
if type(variable) == type(1):
print "is an integer"
else:
print "please input an integer"

This checks if it is an integer, not if it is a numeric type:

py> x = 1
py> type(x) == type(1)
True
py> # using int instead of type(1)
py> type(x) == int
True
py> x = 1.0
py> type(x) == int
False

Steve
 
S

Steven Bethard

Dan said:
How about this?

... def is_number(x):
... try:
... x + 1
... return True
... except TypeError:
... return False

Great, thanks! That's the kind of thing I was looking for!

Steve
 
F

Fredrik Lundh

Steven said:
Is there a good way to determine if an object is a numeric type? Generally, I avoid type-checks in
favor of try/except blocks, but I'm not sure what to do in this case:

def f(i):
...
if x < i:
...

The problem is, no error will be thrown if 'i' is, say, a string:

py> 1 < 'a'
True
py> 10000000000 < 'a'
True

But for my code, passing a string is bad, so I'd like to provide an appropriate error.

assert operator.isNumberType(i)

(but make sure you read the warning in the docs:
http://docs.python.org/lib/module-operator.html )

</F>
 
G

George Sakkis

Steven Bethard said:
Is there a good way to determine if an object is a numeric type?
Generally, I avoid type-checks in favor of try/except blocks, but I'm
not sure what to do in this case:

def f(i):
...
if x < i:
...

The problem is, no error will be thrown if 'i' is, say, a string:

py> 1 < 'a'
True
py> 10000000000 < 'a'
True

But for my code, passing a string is bad, so I'd like to provide an
appropriate error.

I thought about calling int() on the value, but this will also allow
some strings (e.g. '1'). I guess this isn't horrible, but it seems
somewhat suboptimal...

Ideas?

Steve

The thing is that python avoids giving a strict definition of what is a 'numeric type', i.e. a
well-defined interface (the same holds for other frequently used interfaces such as 'sequence',
'mapping', 'file-like', etc). It's up to you (the user) to think of all the operations you
anticipate to be supported by each argument, attribute, etc. and define a 'numeric type checker' (or
even better, adaptor) that suits *your application*. In your example, what does your application
consider to be numeric ? Strings are definitely not, integers and floats typically are; on the other
hand, complex numbers are usually not expected, even though mathematically they are 'numeric'. In
this case, you can write something like:
def isNumeric(obj):
# consider only ints and floats numeric
return isinstance(obj,int) or isinstance(obj,float)

The bottom line is that python wisely doesn't enforce any universal numeric type ("in the face of
ambiguity, refuse the temptation to guess").

George
 
S

Steven Bethard

George said:
In your example, what does your application consider to be numeric?

Well, here's the basic code:

def f(max=None):
...
while max is None or n <= max:
...
# complicated incrementing of n

So for 'max', technically all I need is <= support. However, the code
also depends on the fact that after incrementing 'n' enough, it will
eventually exceed 'max'. Currently, ints, longs, floats, and Decimals
will all meet this behavior. But I'd rather not specify only those 4
(e.g. with a typecheck), since someone could relatively easily create
their own new numeric type with the same behavior. Do you know a better
way to test for this kind of behavior?

Steve
 
J

John Lenton

Well, here's the basic code:

def f(max=None):
...
while max is None or n <= max:
...
# complicated incrementing of n

So for 'max', technically all I need is <= support. However, the code
also depends on the fact that after incrementing 'n' enough, it will
eventually exceed 'max'. Currently, ints, longs, floats, and Decimals
will all meet this behavior. But I'd rather not specify only those 4
(e.g. with a typecheck), since someone could relatively easily create
their own new numeric type with the same behavior. Do you know a better
way to test for this kind of behavior?

Why don't you express just this need as an assertion?

assert 0 <= max <= max + 1, 'Argument must not be zany'

(or whatever error message is suitable)

--
John Lenton ([email protected]) -- Random fortune:
<Silvrbear> Oxymorons? I saw one yesterday - the pamphlet on "Taco Bell
Nutritional Information"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.0 (GNU/Linux)

iD8DBQFCDR0DgPqu395ykGsRAsZ0AKCSwEft713brTFzqOkLbkzQPFbfCgCfZPju
ik3e+/fqeqxX3tps6hELGnw=
=ZYYX
-----END PGP SIGNATURE-----
 
F

Fredrik Lundh

Michael Hartl wrote
As luck would have it, isnumber is a built-in Python function:

False

and what strange Python version would that be?
Traceback (most recent call last):
File "<stdin>", line 1, in ?
NameError: name 'isnumber' is not defined

</F>
 
G

George Sakkis

Michael Hartl said:
As luck would have it, isnumber is a built-in Python function:

False

Michael

Huh ? What python version has this ? Surely not 2.3 and 2.4.

George
 
G

George Sakkis

George said:
Well, here's the basic code:

def f(max=None):
...
while max is None or n <= max:
...
# complicated incrementing of n

So for 'max', technically all I need is <= support. However, the code
also depends on the fact that after incrementing 'n' enough, it will
eventually exceed 'max'. Currently, ints, longs, floats, and Decimals
will all meet this behavior. But I'd rather not specify only those 4
(e.g. with a typecheck), since someone could relatively easily create
their own new numeric type with the same behavior. Do you know a better
way to test for this kind of behavior?

Steve

The problem is more conceptional rather than technical. So what you're saying is that 3 <= "3.0"
should not be allowed, but 3 <= SomeUserDefinedNumericClass(3) is ok, although your program knows
nothing a priori about SomeUserDefinedNumericClass. The idea suggested before, try if "x+1" fails or
not does not get you far; any class that overrides __add__ may fool this test:

class Dummy:
def __add__(self,other):
return 0

Is Dummy 'numeric' ? Probably not. Whether a given instance is "numeric" (or "sequence",
"file-like", etc. for that matter) is a semantic issue. It cannot be resolved by relying on which
methods are supported or not, but what these methods *do*; this is something that no programming
language can tell you now and in the (foreseeable) future.

George
 
S

Steven Bethard

George said:
The problem is more conceptional rather than technical.
Yup.

So what you're saying is that 3 <= "3.0" should not be allowed, but
3 <= SomeUserDefinedNumericClass(3) is ok, although your program knows
nothing a priori about SomeUserDefinedNumericClass. The idea suggested
before, try if "x+1" fails or not does not get you far; any class that
overrides __add__ may fool this test:

class Dummy:
def __add__(self,other):
return 0

Right but by the same logic, we can fool protocols that are builtin to
Python:

py> class C(object):
.... def __iter__(self):
.... return 0
....
py> for x in C():
.... print x
....
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: iter() returned non-iterator of type 'int'

I'm not trying to catch the case where someone is deliberately violating
the protocol. Only the case where they've provided an object that
doesn't conform to the protcol.


Clearly, complex objects don't conform to the protocol I want -- they
don't support comparisons to ints. This is easy to check:

try:
x <= 1
except TypeError:
raise TypeError('%s does not support comparisons to ints' %
type(x).__name__)

Now str objects also don't conform my protocol -- they aren't comparable
to integers in any meaningful sense. Ideally, I should be able to use
the same code as above for str objects (and get a TypeError like "str
objects cannot be compared with int objects"), but unfortunately, the
Python wart of cross-type default comparisons gets in the way. I look
forward to the day when Python 3.0 removes such comparisons. =)

Steve
 
S

Steven Bethard

Fredrik said:
assert operator.isNumberType(i)

Interesting, thanks! If I read the source right, PyNumber_Check (which
operator.isNumberType is an alias for) basically just returns True if
the object's type has a __int__ or __float__ method defined:

int
PyNumber_Check(PyObject *o)
{
return o && o->ob_type->tp_as_number &&
(o->ob_type->tp_as_number->nb_int ||
o->ob_type->tp_as_number->nb_float);
}

Did I read that right?

Steve
 
M

Michael Hartl

Oops, my bad. The utilities file I use gets loaded automatically when
I start my interpreter, so I mistook isnumber for a built-in function.
A battle-tested isnumber function is defined in Peter Norvig's utils.py
(http://aima.cs.berkeley.edu/python/utils.py):

#def isnumber(x):
# "Is x a number? We say it is if it has an __int__ method."
# return hasattr(x, '__int__')

Michael
 
S

Steven Bethard

John said:
Why don't you express just this need as an assertion?

assert 0 <= max <= max + 1, 'Argument must not be zany'

Well a TypeError might be raised in executing the expression:

py> max = complex(0, 1)
py> assert 0 <= max <= max + 1, 'Argument must not be zany'
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: cannot compare complex numbers using <, <=, >, >=

but I do like the max <= max + 1 idea. Maybe I should do something like:

py> def assertnumber(x):
.... try:
.... 1 < x
.... except TypeError:
.... raise TypeError('%s is not comparable to int' %
.... type(x).__name__)
.... try:
.... if not x <= x + 1:
.... raise TypeError
.... except TypeError:
.... raise TypeError('%s is not monotonic' %
.... type(x).__name__)
....
py> assertnumber(1)
py> assertnumber(1.0)
py> assertnumber(complex(0, 1))
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 5, in assertnumber
TypeError: complex is not comparable to int
py> assertnumber('a')
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 11, in assertnumber
TypeError: str is not monotonic
py> class C(object):
.... def __add__(self, other):
.... return 0
.... def __lt__(self, other):
.... return True
....
py> assertnumber(C())
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 11, in assertnumber
TypeError: C is not monotonic

Steve
 
G

George Sakkis

So what you're saying is that 3 <= "3.0" should not be allowed, but
Right but by the same logic, we can fool protocols that are builtin to
Python:

py> class C(object):
... def __iter__(self):
... return 0
...
py> for x in C():
... print x
...
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: iter() returned non-iterator of type 'int'

I'm not trying to catch the case where someone is deliberately violating
the protocol. Only the case where they've provided an object that
doesn't conform to the protcol.

Note however that in my example there's no TypeError; 0 (or any value for that matter) is perfectly
valid for __add__, while __iter__ must return an iterator.
Clearly, complex objects don't conform to the protocol I want -- they
don't support comparisons to ints. This is easy to check:

try:
x <= 1
except TypeError:
raise TypeError('%s does not support comparisons to ints' %
type(x).__name__)

Now str objects also don't conform my protocol -- they aren't comparable
to integers in any meaningful sense. Ideally, I should be able to use
the same code as above for str objects (and get a TypeError like "str
objects cannot be compared with int objects"), but unfortunately, the
Python wart of cross-type default comparisons gets in the way. I look
forward to the day when Python 3.0 removes such comparisons. =)

Me too... the way comparison works as of now is error-prone, to say the least. Comparing ints with
strings is no more valid than adding them, and python (correctly) raises TypeError for the latter,
but not for the former. For the record, here's the arbitrary and undocumented (?) order among some
main builtin types:
None < 0 == 0.0 < {} < [] < "" < ()
True

George
 
S

Steven Bethard

George said:
For the record, here's the arbitrary and undocumented (?) order
among some main builtin types:
None < 0 == 0.0 < {} < [] < "" < ()

If you're curious, you can check the source code. Look for
default_3way_compare in object.c. Basically the rundown for dissimilar
types is:

* None is smaller than anything
* Numbers (as defined by PyNumber_Check) are smaller than everything else
* All other types are compared by type name (and by the address of the
type object if the type names are the same).

Pretty arbitrary, yeah. ;)

Steve
 
M

marco

Steven said:
Is there a good way to determine if an object is a numeric type? ..
..
..

Ideas?
Maybe this can help?

def isnumber(x):
try:
return(x == x-0)
except:
return False

print '1:\t', isnumber(1)
print '1.25:\t', isnumber(1.25)
print '1.0 / 7:\t', isnumber(1.0 / 7)
print '1+0j:\t', isnumber((1+0j))
print '"spam":\t', isnumber("spam")

output:

1: True
1.25: True
1.0/7: True
1+0j: True
"spam": False

Ooops! While checking other posts I realized that this is almost the
same as Dan Bishop's solution.

Marco
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Forum statistics

Threads
474,219
Messages
2,571,120
Members
47,740
Latest member
george1

Latest Threads

Top