如何在不使用try/except的情况下检查一个字符串是否表示一个整数?

717

有没有一种方法可以判断一个字符串是否表示一个整数(例如'3''-17'但不是'3.14''asfasfas'),而不使用try / except机制?

is_int('3.14') == False
is_int('-7')   == True

28
为什么要“费力地”这样做?用try/except有什么问题吗? - S.Lott
8
是的,使用try/except有什么问题?宁愿请求宽恕,也不要征得许可。 - mk12
82
我会问为什么这个简单的事情需要使用try/except?异常系统是一个复杂的东西,但这只是一个简单的问题。 - Aivar
18
@Aivar停止散播恐慌情绪。一个try/except块甚至都不算“复杂”。 - Kenan Banks
68
这并不是真正的FUD。你实际上要编写4行代码,期望某些东西会出错,捕获异常并执行默认操作,而不是使用一行代码。 - andersonvom
显示剩余11条评论
23个回答

1105

如果使用正整数,可以使用.isdigit方法:

>>> '16'.isdigit()
True

不过它不能处理负整数。假设你可以尝试以下方法:

>>> s = '-17'
>>> s.startswith('-') and s[1:].isdigit()
True

使用格式为'16.0'的数据不起作用,这类似于int类型的强制转换。

编辑:

def check_int(s):
    if s[0] in ('-', '+'):
        return s[1:].isdigit()
    return s.isdigit()

8
这段代码不能处理 "+17",需要增加一个特殊情况来解决。 - Bryan Oakley
1
你需要测试这两种情况:lambda s: s.isdigit() or (s.startswith('-') and s[1:].isdigit())。 - rob
4
@Roberto:当然你应该这样做!我相信你有能力做到! - SilentGhost
50
请使用 u.isdecimal() 代替 str.isdigit()。在 Python 2 中,str.isdigit() 的结果会受地区设置的影响。需要注意的是,虽然 u'²'.isdigit() 返回 True,但 int(u'²') 会引发 ValueError 错误。 - jfs
8
check_int('') 会引发异常,而不是返回 False - wordbug
显示剩余6条评论

503

如果您只是厌烦在各个地方使用try/except,请编写一个辅助函数:

def represents_int(s):
    try: 
        int(s)
    except ValueError:
        return False
    else:
        return True
>>> print(represents_int("+123"))
True
>>> print(represents_int("10.0"))
False

要完全覆盖Python认为是整数的所有字符串,需要编写更多的代码。我建议在这个问题上遵循Python语言的风格。


172
使用复杂机制解决简单问题符合Pythonic吗?有一种算法可以检测函数“int”中编写的整数 - 我不明白为什么它没有被公开为布尔函数。 - Aivar
97
这个5行函数并不是一个复杂的机制。 - Kenan Banks
48
除非: >>> print RepresentsInt(10.0) 返回 True,否则返回 False 除非: >>> print RepresentsInt(10.06) 返回 True,否则返回 False - Dannid
102
我不知道为什么这个答案被接受或者获得了那么多赞,因为它完全相反于OP所询问的。 - FearlessFuture
14
问题中包含:“不使用try / except机制?”。该要求未得到满足,因为try/except被使用(即使是包装在函数中)。 - CristiFati
显示剩余22条评论

136
你知道吗,我发现(并且一再测试)try/except并不表现得很好,原因不明。我经常尝试几种做事情的方法,但我从来没有找到一种使用try/except的方法是最好的,实际上在我测试的这些方法中,它们似乎通常是最差的,如果不是最差的话,也接近最差。虽然并非每种情况都如此,但在许多情况下都是如此。我知道很多人说这是“Pythonic”的方式,但在这一点上,我与他们分道扬镳。对我来说,它既不是非常高效,也不是非常优雅,所以,我倾向于仅将其用于错误捕获和报告。
我本来想抱怨PHP、perl、ruby、C甚至是该死的shell都有简单的函数来测试字符串是否为整数,但是验证这些假设的尽职尽责却使我困惑了!显然,这种缺乏是一种常见的疾病。
以下是Bruno帖子的快速而简单的编辑:
import sys, time, re

g_intRegex = re.compile(r"^([+-]?[1-9]\d*|0)$")

testvals = [
    # integers
    0, 1, -1, 1.0, -1.0,
    '0', '0.','0.0', '1', '-1', '+1', '1.0', '-1.0', '+1.0', '06',
    # non-integers
    'abc 123',
    1.1, -1.1, '1.1', '-1.1', '+1.1',
    '1.1.1', '1.1.0', '1.0.1', '1.0.0',
    '1.0.', '1..0', '1..',
    '0.0.', '0..0', '0..',
    'one', object(), (1,2,3), [1,2,3], {'one':'two'},
    # with spaces
    ' 0 ', ' 0.', ' .0','.01 '
]

