如何在不使用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个回答

5

Greg Hewgill的方法缺少了一些组件:在正则表达式中添加一个"^"只匹配字符串的开头,以及预先编译正则表达式。但这种方法可以避免使用try: except:语句。

import re
INT_RE = re.compile(r"^[-]?\d+$")
def RepresentsInt(s):
    return INT_RE.match(str(s)) is not None

我很想知道你为什么要避免使用try: except语句?


3
风格问题。我认为应该仅在实际出错时使用“try/except”,而不是在正常程序流程中使用它。 - Adam Matan
2
@Udi Pasmon:Python在“正常”程序流程中相当频繁地使用try/except。例如,每个迭代器都会停止并引发异常。 - S.Lott
3
尽管你提到编译正则表达式的提示是正确的,但在批评 Greg 的另一个方面时你是错误的:re.match 与字符串的开头匹配,因此模式中的 ^ 实际上是多余的。(当使用 re.search 时情况会有所不同)。 - ThomasH
S.Lott - 这在Python中被认为是合理的流程吗?这与其他语言有何不同?也许值得单独提出一个问题。 - Adam Matan
1
Python在try/except方面的大量使用已经在SO上进行了讨论。尝试搜索“[python] except”。 - S.Lott
@ThomasH 我之前没有意识到 re.match 在开头隐含了一个 ^。(这不是很“明确”吗?) 我不记得了,但我肯定在过去遇到过这个问题。因此,我很高兴有这次交流。 - Bruno Bronosky

5

我认为

s.startswith('-') and s[1:].isdigit()

最好重写为:

s.replace('-', '').isdigit()

因为 s[1:] 也会创建一个新的字符串

但更好的解决方案是

s.lstrip('+-').isdigit()

3
你知道 replace 是做什么的吗?另外,这会错误地接受 5-2 这样的输入。 - Ry-
如果s='-',将会抛出一个IndexError。 - Anti Earth
s = '-'; s.replace('-', '').isdigit() -> Falses = '-'; s.replace('-', '').isdigit() -> False - Vladyslav Savchenko
1
s.lstrip('+-').isdigit() accepts stuff like -+1 or +++++++1 - user9645
请注意,创建字符串也是一种不好的习惯。这比使用try catch的问题要小一些,但也很难看。 - Ievgen

3

前提条件:

  • 我们在讨论整数(不是小数/浮点数);
  • 内置的int()行为对我们来说是标准的(有时它很奇怪:“-00”是它的正确输入)。

简短回答:

使用以下代码。它是简单的正确的(而这个线程中的许多变体并不是)并且几乎try/exceptregex变体快两倍

def is_int_str(string):
    return (
        string.startswith(('-', '+')) and string[1:].isdigit()
    ) or string.isdigit()

简短回答:

我测试了三种主要的变体:(1) try/except,(2) re.match()和(3)字符串操作(见上文)。第三个变体比try/exceptre.match()都快大约两倍。顺便说一下:正则表达式变体是最慢的!请参见下面的测试脚本。

import re
import time


def test(func, test_suite):
    for test_case in test_suite:
        actual_result = func(*test_case[0])
        expected_result = test_case[1]
        assert (
            actual_result == expected_result
        ), f'Expected: {expected_result} but actual: {actual_result}'


def perf(func, test_suite):
    start = time.time()

    for _ in range(0, 1_000_000):
        test(func, test_suite)

    return time.time() - start


def is_int_str_1(string):
    try:
        int(string)
        return True
    except ValueError:
        return False


def is_int_str_2(string):
    return re.match(r'^[\-+]?\d+$', string) is not None


def is_int_str_3(string):
    return (
        string.startswith(('-', '+')) and string[1:].isdigit()
    ) or string.isdigit()


# Behavior of built-in int() function is a standard for the following tests
test_suite = [
    [['1'], True],  # func('1') -> True
    [['-1'], True],
    [['+1'], True],
    [['--1'], False],
    [['++1'], False],
    [['001'], True],  # because int() can read it
    [['-00'], True],  # because of quite strange behavior of int()
    [['-'], False],
    [['abracadabra'], False],
    [['57938759283475928347592347598357098458405834957984755200000000'], True],
]

time_span_1 = perf(is_int_str_1, test_suite)
time_span_2 = perf(is_int_str_2, test_suite)
time_span_3 = perf(is_int_str_3, test_suite)

print(f'{is_int_str_1.__name__}: {time_span_1} seconds')
print(f'{is_int_str_2.__name__}: {time_span_2} seconds')
print(f'{is_int_str_3.__name__}: {time_span_3} seconds')


输出如下:
is_int_str_1: 4.314162969589233 seconds
is_int_str_2: 5.7216269969940186 seconds
is_int_str_3: 2.5828163623809814 seconds

我认为你的正则表达式变体是最慢的,因为你没有先预编译正则表达式模式。当我预编译正则表达式时,它变成了第二快的。在Python 3.7上:9.66 | 7.03 | 4.86,在Python 3.8上:7.78 | 5.56 | 4.57 - pepoluan

2

我非常喜欢Shavais的帖子,但我添加了一个测试用例(和内置的isdigit()函数):

