什么是最pythonic的方式来检查一个对象是一个数字?

给定一个任意的python对象,确定它是否是一个数字的最好方法是什么? 这里is定义为acts like a number in certain circumstances一样的acts like a number in certain circumstances

例如,假设你正在写vector类。 如果给出另一个vector,你想find点积。 如果给定一个标量,你想要扩展整个向量。

检查是否intfloatlongbool是烦人的,并且不包含可能像数字一样的用户定义的对象。 但是,例如,检查__mul__不够好,因为我刚描述的向量类将定义__mul__ ,但它不会是我想要的那种数字。

使用numbers模块中的Number来testingisinstance(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 

当然,这与鸭子打字相反。 如果你更关心的是一个对象的行为而不是它什么,就像你有一个数字一样执行你的操作,并用exception来告诉你。

你想检查一些对象

在某些情况下就像一个数字

如果您使用Python 2.5或更高版本,唯一真正的方法是检查一些“特定情况”并查看。

在2.6或更高版本中,可以使用isinstance和numbers.Number – 一个抽象基类(ABC),它完全是为了这个目的而存在的(在collections模块中为各种forms的集合/容器存在更多的ABCs,而且,只有在这些版本中,如果需要,您可以轻松地添加自己的抽象基类)。

巴赫到2.5和更早的时候,“可以加0 ,不可迭代”在某些情况下可能是一个很好的定义。 但是,你真的需要问自己,你在问什么是你想要考虑的“一个数字”,一定是能够做到的 ,什么是绝对不能做到的 – 并且检查。

这可能也需要在2.6或更高版本,也许是为了使自己的注册添加你所关心的,尚未注册到numbers.Numberstypes的目的 – 如果你想排除某些types,声称他们是数字,但你不能处理,这需要更多的关注,因为ABC没有unregister方法[例如,你可以做自己的ABC WeirdNum并在那里注册所有这样怪异的types,然后首先检查其实例在继续检查正常numbers.Number的情况下,先numbers.Numbernumbers.Number继续成功。

顺便说一句,如果当你需要检查x可以做或不能做什么,你通常必须尝试像这样:

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

__add__本身的存在告诉你什么都没有用,因为例如所有的序列都有与其他序列连接的目的。 例如,这个检查等同于定义“一个数字是这样的事情序列是一个有效的内部函数sum单一参数”。 完全奇怪的types(例如,当总结为0时引起“错误”exception的types,例如ZeroDivisionErrorValueError &c)将传播exception,但这没问题,让用户知道这样疯狂的types是不可接受的在良好的公司;-); 但是,一个可汇总为标量的“向量”(Python的标准库没有一个,当然它们当然是第三方的扩展)也会在这里给出错误的结果,所以(例如)这个检查应该 (例如,检查iter(x)会引发TypeError ,或者是否存在特殊的方法__iter__ – 如果您在2.5或更早的版本中,则需要您自己的检查)。

简单地看一下这种复杂情况,可能足以激励你在可行的时候依靠抽象基类来实现… ;-)。

这是一个很好的例子,例外真的很闪耀。 只要做你会做的数字types,并从其他一切捕获TypeError

但显然,这只会检查一个操作是否有效 ,而不是判断是否合理 。 唯一真正的解决scheme就是永远不要混合types,并且始终确切知道你的值属于哪个types。

为了改变你的问题,你正试图确定某件事是一个集合还是一个单一的价值。 试图比较是否是vector或数字是苹果比较桔子 – 我可以有一个string或数字的向量,我可以有一个单一的string或单一的数字。 你感兴趣的是你有多less(1或更多) ,而不是你实际拥有的types。

我解决这个问题的方法是通过检查__len__的存在来检查input是单个值还是集合。 例如:

 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] 

或者,对于duck-typing方法,您可以先尝试迭代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] 

最终,testing是否有类似vector的东西比testing是否具有标量类更容易。 如果你有不同types的值(即string,数字等),那么你的程序的逻辑可能需要一些工作 – 你怎么最终试图乘以一个数字向量的string呢?

也许最好反其道而行:你检查它是否是一个向量。 如果是的话,你做一个点积,在其他情况下,你尝试标量乘。

检查向量是很容易的,因为它应该是你的向量类types(或inheritance它)。 你也可以先尝试一个点积,如果失败了(=它不是一个向量),则返回到标量乘。

只是为了增加。 也许我们可以使用isinstance和isdigit的组合来查找一个值是否是数字(int,float等)

如果isinstance(num1,int)或isinstance(num1,float)或num1.isdigit():

将对象乘以零。 任何数字时间零是零。 任何其他结果意味着该对象不是一个数字(包括例外)

 def isNumber(x): try: return 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__乘以零时返回零,但这是一个极端的例外。 这个解决scheme应该覆盖你生成/编码的所有正常合理的代码。

对于假设的vector类:

v是一个向量,我们乘以x 。 如果用x乘以v的每个分量是有意义的,那么我们可能就是这个意思了,所以先试试。 如果没有,也许我们可以点? 否则,这是一个types错误。

编辑 – 下面的代码不起作用,因为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 ) 

在实现一个向量类的时候,我遇到了类似的问题。 一种方法来检查一个数字是只是转换为一个,即通过使用

 float(x) 

这应该拒绝x不能被转换成数字的情况; 但是也可以拒绝其他types的可能有效的数字结构,例如复数。

你可以使用isdigit()函数。

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

总结/评估现有的方法:

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