def isInt_try(v):
    try:     i = int(v)
    except:  return False
    return True

def isInt_str(v):
    v = str(v).strip()
    return v=='0' or (v if v.find('..') > -1 else v.lstrip('-+').rstrip('0').rstrip('.')).isdigit()

def isInt_re(v):
    import re
    if not hasattr(isInt_re, 'intRegex'):
        isInt_re.intRegex = re.compile(r"^([+-]?[1-9]\d*|0)$")
    return isInt_re.intRegex.match(str(v).strip()) is not None

def isInt_re2(v):
    return g_intRegex.match(str(v).strip()) is not None

def check_int(s):
    s = str(s)
    if s[0] in ('-', '+'):
        return s[1:].isdigit()
    return s.isdigit()    


def timeFunc(func, times):
    t1 = time.time()
    for n in range(times):
        for v in testvals: 
            r = func(v)
    t2 = time.time()
    return t2 - t1

def testFuncs(funcs):
    for func in funcs:
        sys.stdout.write( "\t%s\t|" % func.__name__)
    print()
    for v in testvals:
        if type(v) == type(''):
            sys.stdout.write("'%s'" % v)
        else:
            sys.stdout.write("%s" % str(v))
        for func in funcs:
            sys.stdout.write( "\t\t%s\t|" % func(v))
        sys.stdout.write("\r\n") 

if __name__ == '__main__':
    print()
    print("tests..")
    testFuncs((isInt_try, isInt_str, isInt_re, isInt_re2, check_int))
    print()

    print("timings..")
    print("isInt_try:   %6.4f" % timeFunc(isInt_try, 10000))
    print("isInt_str:   %6.4f" % timeFunc(isInt_str, 10000)) 
    print("isInt_re:    %6.4f" % timeFunc(isInt_re, 10000))
    print("isInt_re2:   %6.4f" % timeFunc(isInt_re2, 10000))
    print("check_int:   %6.4f" % timeFunc(check_int, 10000))

以下是性能比较结果:
timings..
isInt_try:   0.6426
isInt_str:   0.7382
isInt_re:    1.1156
isInt_re2:   0.5344
check_int:   0.3452

一个C方法可以一次扫描,然后就完成了。我认为,一种扫描字符串的C方法是正确的做法。 编辑: 我已经更新了上面的代码以在Python 3.5中工作,并包括来自当前最受欢迎的回答的check_int函数,并使用我能找到的当前最流行的用于测试整数性的正则表达式。此正则表达式拒绝像'abc 123'这样的字符串。我添加了'abc 123'作为测试值。 非常有趣的是,在这一点上,所有测试过的函数,包括尝试方法、流行的check_int函数和测试整数性的最流行的正则表达式,都没有返回所有测试值的正确答案(好吧,这取决于你认为正确答案是什么;请参见下面的测试结果)。 内置的int()函数会默默地截断浮点数的小数部分,并在小数点前返回整数部分,除非浮点数首先被转换为字符串。 check_int()函数对于像0.0和1.0这样的值返回false(这在技术上是整数),并对像'06'这样的值返回true。 以下是当前(Python 3.5)的测试结果:
              isInt_try |       isInt_str       |       isInt_re        |       isInt_re2       |   check_int   |