def isInt_loop(v):
    v = str(v).strip()
    # swapping '0123456789' for '9876543210' makes nominal difference (might have because '1' is toward the beginning of the string)
    numbers = '0123456789'
    for i in v:
        if i not in numbers:
            return False
    return True

def isInt_Digit(v):
    v = str(v).strip()
    return v.isdigit()

并且它显著地一直超越了其他时间:
timings..
isInt_try:   0.4628
isInt_str:   0.3556
isInt_re:    0.4889
isInt_re2:   0.2726
isInt_loop:   0.1842
isInt_Digit:   0.1577

使用普通的Python 2.7:

$ python --version
Python 2.7.10

我添加的两个测试用例(isInt_loop和isInt_digit)通过了完全相同的测试用例(它们都只接受无符号整数),但我认为人们可以更聪明地修改字符串实现(isInt_loop)而不是内置的isdigit()函数,所以我包含了它,即使执行时间略有不同。(两种方法都远远超过其他方法,但不能处理额外的内容:“./+/-”)
此外,我发现有趣的是,正则表达式(isInt_re2方法)在Shavais于2012年进行的相同测试中击败了字符串比较(目前是2018年)。也许正则表达式库已经得到改进?

1
这是一个可以在不引发错误的情况下进行解析的函数。它处理明显的情况,并在失败时返回None(默认情况下在CPython上处理多达2000个“- / +”符号!):
#!/usr/bin/env python

def get_int(number):
    splits = number.split('.')
    if len(splits) > 2:
        # too many splits
        return None
    if len(splits) == 2 and splits[1]:
        # handle decimal part recursively :-)
        if get_int(splits[1]) != 0:
            return None

    int_part = splits[0].lstrip("+")
    if int_part.startswith('-'):
        # handle minus sign recursively :-)
        return get_int(int_part[1:]) * -1
    # successful 'and' returns last truth-y value (cast is always valid)
    return int_part.isdigit() and int(int_part)

一些测试:

tests = ["0", "0.0", "0.1", "1", "1.1", "1.0", "-1", "-1.1", "-1.0", "-0", "--0", "---3", '.3', '--3.', "+13", "+-1.00", "--+123", "-0.000"]

for t in tests:
    print "get_int(%s) = %s" % (t, get_int(str(t)))

结果:

get_int(0) = 0
get_int(0.0) = 0
get_int(0.1) = None
get_int(1) = 1
get_int(1.1) = None
get_int(1.0) = 1
get_int(-1) = -1
get_int(-1.1) = None
get_int(-1.0) = -1
get_int(-0) = 0
get_int(--0) = 0
get_int(---3) = -3
get_int(.3) = None
get_int(--3.) = 3
get_int(+13) = 13
get_int(+-1.00) = -1
get_int(--+123) = 123
get_int(-0.000) = 0

您可以使用以下内容来满足您的需求:

def int_predicate(number):
     return get_int(number) is not None

1

在我看来,这可能是最直接和Pythonic的方法。我没有看到这个解决方案,它基本上与正则表达式相同,但没有使用正则表达式。

def is_int(test):
    import string
    return not (set(test) - set(string.digits))

如果我们跳过开头的'-+ '和结尾的.0E-1,那么set(input_string) == set(string.digits) - jfs

1
我有一种可能性,完全不使用int,并且只有在字符串不能表示数字时才会引发异常。
float(number)==float(number)//1

它应该适用于任何 float 可接受的字符串,包括正数、负数、工程符号等。

1
如果您只想接受小写ASCII数字,以下是要执行的测试:
Python 3.7+:(u.isdecimal() and u.isascii()) Python <= 3.6:(u.isdecimal() and u == str(int(u))) 其他答案建议使用 .isdigit().isdecimal(),但这些方法both include some upper-unicode characters,例如'٢' (u'\u0662')。
u = u'\u0662'     # '٢'
u.isdigit()       # True
u.isdecimal()     # True
u.isascii()       # False (Python 3.7+ only)
u == str(int(u))  # False

这段代码无法处理负数或者带空格的值,而 int() 函数可以很好地处理它们。 - ShadowRanger

0

我理解您想要检查字符串是否可以转换为整数。为了做到这一点,您可以:

  1. 将“-”替换为空,因为“-”不是数字,“-7”也可以转换为整数。
  2. 检查它是否为数字。
def is_string_convertable_to_int(value: str) -> bool:
    return value.replace('-', '').isdigit()

顺便说一句,您可以轻松修改此函数以检查字符串是否可转换为浮点数,只需添加 replace('.', '') 并使用 value.count('.') = 1 检查一个 '.' 是否存在。


0

检查整数后将值转换为字符串,然后检查字符串的第一个字符值是-+,并且其余部分是isdigit。最后只需检查isdigit

test = ['1','12015','1..2','a2kk78','1.5',2,1.24,'-8.5','+88751.71','-1','+7']

检查

for k,v in enumerate(test): 
    print(k, v, 'test: ', True if isinstance(v, int) is not False else True if str(v)[0] in ['-', '+'] and str(v)[1:].isdigit() else str(v).isdigit())

结果

0 1 test:  True
1 12015 test:  True
2 1..2 test:  False
3 a2kk78 test:  False
4 1.5 test:  False
5 2 test:  True
6 1.24 test:  False
7 -8.5 test:  False
8 +88751.71 test:  False
9 -1 test:  True
10 +7 test:  True

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