如何确定一个Python对象是否为数字?这里的“是”指在某些情况下表现得像数字。
例如,假设你正在编写一个向量类。如果给定另一个向量,则要计算点积。如果给定标量,则要缩放整个向量。
检查是否为int
、float
、long
、bool
很烦人,并且不包括可能像数字一样运作的用户定义对象。但是,仅仅检查__mul__
等特殊方法并不够好,因为刚才描述的向量类将定义__mul__
,但它不是我想要的数字类型。
numbers
模块中的Number
来测试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(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
当然,这与鸭子类型相反。如果您更关心对象的行为而不是其本质,请按照数字的方式执行操作,并使用异常来告诉您其他情况。
您想检查某个对象在特定情况下是否像数字。
表现得像数字的一些情况
如果您使用的是 Python 2.5 或更早版本,则唯一真正的方法是检查其中一些"特定情况"并查看结果。
在 2.6 或更高版本中,您可以使用 numbers.Number(一个专门用于此目的的抽象基类(ABC))和 isinstance
来进行检查(在 2.6 中引入了很多 ABC,包括各种形式的集合/容器,在这些版本中,您还可以轻松添加自己的抽象基类,如果需要的话)。
回到 2.5 及更早版本,"可以添加到 0
并且不可迭代" 可能是某些情况下的好定义。但是,您确实需要问自己,您要考虑的 "数字" 必须绝对能够做什么以及必须绝对无法做什么,并进行检查。
numbers.Numbers
的类型 - 如果您想要排除一些声称它们是数字但您无法处理的类型,则需要更加小心,因为ABC没有unregister
方法[[例如,您可以创建自己的ABC WeirdNum
并在其中注册所有这些对您来说奇怪的类型,然后首先检查其是否为isinstance
以退出,然后再继续成功检查正常的numbers.Number
的isinstance
。
顺便说一句,如果您需要检查x
是否可以做某事或不能做某事,通常必须尝试以下操作:
try: 0 + x
except TypeError: canadd=False
else: canadd=True
__add__
的存在本身并没有提供有用的信息,因为例如所有序列都具有它,目的是与其他序列连接。这个检查等同于定义“数字是这样一种东西,即这样一些东西的序列是内置函数 sum
的有效单个参数”,例如。完全奇怪的类型(例如当与0相加时引发“错误”异常的类型,例如 ZeroDivisionError
或 ValueError
等)将传播异常,但这没关系,让用户尽快知道这些疯狂的类型在好公司中不可接受;-);但是,可加和为标量的“向量”(Python 标准库没有,但当然作为第三方扩展很受欢迎)在这里也会给出错误的结果,因此(例如)这个检查应该在“不允许迭代”之后进行(例如,检查 iter(x)
是否引发 TypeError
,或者检查特殊方法 __iter__
的存在 - 如果您在 2.5 或更早版本中,因此需要自己进行检查)。
对这些复杂性的简要了解可能足以激励您在可能的情况下改而依赖抽象基类...;-)。
这是一个很好的例子,展示了异常处理的优点。只需像处理数值类型一样操作,并捕获其他所有异常的TypeError
。
但是,显然,这只检查操作是否“可行”,而不是它们是否“有意义”!真正的解决方法是永远不要混合类型,并始终知道您的值属于哪个类型类。
isinstance
在许多情况下实际上可以是有用的(例如“检查它是否有意义”以及操作的正式适用性)。对于长期只使用Python的人来说,这是一个困难的转变,但这是Python哲学中非常重要而微妙的趋势,忽视它将是一个严重的错误。 - Alex Martellicollections.Sequence
和相关类别)。不过据我所知,针对数字、向量或其他数学对象没有这样的类别。 - Jochen Ritzel(1,) + (3,)
显然不是与1 + 3
相同的“加法”。 - Stef将对象乘以零。任何数乘以零都是零。任何其他结果都意味着该对象不是一个数字(包括异常情况)。
def isNumber(x):
try:
return bool(0 == x*0)
except:
return False
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])
True * 0 == 0
的翻译是: 真乘以零等于零。 - endolithint
,因此我的函数将正确地表示布尔值是数字。 - shrewmouse__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]
最终,测试某物是否类似于向量比测试某物是否类似于标量更容易。如果您有不同类型的值(例如字符串、数字等)传入,则程序逻辑可能需要进行一些调整 - 您如何首先尝试将字符串乘以数值向量?
总结/评估现有的方法:
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))
float('nan'),'nan','123.45','42','42a','0x8','0xa'
,添加math.isnan
。 - Martin Thomadef check_if_number(str1):
try:
int(float(str1))
return 'number'
except:
return 'not a number'
a = check_if_number('32322')
print (a)
# number
补充一下。 也许我们可以使用isinstance和isdigit的组合来判断一个值是否为数字(int,float等)
if isinstance(num1, int) or isinstance(num1 , float) or num1.isdigit():
对于假设的向量类:
假设v
是一个向量,我们正在将其乘以x
。如果每个v
的分量乘以x
都有意义,那么我们可能是这个意思,因此先尝试这样做。如果不行,也许我们可以点积?否则就是类型错误。
编辑——下面的代码不起作用,因为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 )
x
是一个向量,那么 [comp * x for comp in self]
将产生 x
和 v
的外积。这是一个二阶张量,而不是标量。 - aaronasterlingcomp*x
将通过comp
缩放x
,而我则假设它会引发TypeError。不幸的是,它实际上会将x
与自身连接comp
次。糟糕。 - Katrielx
是一个向量,那么它应该有一个 __rmul__
方法 (__rmul__ = __mul__
),这样 comp * x
就可以按照 x * comp
明显意图的方式缩放 x
。 - aaronasterling
isinstance(value, Number) and type(value) != bool
。 - Yo Ludkefloat("-inf")
是一个数字。根据情况,这可能不合适。 - Grey Christoforo