0               True    |               True    |               True    |               True    |       True    |
1               True    |               True    |               True    |               True    |       True    |
-1              True    |               True    |               True    |               True    |       True    |
1.0             True    |               True    |               False   |               False   |       False   |
-1.0            True    |               True    |               False   |               False   |       False   |
'0'             True    |               True    |               True    |               True    |       True    |
'0.'            False   |               True    |               False   |               False   |       False   |
'0.0'           False   |               True    |               False   |               False   |       False   |
'1'             True    |               True    |               True    |               True    |       True    |
'-1'            True    |               True    |               True    |               True    |       True    |
'+1'            True    |               True    |               True    |               True    |       True    |
'1.0'           False   |               True    |               False   |               False   |       False   |
'-1.0'          False   |               True    |               False   |               False   |       False   |
'+1.0'          False   |               True    |               False   |               False   |       False   |
'06'            True    |               True    |               False   |               False   |       True    |
'abc 123'       False   |               False   |               False   |               False   |       False   |
1.1             True    |               False   |               False   |               False   |       False   |
-1.1            True    |               False   |               False   |               False   |       False   |
'1.1'           False   |               False   |               False   |               False   |       False   |
'-1.1'          False   |               False   |               False   |               False   |       False   |
'+1.1'          False   |               False   |               False   |               False   |       False   |
'1.1.1'         False   |               False   |               False   |               False   |       False   |
'1.1.0'         False   |               False   |               False   |               False   |       False   |
'1.0.1'         False   |               False   |               False   |               False   |       False   |
'1.0.0'         False   |               False   |               False   |               False   |       False   |
'1.0.'          False   |               False   |               False   |               False   |       False   |
'1..0'          False   |               False   |               False   |               False   |       False   |
'1..'           False   |               False   |               False   |               False   |       False   |
'0.0.'          False   |               False   |               False   |               False   |       False   |
'0..0'          False   |               False   |               False   |               False   |       False   |
'0..'           False   |               False   |               False   |               False   |       False   |
'one'           False   |               False   |               False   |               False   |       False   |
<obj..>         False   |               False   |               False   |               False   |       False   |
(1, 2, 3)       False   |               False   |               False   |               False   |       False   |
[1, 2, 3]       False   |               False   |               False   |               False   |       False   |
{'one': 'two'}  False   |               False   |               False   |               False   |       False   |
' 0 '           True    |               True    |               True    |               True    |       False   |
' 0.'           False   |               True    |               False   |               False   |       False   |
' .0'           False   |               False   |               False   |               False   |       False   |
'.01 '          False   |               False   |               False   |               False   |       False   |

刚才我尝试添加了这个功能:

def isInt_float(s):
    try:
        return float(str(s)).is_integer()
    except:
        return False

它的表现几乎与check_int(0.3486)相同,并且对于像1.0、0.0、+1.0、0.和.0等值返回true。但是它也会对'06'返回true,所以你可以自行选择。


