Есть ли способ сказать, представляет ли строка целое число (например '3'
, '-17'
но не '3.14'
или 'asfasfas'
), не используя механизм try / исключением?
is_int('3.14') = False
is_int('-7') = True
Есть ли способ сказать, представляет ли строка целое число (например '3'
, '-17'
но не '3.14'
или 'asfasfas'
), не используя механизм try / исключением?
is_int('3.14') = False
is_int('-7') = True
Ответы:
Если вы действительно раздражаетесь от использования try/except
s повсеместно, просто напишите вспомогательную функцию:
def RepresentsInt(s):
try:
int(s)
return True
except ValueError:
return False
>>> print RepresentsInt("+123")
True
>>> print RepresentsInt("10.0")
False
Это будет ПУТЬ больше кода, чтобы точно охватить все строки, которые Python считает целыми числами. Я говорю просто будь питоническим на этом.
>>> print RepresentsInt(10.0)
True
>>> print RepresentsInt(10.06)
True
с положительными целыми числами вы можете использовать .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()
u'²'.isdigit()
верно, но int(u'²')
вызывает ValueError. Используйте u.isdecimal()
вместо этого. str.isdigit()
зависит от языка Python 2.
check_int('')
будет False
Знаете, я обнаружил (и проверял это снова и снова), что попытка / исключение не так хорошо работает по любой причине. Я часто пробую несколько способов сделать что-то, и я не думаю, что я когда-либо нашел метод, который использует try /, кроме как для выполнения лучших из протестированных, на самом деле мне кажется, что эти методы, как правило, приближаются к худший, если не худший. Не в каждом случае, но во многих случаях. Я знаю, что многие люди говорят, что это "Pythonic", но это одна из областей, где я расстаюсь с ними. Для меня это не очень производительно и не очень элегантно, поэтому я склонен использовать его только для отслеживания ошибок и составления отчетов.
Я собирался понять, что в PHP, perl, ruby, C и даже уродливой оболочке есть простые функции для проверки строки на целочисленность, но должная осмотрительность в проверке этих предположений меня смутила! По-видимому, этот недостаток является обычной болезнью.
Вот быстрое и грязное редактирование поста Бруно:
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
Метод AC может сканировать его один раз, и будет сделано. Я думаю, что метод AC, который сканирует строку один раз, будет правильным решением.
РЕДАКТИРОВАТЬ:
Я обновил приведенный выше код для работы в Python 3.5, а также для включения функции check_int из ответа, получившего наибольшее количество голосов, и использования текущего наиболее популярного регулярного выражения, которое я могу найти для тестирования на целочисленность. Это регулярное выражение отклоняет строки типа 'abc 123'. Я добавил 'abc 123' в качестве тестового значения.
Мне очень интересно отметить, что на этом этапе ни одна из протестированных функций, включая метод try, популярную функцию check_int и самое популярное регулярное выражение для тестирования целочисленных значений, не возвращает правильных ответов для всех значения теста (ну, в зависимости от того, что вы думаете, правильные ответы; см. результаты теста ниже).
Встроенная функция int () молча обрезает дробную часть числа с плавающей запятой и возвращает целую часть перед десятичной дробью, если только число с плавающей запятой сначала не преобразуется в строку.
Функция check_int () возвращает false для значений, таких как 0.0 и 1.0 (которые технически являются целыми числами), и возвращает true для значений, таких как '06'.
Вот текущие (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), и возвращает true для значений, таких как 1.0 и 0.0 и +1.0 и 0. и .0 и так далее. Но это также возвращает истину для «06», так что. Выбери свой яд, наверное.
try
это более эффективно: isInt_try: 0.6552 / isInt_str: 0.6396 / isInt_re: 1.0296 / isInt_re2: 0.5168.
str.isdigit()
должен сделать свое дело.
Примеры:
str.isdigit("23") ## True
str.isdigit("abc") ## False
str.isdigit("23.4") ## False
РЕДАКТИРОВАТЬ : Как @BuzzMoschetti указал, этот путь не будет работать для минус число (например, «-23» ). Если ваш input_num может быть меньше 0, используйте re.sub (regex_search, regex_replace, contents) перед применением str.isdigit () . Например:
import re
input_num = "-23"
input_num = re.sub("^-", "", input_num) ## "^" indicates to remove the first "-" only
str.isdigit(input_num) ## True
Используйте регулярное выражение:
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()
.
Правильное решение RegEx объединит идеи Грега Хьюгилла и Новелла, но не будет использовать глобальную переменную. Вы можете сделать это, прикрепив атрибут к методу. Кроме того, я знаю, что не рекомендуется помещать импорт в метод, но я собираюсь использовать эффект «ленивого модуля», например http://peak.telecommunity.com/DevCenter/Importing#lazy-imports.
edit: Моя любимая техника до сих пор заключается в использовании исключительно методов объекта 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
>>> "+7".lstrip("-+").isdigit()
True
>>> "-7".lstrip("-+").isdigit()
True
>>> "7".lstrip("-+").isdigit()
True
>>> "13.4".lstrip("-+").isdigit()
False
Таким образом, ваша функция будет:
def is_int(val):
return val[1].isdigit() and val.lstrip("-+").isdigit()
В подходе Грега Хьюгилла не хватало нескольких компонентов: ведущий «^» соответствовал только началу строки и предварительно компилировал re. Но такой подход позволит вам избежать попытки: exept:
import re
INT_RE = re.compile(r"^[-]?\d+$")
def RepresentsInt(s):
return INT_RE.match(str(s)) is not None
Мне было бы интересно, почему вы пытаетесь избежать попробовать: кроме?
Я должен делать это все время, и у меня есть легкое, но, по общему признанию, иррациональное отвращение к использованию шаблона «попробуй / кроме». Я использую это:
all([xi in '1234567890' for xi in x])
Он не содержит отрицательных чисел, поэтому вы можете удалить один знак минус (если есть), а затем проверить, содержит ли результат цифры от 0 до 9:
all([xi in '1234567890' for xi in x.replace('-', '', 1)])
Вы также можете передать x в str (), если вы не уверены, что ввод является строкой:
all([xi in '1234567890' for xi in str(x).replace('-', '', 1)])
Есть по крайней мере два (край?) Случая, когда это разваливается:
type(1E2)
дает, <class 'float'>
тогда как type(10^2)
дает <class 'int'>
.Таким образом, он не будет работать для всех возможных входных данных, но если вы можете исключить научную запись, экспоненциальную запись и пустые строки, то это будет однострочная проверка ОК, которая возвращает, False
если x не является целым числом и True
если x является целым числом.
Я не знаю, является ли это pythonic, но это одна строка, и это относительно ясно, что делает код.
all(xi in '1234567890' for xi in x])
шаблон больше похож на просьбу разрешить пройти по лужайке. Я не в восторге от того, что спрашиваю разрешения, но мы здесь.
Я думаю
s.startswith('-') and s[1:].isdigit()
было бы лучше переписать на:
s.replace('-', '').isdigit()
потому что s [1:] также создает новую строку
Но гораздо лучшее решение
s.lstrip('+-').isdigit()
replace
делает? Кроме того, это будет неправильно принимать 5-2
, например.
s='-'
Мне очень понравился пост Шавайса, но я добавил еще один тестовый пример (и встроенную функцию 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 --version
Python 2.7.10
Оба тестовых примера, которые я добавил (isInt_loop и isInt_digit), проходят одинаковые тестовые примеры (они оба принимают только целые числа без знака), но я подумал, что люди могут быть более умными с изменением реализации строки (isInt_loop) в отличие от встроенного isdigit (), поэтому я включил ее, хотя есть небольшая разница во времени выполнения. (и оба метода намного превосходят все остальное, но не обрабатывают лишние вещи: "./+/-")
Кроме того, мне было интересно отметить, что регулярное выражение (метод isInt_re2) побеждает сравнение строк в том же тесте, который был выполнен Shavais в 2012 году (в настоящее время 2018). Может быть, библиотеки регулярных выражений были улучшены?
Это, пожалуй, самый простой и питонный способ приблизиться к этому, на мой взгляд. Я не видел это решение, и оно в основном такое же, как и регулярное выражение, но без регулярного выражения.
def is_int(test):
import string
return not (set(test) - set(string.digits))
set(input_string) == set(string.digits)
если мы пропускаем '-+ '
в начале и .0
, E-1
в конце концов.
Вот функция, которая анализирует без возникновения ошибок. Он обрабатывает очевидные случаи возврата None
при сбое (по умолчанию обрабатывает до 2000 знаков '- / +' на CPython!):
#!/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
Я предлагаю следующее:
import ast
def is_int(s):
return isinstance(ast.literal_eval(s), int)
Из документов :
Безопасно оцените узел выражения или строку, содержащую литерал Python или отображение контейнера. Предоставленная строка или узел могут состоять только из следующих литеральных структур Python: строк, байтов, чисел, кортежей, списков, диктов, множеств, логических значений и None.
Я должен отметить, что это вызовет ValueError
исключение при вызове против всего, что не является литералом Python. Поскольку вопрос задан для решения без попытки / кроме, у меня есть решение типа Кобаяси-Мару для этого:
from ast import literal_eval
from contextlib import suppress
def is_int(s):
with suppress(ValueError):
return isinstance(literal_eval(s), int)
return False
¯ \ _ (ツ) _ / ¯
У меня есть одна возможность, которая вообще не использует int, и не должна вызывать исключение, если строка не представляет число
float(number)==float(number)//1
Это должно работать для любого вида строк, которые могут принимать плавающие, положительные, отрицательные, инженерные обозначения ...
Я предполагаю, что вопрос связан со скоростью, так как попытка / исключение имеет штраф за время:
Сначала я создал список из 200 строк, 100 строк с ошибками и 100 строк с цифрами.
from random import shuffle
numbers = [u'+1'] * 100
nonumbers = [u'1abc'] * 100
testlist = numbers + nonumbers
shuffle(testlist)
testlist = np.array(testlist)
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 намного быстрее.
Если вы хотите принимать только младшие цифры, вот тесты для этого:
Python 3.7+: (u.isdecimal() and u.isascii())
Python <= 3.6: (u.isdecimal() and u == str(int(u)))
Другие ответы предлагают использовать .isdigit()
или, .isdecimal()
но оба они включают некоторые символы верхнего юникода, такие как '٢'
( u'\u0662'
):
u = u'\u0662' # '٢'
u.isdigit() # True
u.isdecimal() # True
u.isascii() # False (Python 3.7+ only)
u == str(int(u)) # False
int()
.
Э-э .. Попробуйте это:
def int_check(a):
if int(a) == a:
return True
else:
return False
Это работает, если вы не поместите строку, которая не является числом.
А также (я забыл поставить часть проверки номера.), Есть функция, проверяющая, является ли строка числом или нет. Это str.isdigit (). Вот пример:
a = 2
a.isdigit()
Если вы вызовете a.isdigit (), он вернет True.
2
присвоенного a
.