如何在不使用 try/except 的情况下检查字符串是否代表 int?[重复]
- 2024-12-03 08:44:00
- admin 原创
- 172
问题描述:
有没有办法判断一个字符串是否代表一个整数(例如,'3'
但'-17'
不是'3.14'
或'asfasfas'
),而不使用try/except机制?
is_int('3.14') == False
is_int('-7') == True
解决方案 1:
对于正整数,您可以使用.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()
解决方案 2:
如果你真的只是讨厌到处使用try/except
s,请编写一个辅助函数:
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 风格。
解决方案 3:
你知道,我发现(并且我反复测试过)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( " %s |" % 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( " %s |" % func(v))
sys.stdout.write("
")
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
AC 方法可以扫描一次,然后就完成了。我认为,扫描一次字符串的 AC 方法是正确的做法。
编辑:
我已更新上述代码,使其可以在 Python 3.5 中使用,并包含当前得票最多的答案中的 check_int 函数,并使用我能找到的当前最流行的正则表达式来测试整数性。此正则表达式拒绝像“abc 123”这样的字符串。我已添加“abc 123”作为测试值。
令我非常感兴趣的是,在这一点上,所测试的所有函数(包括 try 方法、流行的 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,所以。我想,选择你的毒药吧。
解决方案 4:
str.isdigit()
应该可以解决问题。
例子:
str.isdigit("23") ## True
str.isdigit("abc") ## False
str.isdigit("23.4") ## False
编辑:正如@BuzzMoschetti 指出的那样,这种方法对于负数(例如"-23")将失败。如果您的input_num小于 0,请在应用str.isdigit()之前使用re.sub(regex_search,regex_replace,contents)。例如:
import re
input_num = "-23"
input_num = re.sub("^-", "", input_num) ## "^" indicates to remove the first "-" only
str.isdigit(input_num) ## True
解决方案 5:
使用正则表达式:
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()
。
解决方案 6:
>>> "+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()
解决方案 7:
正确的 RegEx 解决方案将结合 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
解决方案 8:
最简单的方法,我用的是
def is_int(item: str) -> bool:
return item.lstrip('-+').isdigit()
解决方案 9:
我总是这样做,因为我对使用 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.2E3、10^3 等)——两者都将返回 False。我认为其他答案也没有考虑到这一点,甚至 Python 3.8 也有不一致的意见,因为
type(1E2)
给出了<class 'float'>
而type(10^2)
给出了<class 'int'>
。空字符串输入将返回 True。
前导加号(例如“+7”)表示 False。
只要是前导字符,多个减号就会被忽略。此行为类似于
type(---1)
返回的 Python 解释器*。但是,它与给出错误的<class int>
解释器并不完全一致,但我的解决方案返回相同的输入。int('---1')
`True`
因此它不会对所有可能的输入都起作用,但如果您可以排除这些输入,那么它就是一个不错的单行检查,它会返回False
x 是否为整数以及x 是否为整数。但如果您真的想要完全模拟内置True
的行为,最好使用 try/except。int()
我不知道它是否符合 Python 风格,但它只有一行,而且代码的作用相对清晰。
*我并不是说解释器会忽略前导减号,只是说任何数量的前导减号都不会改变结果为整数。int(--1)
实际上被解释为-(-1)
,或 1。int(---1)
被解释为-(-(-1))
,或 -1。因此,偶数个前导减号会给出一个正整数,奇数个减号会给出一个负整数,但结果始终是一个整数。
解决方案 10:
可以用下面的方法来检查。
def check_if_string_is_int(string1):
for character in string1:
if not character.isdigit():
return "Not a number"
else:
return "Is a number"
解决方案 11:
Greg Hewgill 的方法缺少几个组件:前导“^”仅匹配字符串的开头,并预先编译 re。但这种方法可以让您避免 try: exept:
import re
INT_RE = re.compile(r"^[-]?d+$")
def RepresentsInt(s):
return INT_RE.match(str(s)) is not None
我感兴趣你为什么要避免尝试:except?
解决方案 12:
我认为
s.startswith('-') and s[1:].isdigit()
最好重写为:
s.replace('-', '').isdigit()
因为 s[1:] 也创建了一个新字符串
但更好的解决方案是
s.lstrip('+-').isdigit()
解决方案 13:
先决条件:
我们讨论的是整数(不是小数/浮点数);
内置行为
int()
对我们来说是一个标准(有时很奇怪:“-00”是正确的输入)
简短回答:
使用以下代码。它很简单、正确(而此线程中的许多变体却不正确),并且性能几乎比和变体都高出两倍。try/except
`regex`
def is_int_str(string):
return (
string.startswith(('-', '+')) and string[1:].isdigit()
) or string.isdigit()
TL;DR 答案:
我测试了 3 个主要变体 (1) try/except、(2) re.match() 和 (3) 字符串操作(见上文)。第三个变体比和都快两倍左右try/except
。re.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
解决方案 14:
我真的很喜欢 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
使用普通的 2.7 python:
$ python --version
Python 2.7.10
我添加的两个测试用例(isInt_loop 和 isInt_digit)都通过了完全相同的测试用例(它们都只接受无符号整数),但我认为人们可以更聪明地修改字符串实现(isInt_loop)而不是内置的 isdigit() 函数,所以我把它包括在内,即使执行时间略有不同。(这两种方法都比其他方法好很多,但不处理额外的东西:“。/+/-”)
此外,我发现一个有趣的现象:在 Shavais 于 2012 年(目前为 2018 年)进行的相同测试中,正则表达式(isInt_re2 方法)击败了字符串比较。也许正则表达式库已经得到改进?
解决方案 15:
在我看来,这可能是最直接、最 Python 化的方法。我没有看到这个解决方案,它基本上与正则表达式解决方案相同,但没有正则表达式。
def is_int(test):
import string
return not (set(test) - set(string.digits))
解决方案 16:
我有一种根本不使用 int 的可能性,除非字符串不代表数字,否则不应引发异常
float(number)==float(number)//1
它应该适用于浮点数接受的任何类型的字符串,正数、负数、工程符号......
解决方案 17:
这是一个解析时不会引发错误的函数。它处理明显的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
解决方案 18:
如果您只想接受较低的 ASCII 数字,请进行以下测试:
Python 3.7+:(u.isdecimal() and u.isascii())
Python <= 3.6:(u.isdecimal() and u == str(int(u)))
其他答案建议使用.isdigit()
或.isdecimal()
但这些都包含一些大写Unicode字符如'٢'
( u'/u0662'
):
u = u'/u0662' # '٢'
u.isdigit() # True
u.isdecimal() # True
u.isascii() # False (Python 3.7+ only)
u == str(int(u)) # False
解决方案 19:
我建议如下:
import ast
def is_int(s):
return isinstance(ast.literal_eval(s), int)
来自文档:
安全地评估包含 Python 文字或容器显示的表达式节点或字符串。提供的字符串或节点只能由以下 Python 文字结构组成:字符串、字节、数字、元组、列表、字典、集合、布尔值和 None。
我应该注意,当对任何不构成 Python 文字的内容进行调用时,这将引发ValueError
异常。由于问题要求不使用 try/except 的解决方案,因此我有一个 Kobayashi-Maru 类型的解决方案:
from ast import literal_eval
from contextlib import suppress
def is_int(s):
with suppress(ValueError):
return isinstance(literal_eval(s), int)
return False
¯\_(ツ)_/¯
解决方案 20:
检查后将值转换为字符串是否为整数,然后检查字符串第一个字符的值是否为-
或+
以及字符串的其余部分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
解决方案 21:
我猜这个问题与速度有关,因为 try/except 有时间惩罚:
测试数据
首先,我创建了一个包含 200 个字符串、100 个失败字符串和 100 个数字字符串的列表。
from random import shuffle
numbers = [u'+1'] * 100
nonumbers = [u'1abc'] * 100
testlist = numbers + nonumbers
shuffle(testlist)
testlist = np.array(testlist)
numpy 解决方案(仅适用于数组和 unicode)
np.core.defchararray.isnumeric 也可以处理 unicode 字符串np.core.defchararray.isnumeric(u'+12')
,但它返回的是数组。因此,如果您必须进行数千次转换并且有缺失数据或非数字数据,这是一个很好的解决方案。
import numpy as np
%timeit np.core.defchararray.isnumeric(testlist)
10000 loops, best of 3: 27.9 µs per loop # 200 numbers per loop
尝试/除外
def check_num(s):
try:
int(s)
return True
except:
return False
def check_list(l):
return [check_num(e) for e in l]
%timeit check_list(testlist)
1000 loops, best of 3: 217 µs per loop # 200 numbers per loop
看来 numpy 解决方案要快得多。
解决方案 22:
据我了解,您想检查 int 中的字符串可转换性。为此,您可以:
将“-”替换为无,因为“-”不是数字,并且“-7”也可以转换为整数。
检查它是否是数字。
def is_string_convertable_to_int(value: str) -> bool:
return value.replace('-', '').isdigit()
PS 您可以轻松修改此定义以检查浮点数中的字符串可转换性,只需添加 replace('.', '') 并使用 value.count('.') = 1 检查一个 '.' 是否存在。
解决方案 23:
呃..试试这个:
def int_check(a):
if int(a) == a:
return True
else:
return False
如果您没有输入非数字的字符串,则此方法有效。
另外(我忘了加上数字检查部分。),还有一个函数用于检查字符串是否为数字。它是 str.isdigit()。以下是示例:
a = 2
a.isdigit()
如果你调用 a.isdigit(),它将返回 True。