6
时间选择恰当。我同意这个例外处理的方式对于一个如此简单的问题来说不够优雅。你会期望存在一个内置的帮助方法来解决这种常见问题... - RickyA
12
我知道这个帖子基本上是不活跃的,但考虑到运行时间,我支持它。行长度并不总是表明底层复杂性;当然,try/except可能看起来很简单(并且读起来也很容易,这也很重要),但它确实是一个代价高昂的操作。我认为首选级别应该始终类似于以下内容:
  1. 易于阅读的显式解决方案(SilentGhost's)。
  2. 易于阅读的隐式解决方案(Triptych's)。
  3. 没有第三个。
- Eric Humphrey
(.. 等我们等待语言被更改的时候,我们可以创建一个临时的C扩展或将is_int()添加到现有的扩展中。) - Shavais
1
感谢您对这个看似微不足道的话题进行了深入的调查。我会选择isInt_str(),无论它是否符合Pythonic风格。让我困扰的是,我没有找到关于v.find('..')含义的任何信息。这是一种特殊的查找语法还是数字字符串的边缘情况? - JackLeEmmerdeur
3
是的,这篇分析有点过时,但仍然非常好并且相关。在Python 3.5中,使用try更有效率:isInt_try: 0.6552 / isInt_str: 0.6396 / isInt_re: 1.0296 / isInt_re2: 0.5168。 - Dave
显示剩余10条评论

73

str.isdigit() 能够解决问题。

例如:

str.isdigit("23") ## True
str.isdigit("abc") ## False
str.isdigit("23.4") ## False

编辑: 正如@BuzzMoschetti指出的那样,这种方法对于负数(例如“-23”)将会失败。如果您的可能小于0,请在应用之前使用。例如:

import re
input_num = "-23"
input_num = re.sub("^-", "", input_num) ## "^" indicates to remove the first "-" only
str.isdigit(input_num) ## True

4
因为-23得出的结果是false。 - Buzz Moschetti
4
@BuzzMoschetti 你说得对。快速修复的方法是在应用 str.isdigit() 之前,通过 re.replace(regex_search,regex_replace,contents) 去除减号。 - Chau Pham
1
我正好在找这个!尝试从CSV中解析正整数,这非常完美! - Jay Dadhania
s.isnumeric()s.isdigit()有什么区别? - Charlie Parker
1
让我举几个例子来说明:对于字节编号,例如 s = b'123',s.isdigit()=True,但是 s.isnumeric() 和 s.isdecimal() 会抛出 AttributeError 异常。对于中文或罗马数字,例如 s = '三叁' 或 s = 'Ⅲ',s.isdigit()=False 和 s.isdecimal()=False,但是 s.isnumeric()=True。 - K. Symbol

30

使用正则表达式:

import re
def RepresentsInt(s):
    return re.match(r"[-+]?\d+$", s) is not None
如果您必须接受小数分数:
def RepresentsInt(s):
    return re.match(r"[-+]?\d+(\.0*)?$", s) is not None

如果你要经常执行这个操作,为了提高性能,请使用re.compile()只编译一次正则表达式。


24
+1表明与try/except相比,这非常复杂且昂贵。 - S.Lott
2
我认为这本质上是@SilentGhost提供的“isnumeric”解决方案的较慢、定制版本。 - Greg
re 模块无论如何都会缓存编译后的正则表达式。 - Kenan Banks
1
@S.Lott:当然,任何能在SO上发帖的人都应该能够扩展我的示例以涵盖符号。 - SilentGhost
2
正则表达式是存在的最复杂和晦涩的东西之一,我发现上面的简单检查要清晰得多,即使我认为它仍然很丑陋,这更丑陋。 - PlexQ
显示剩余4条评论

20
适当的正则表达式解决方案将结合Greg Hewgill和Nowell的想法,但不使用全局变量。您可以通过附加属性到方法来实现这一点。此外,我知道在方法中放置导入是不受欢迎的,但我所追求的是像http://peak.telecommunity.com/DevCenter/Importing#lazy-imports一样的“懒模块”效果。
编辑:到目前为止,我最喜欢的技术是仅使用String对象的方法。
#!/usr/bin/env python

# Uses exclusively methods of the String object
def isInteger(i):
    i = str(i)
    return i=='0' or (i if i.find('..') > -1 else i.lstrip('-+').rstrip('0').rstrip('.')).isdigit()

# Uses re module for regex
def isIntegre(i):
    import re
    if not hasattr(isIntegre, '_re'):
        print("I compile only once. Remove this line when you are confident in that.")
        isIntegre._re = re.compile(r"[-+]?\d+(\.0*)?$")
    return isIntegre._re.match(str(i)) is not None

# When executed directly run Unit Tests
if __name__ == '__main__':
    for obj in [
                # integers
                0, 1, -1, 1.0, -1.0,
                '0', '0.','0.0', '1', '-1', '+1', '1.0', '-1.0', '+1.0',
                # non-integers
                1.1, -1.1, '1.1', '-1.1', '+1.1',
                '1.1.1', '1.1.0', '1.0.1', '1.0.0',
                '1.0.', '1..0', '1..',
                '0.0.', '0..0', '0..',
                'one', object(), (1,2,3), [1,2,3], {'one':'two'}
            ]:
        # Notice the integre uses 're' (intended to be humorous)
        integer = ('an integer' if isInteger(obj) else 'NOT an integer')
        integre = ('an integre' if isIntegre(obj) else 'NOT an integre')
        # Make strings look like strings in the output
        if isinstance(obj, str):
            obj = ("'%s'" % (obj,))
        print("%30s is %14s is %14s" % (obj, integer, integre))

对于那些不太冒险的班级成员,这是输出结果:

I compile only once. Remove this line when you are confident in that.
                             0 is     an integer is     an integre
                             1 is     an integer is     an integre
                            -1 is     an integer is     an integre
                           1.0 is     an integer is     an integre
                          -1.0 is     an integer is     an integre
                           '0' is     an integer is     an integre
                          '0.' is     an integer is     an integre
                         '0.0' is     an integer is     an integre
                           '1' is     an integer is     an integre
                          '-1' is     an integer is     an integre
                          '+1' is     an integer is     an integre
                         '1.0' is     an integer is     an integre
                        '-1.0' is     an integer is     an integre
                        '+1.0' is     an integer is     an integre
                           1.1 is NOT an integer is NOT an integre
                          -1.1 is NOT an integer is NOT an integre
                         '1.1' is NOT an integer is NOT an integre
                        '-1.1' is NOT an integer is NOT an integre
                        '+1.1' is NOT an integer is NOT an integre
                       '1.1.1' is NOT an integer is NOT an integre
                       '1.1.0' is NOT an integer is NOT an integre
                       '1.0.1' is NOT an integer is NOT an integre
                       '1.0.0' is NOT an integer is NOT an integre
                        '1.0.' is NOT an integer is NOT an integre
                        '1..0' is NOT an integer is NOT an integre
                         '1..' is NOT an integer is NOT an integre
                        '0.0.' is NOT an integer is NOT an integre
                        '0..0' is NOT an integer is NOT an integre
                         '0..' is NOT an integer is NOT an integre
                         'one' is NOT an integer is NOT an integre
<object object at 0x103b7d0a0> is NOT an integer is NOT an integre
                     (1, 2, 3) is NOT an integer is NOT an integre
                     [1, 2, 3] is NOT an integer is NOT an integre
                {'one': 'two'} is NOT an integer is NOT an integre

4
我同意我的测试套件有些过头了。当我编写代码时,我喜欢证明它的工作原理。但是你认为我的isInteger函数也过度了吗?肯定不会。 - Bruno Bronosky
2
我刚刚收到了一个没有评论的负评。人们怎么了?我知道千禧一代现在把“赞”当作“已读回执”。但是他们现在是否将负评用作“不是我选择的方法”标记?也许他们没有意识到,给答案打负分会从你自己的声誉中扣除2分。SO/SE这样做是为了鼓励只因错误信息而进行负评,如果是这种情况,我希望你能留下评论 - Bruno Bronosky

20
>>> "+7".lstrip("-+").isdigit()
True
>>> "-7".lstrip("-+").isdigit()
True
>>> "7".lstrip("-+").isdigit()
True
>>> "13.4".lstrip("-+").isdigit()
False

那么你的函数将是:

def is_int(val):
   return val.lstrip("-+").isdigit()

1
is_int("2")引发了IndexError错误。 - anttikoo
def is_int(val): ... return val.lstrip("-+").isdigit() ... is_int("2") True这不会导致错误。在我看来,这是最好的答案,实际上符合需求,而不依赖于昂贵或奇怪的解决方案。 - Justus Wingert
不错,但例如 is_int('+++25') 返回 True。 - Konstantin Smolyanin

8

我经常这样做,因为我有一种轻微但公开的不合理的厌恶情绪,不愿使用try/except模式。我使用以下方式:

all([xi in '1234567890' for xi in x])

它不支持负数,因此您可以剥离左侧的所有减号,然后检查结果是否包含0-9之间的数字:
all([xi in '1234567890' for xi in x.lstrip('-')])

如果您不确定输入是否为字符串,您也可以将 x 传递给 str():

all([xi in '1234567890' for xi in str(x).lstrip('-')])

这种方法在某些情况下可能会失效:

  1. 对于各种科学和/或指数符号(例如1.2E3、10^3等),它并不能正常工作,两者都会返回False。我认为其他答案也无法处理这种情况,即使Python 3.8的意见也不一致,因为 type(1E2) 返回 <class 'float'>,而 type(10^2) 返回 <class 'int'>
  2. 空字符串输入会返回True。
  3. 前导加号(例如“+7”)会返回False。
  4. 多个减号只要它们是前导字符就会被忽略。这种行为类似于Python解释器*,因为 type(---1) 返回 <class int>。然而,它与解释器并不完全一致,因为int('---1')会报错,但我的解决方案使用相同的输入返回True

因此,它不能处理所有可能的输入,但如果您可以排除这些输入,它是一个可以在一行代码中运行的检查,如果x不是整数,则返回False,如果x是整数则返回True。但是,如果您真的想要完全模拟内置的int()函数行为,最好使用try/except。

我不知道它是否符合Pythonic标准,但它只有一行代码,而且代码的含义相对清晰。

*我并不是说解释器忽略前导减号,而是说任何数量的前导减号都不会改变结果是整数的事实。例如,int(--1)实际上被解释为-(-1),即1。int(---1)被解释为-(-(-1)),即-1。因此,偶数个前导减号得到一个正整数,奇数个减号得到一个负整数,但结果始终是整数。


对于 '12-34',这将返回 True - AMC
@AMC 是的,说得好,我认为这值得排除。我编辑了我的答案,引入了另一个我认为可以接受的警告。然而,这表明这是一个主观问题,因为我们必须假设哪些字符串是和不是可接受的整数表示。我们也不知道我们可以对输入做出什么样的假设。.replace().lstrip() 的实现都适用于 OP 的示例。 - mRotten

6
我使用的最简单的方法是:
def is_int(item: str) -> bool:
    return item.lstrip('-+').isdigit()

2
我简直不敢相信没有内置 .isint() 函数,以及 .isfloat() 和 .isbool()。这是有什么原因吗? - sinekonata
1
是的,这有点荒谬。 - Ari K
1
-+123 got True - yurenchen

5

可以使用以下方法进行检查。

def check_if_string_is_int(string1):
    for character in string1:
        if not character.isdigit():
            return "Not a number"
    else:
        return "Is a number"

+1 是为了简单起见。但请注意,您应该删除 else: 语句。只需要在当前else所在的位置直接写入 return "是数字" 即可。 - Ronald Souza
另外,请注意空格会导致它失败(例如,44 返回 True,但 44 返回 False)。在开头添加 string1 = string1.strip() 可以解决这个问题。 - Ronald Souza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接