706

Is there any way to tell whether a string represents an integer (e.g., '3', '-17' but not '3.14' or 'asfasfas') Without using a try/except mechanism?

is_int('3.14') == False
is_int('-7')   == True
matan h
  • 900
  • 1
  • 10
  • 19
Adam Matan
  • 128,757
  • 147
  • 397
  • 562
  • 28
    Why both trying to do this "the hard way?" What's wrong with try/except? – S.Lott Aug 12 '09 at 12:09
  • 8
    Yes, what's wrong with try/except? Better to ask for forgiveness than for permission. – mk12 Sep 14 '09 at 02:27
  • 82
    I would ask why should this simple thing require try/except? Exception system is a complex beast, but this is a simple problem. – Aivar Sep 23 '11 at 20:18
  • 18
    @Aivar stop spreading FUD. A single try/except block does not even approach "complex". – Kenan Banks Feb 03 '12 at 20:21
  • 68
    It's not really FUD, though. You'd be effectively writing 4 lines of code, expecting something to blow up, catching that exception and doing your default, instead of using a one liner. – andersonvom Nov 12 '13 at 19:32
  • 2
    isinstance(your_variable, basestring) will tell you if the var is either str or unicode, covering both. While this does not answer the specific question above (involving conversion of string to number), it answers at least one question marked duplicate to this one on a different page or at least the question I had in finding this page, is therefore needed. – gseattle Mar 21 '17 at 07:49
  • Homework assignments sometimes forbid use of try:. If an exception occurs, even if you handle it, it will be detected and you fail the assignment. – john k Jan 18 '18 at 23:28
  • 4
    @johnktejik Here's an idea - make a webservice that will answer whether a string can be an integer, then make an HTTP request to it in your assignment's code. The webservice can do the try/catch – Dagrooms Feb 26 '18 at 22:32
  • 3
    @dagrooms Love it. I wonder if their hacker alarms would go off. – john k Feb 26 '18 at 23:21
  • 1
    @S.Lott i am trying to do this in a list comprehension so no try except – PirateApp Jun 02 '18 at 06:05
  • 2
    Exceptions are common in Python. You can't even write a `for` loop without exception handling - a `for` loop stops when the iterator raises a `StopIteration` exception. The **design intent** is that if you want to know whether a string can be interpreted as an integer, you call `int` and catch the exception. – user2357112 Mar 23 '20 at 23:20
  • 5
    "Do it the Pythonic way!", "Do it the Microsoft way!", "Do it the Google way!", "Do it My way!". How about.. The OP asked a question about how to do something and gave the conditions for doing it. I have a similar issue: I'm trying to figure out how to check an item in a list, and the exercise specifically says I cannot use try/except, and some other restrictions, including some restrictions on performance and optimization. Thus far, I still have not found a solution for this exercise. – C. M. May 19 '20 at 05:56
  • 2
    @S.Lott If exceptions are not exceptional, then why are they called exceptions. Exceptions should be for error (and I mean errors: programming errors) handling. Exceptions are the one bit of python that I don't like. – ctrl-alt-delor Jul 22 '20 at 10:32
  • I think the fact that OP doesn't precisely define what _whether a string represents an integer_ means has led to a bunch of unnecessary confusion and arguing. – AMC Oct 28 '20 at 23:12
  • 1
    *Why not use try/except?* Because you can't use them in a lambda function, such as you might supply to `filter()`. Of course, defining a non-lambda function is always short and clear so it's not a biggie but it is valid question. – President James K. Polk Dec 05 '20 at 14:58
  • what is the difference with `s.isnumeric()`? – Charlie Parker Nov 26 '21 at 18:13

23 Answers23

1092

with positive integers you could use .isdigit:

>>> '16'.isdigit()
True

it doesn't work with negative integers though. suppose you could try the following:

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

it won't work with '16.0' format, which is similar to int casting in this sense.

edit:

def check_int(s):
    if s[0] in ('-', '+'):
        return s[1:].isdigit()
    return s.isdigit()
SilentGhost
  • 307,395
  • 66
  • 306
  • 293
499

If you're really just annoyed at using try/excepts all over the place, please just write a helper function:

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

It's going to be WAY more code to exactly cover all the strings that Python considers integers. I say just be pythonic on this one.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Kenan Banks
  • 207,056
  • 34
  • 155
  • 173
  • 170
    So it's pythonic to solve simple problem with complex mechanism? There is an algorithm for detecting int's written inside function "int" -- I don't see why isn't this exposed as a boolean function. – Aivar Sep 23 '11 at 20:21
  • 97
    @Aivar: This 5 line function is not a complex mechanism. – Kenan Banks Feb 03 '12 at 20:27
  • 3
    Before using this in code that must perform with high frequency, you should see the post by Shavais below where he times the solutions on this page. The fastest solution is my pure string method chaining. – Bruno Bronosky Jun 06 '12 at 14:16
  • 48
    Except:`>>> print RepresentsInt(10.0)` `True` `>>> print RepresentsInt(10.06)` `True` – Dannid Dec 12 '13 at 19:24
  • 5
    I guess it's "pythonic" in the sense that if Python thinks the string is an int, so does your program. If Python changes, so does your program, and without changing a single line of code. There's some value in that. It might be the right thing to do depending on the circumstances. – Shavais Oct 08 '14 at 16:07
  • 2
    @Aivar: **none** of the other answers are correct. None!!! They either fail to accept all values that `int()` accepts or they fail to reach their own definitions of an integer (e.g., to accept a string with a float that is also a whole number (`float(s).is_integer()` is true)). – jfs Oct 19 '14 at 07:05
  • 5
    @Dannid: the question is about input **strings**. You could use `.is_integer()` method in your case. – jfs Oct 19 '14 at 07:09
  • 1
    ...and notice that now that underscores are valid separators in number, the value of this solution should be even more clear! – Rmano Jan 18 '17 at 12:20
  • 100
    I don't know why this is the accepted answer or has so many upvotes, since this is exactly the opposite of what OP is asking for. – FearlessFuture Feb 15 '17 at 17:06
  • @Triptych: it may not be complex on the surface, but an if-else should not have to be replaced by a try-except to check something simple like if a string is an integer. Python is irritating the way it's decided to handle simple little things like this. Maybe its developers are also at fault for advocating the wrong solution. – craned Dec 06 '17 at 17:47
  • 14
    The question contains: "*Without using a try/except mechanism?*". That requirement is not met, as `try/except` is used (even if wrapped in a func). – CristiFati Jan 29 '18 at 17:39
  • @FearlessFuture Well obviously it worked for OP since they accepted it. – Rob Rose May 20 '18 at 21:02
  • 1
    @FearlessFuture It's because this is the correct solution and the OP's requirement is counterproductive. – Aran-Fey Jun 16 '18 at 19:31
  • 1
    @Aran-Fey Whether it's counter-productive or not can't be determined from OP. What if there are performance concerns? Some tests like Shavais' answer below or [this one](http://gerg.ca/blog/post/2015/try-except-speed/) indicate performance hits if exceptions are common, so in that case using try/except would be the counter-produtive choice. – kevlarr Aug 10 '18 at 02:15
  • 3
    @kevlarr If there are performance concerns, the question should be *"What's the fastest way to convert a string to an int"*. Asking how to do this without a `try` is just asking for bad code. – Aran-Fey Aug 10 '18 at 06:31
  • 1
    @Aran-Fey Agreed that performance concerns should not be implied here, I was just countering your absolute assertion that the ".. requirement is counterproductive". Asking if there is another way of checking the string without using try/except is a perfectly valid question - questions like this are *how people learn a language*. It's fine if the answer is "No, there is not a more Pythonic way" but in no way does that mean the question is "asking for bad code". – kevlarr Aug 10 '18 at 15:10
  • 2
    Fact: this method doesn't work for strings with underscores: `>>> int("2019_01_01")` returns `20190101` – Tavy Mar 18 '19 at 14:43
  • 3
    It really bothers me that this is the 'pythonic' way. This is bad software engineering by language design and certainly isn't simpler than just having an is_int function that I can call. By calling it 'pythonic' it's essentially the communities way of saying "It's a feature, not a bug!" – Ben Kushigian Aug 04 '19 at 00:21
  • 2
    To add another angle on this: testing whether a string is able to be converted to an integer is a common task when reading data from delimited text file sources. With many lines of data, it may be common to have both integers _and_ non-integers present, which would contra-indicate using exceptions to handle (due to performance concerns, especially across many fields and billions of lines). Since this answer doesn't address the OP's explicit request (which was _not_ FUD) and then was rabidly defended, it undermines legitimate cases where exceptions are in fact inappropriate. – bsplosion May 31 '20 at 14:42
  • 1
    @bsplosion The answer was accepted by OP, so probably does not contradict the intent behind their question. Note that I do preface with "if you're really just annoyed using try/except". – Kenan Banks Jun 03 '20 at 15:49
  • 1
    @Triptych Fair enough. As a user, it's just not great to come across a question asking for a different way to solve a problem which happens to align with your needs, and the selected answer basically says "do it the same way, but encapsulate". Encapsulation is the most common way to handle any coding annoyances, so it's not exactly novel. But they did select it, I suppose... – bsplosion Jun 04 '20 at 16:23
  • @bsplosion. I do understand your point from a UX perspective. Even under your case though, I would have written an answer strongly suggesting another approach. – Kenan Banks Jun 05 '20 at 19:15
  • This is wrong! The function results True even for string '1_1' since int('1_1') returns 11 – Hamid Heydarian Aug 19 '20 at 07:30
  • I've tested 3 most popular solutions and this one is neither fast nor short from all. See my answer here: https://stackoverflow.com/a/67500710/1823469 – Konstantin Smolyanin May 12 '21 at 12:56
  • 2
    @KonstantinSmolyanin The problem with every other solution is that either it would only be correct for a subset of strings / characters (e.g. ASCII) or there is no guarantee of correctness for any future versions of Python (to the best of my knowledge). This solution is correct for any possible strings and is guaranteed to remain correct as long as there aren't any major changes to the language. Of course there are plenty of use cases where you're already limited to a subset of strings, in which case some of those other solutions may be perfectly fine (or even better than this solution). – Bernhard Barker Sep 22 '21 at 11:57
  • @Tavy That's working as expected since int literals can contain underscores to separate digits. (And not just as thousands separators because other cultures group numbers differently, for example in India they have the crore, 1,00,00,000.) If you want to catch strings that look like dates, you should do that before passing the string into this function. – wjandrea Jan 29 '23 at 17:08
  • @Hamid See my comment above replying to Tavy – wjandrea Jan 29 '23 at 17:08
134

You know, I've found (and I've tested this over and over) that try/except does not perform all that well, for whatever reason. I frequently try several ways of doing things, and I don't think I've ever found a method that uses try/except to perform the best of those tested, in fact it seems to me those methods have usually come out close to the worst, if not the worst. Not in every case, but in many cases. I know a lot of people say it's the "Pythonic" way, but that's one area where I part ways with them. To me, it's neither very performant nor very elegant, so, I tend to only use it for error trapping and reporting.

I was going to gripe that PHP, perl, ruby, C, and even the freaking shell have simple functions for testing a string for integer-hood, but due diligence in verifying those assumptions tripped me up! Apparently this lack is a common sickness.

Here's a quick and dirty edit of Bruno's post:

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

Here are the performance comparison results:

timings..
isInt_try:   0.6426
isInt_str:   0.7382
isInt_re:    1.1156
isInt_re2:   0.5344
check_int:   0.3452

A C method could scan it Once Through, and be done. A C method that scans the string once through would be the Right Thing to do, I think.

EDIT:

I've updated the code above to work in Python 3.5, and to include the check_int function from the currently most voted up answer, and to use the current most popular regex that I can find for testing for integer-hood. This regex rejects strings like 'abc 123'. I've added 'abc 123' as a test value.

It is Very Interesting to me to note, at this point, that NONE of the functions tested, including the try method, the popular check_int function, and the most popular regex for testing for integer-hood, return the correct answers for all of the test values (well, depending on what you think the correct answers are; see the test results below).

The built-in int() function silently truncates the fractional part of a floating point number and returns the integer part before the decimal, unless the floating point number is first converted to a string.

The check_int() function returns false for values like 0.0 and 1.0 (which technically are integers) and returns true for values like '06'.

Here are the current (Python 3.5) test results:

              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   |

Just now I tried adding this function:

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

It performs almost as well as check_int (0.3486) and it returns true for values like 1.0 and 0.0 and +1.0 and 0. and .0 and so on. But it also returns true for '06', so. Pick your poison, I guess.

Tiago Martins Peres
  • 14,289
  • 18
  • 86
  • 145
Shavais
  • 2,476
  • 1
  • 27
  • 25
  • Perhaps part of it comes from the fact that an integer is a bit arbitrary itself. A programming system cannot take the luxury of assuming that it's always going to be a decimal representation. 0x4df, is a valid integer in some places, and 0891 is not in others. I dread to think what might arise given unicode in these kinds of checks. – PlexQ Mar 25 '12 at 17:03
  • True, the definition of a valid int is a bit dubious, and different in different contexts, and the presence of various string encodings complicates matter substantially - yet we have the built-in int() function. Maybe the built-in int() should take an optional flag bitmask that controls some things about how it behaves, and maybe there should be a built-in isInt() or is_int() that uses the same code and flags that int() does. – Shavais Mar 25 '12 at 20:40
  • 6
    +1 for the timing. I agree that this whole exception business is not really elegant for such a simple question. You'd expect a build in helper method for such a common problem... – RickyA Sep 28 '12 at 09:32
  • 12
    I know this thread is basically dormant, but +1 for considering run-time. Line length isn't always indicative of underlying complexity; and sure, a try/except might look simple (and read easy, which is important too), but it *is* a costly operation. I'd argue the preference hierarchy should always look something like the following: 1. An easy to read explicit solution (SilentGhost's). 2. An easy to read implicit solution (Triptych's). 3. There is no three. – Eric Humphrey Aug 11 '13 at 00:24
  • I concede that in most modern scenarios, performance is much less important than ease of creation and maintenance. In most situations, the user isn't going to be able to tell a difference anyway. But one of the things I appreciate most about Python is how generally applicable it is, and how much effort has been put into getting it to perform well. I think ceasing that effort would be a very, very bad idea. An optimized C isInt or is_int boolean function, that exposes the int-determination logic in the int function, would result in a well-performing, easy to read explicit Python solution. – Shavais Aug 12 '13 at 17:38
  • (..and while we wait for the language to be changed, we could create a temporary C-extension or add is_int() to an existing one.) – Shavais Aug 12 '13 at 19:07
  • what about '20e-1', '- .0'? – jfs Oct 19 '14 at 06:56
  • 1
    Thanks for your tourough investigations concerning such a seemingly insignificant topic. I'll go with the isInt_str(), pythonic or not. What's nagging me is that I haven't found anything about the meaning of v.find('..'). Is that some kind of special find-syntax or an edge-case of a numeric-string? – JackLeEmmerdeur Aug 10 '15 at 15:01
  • I think v.find('..') is an edge case; if the string contains two (or more) dots, it just lets isdigit() return false rather than continuing to process the other token strips. This answer is getting pretty dated. I wouldn't be surprised if the results were different, now, and/or if there were a better way to do it, now. – Shavais Aug 13 '15 at 00:50
  • 3
    Yes, a bit dated but still really nice and relevant analysis. In Python 3.5 `try` is more efficient: isInt_try: 0.6552 / isInt_str: 0.6396 / isInt_re: 1.0296 / isInt_re2: 0.5168. – Dave Feb 21 '16 at 23:57
  • Suppose you compile the regex once and reuse the compiled regex across a quantity of input, amortizing the cost. The regex would perform substantially better. And the string copy version would encounter substantial costs for alloc/dealloc. – ChuckCottrill Dec 12 '17 at 04:10
  • @ChuckCottrill: Isn't that what IsInt_re2 is doing? It's using a precompiled regex. At the time of this test (quite a few years ago, now), the string copy version was slightly faster, despite alloc/dealloc costs. Regex processing is pretty complicated and can be expensive depending on the pattern. – Shavais Dec 15 '17 at 18:44
  • @user202729: When the post was originally created (python 2.7?), I'm pretty sure the regex was returning correct answers for all the test values. At this point (python 3.5) for me, the regex that was there was returning false for 'abc 123' but was not returning the correct answer for several of the other test values. I've updated the answer as described in the EDIT portion of the answer. – Shavais Sep 05 '18 at 17:16
  • When considering an a method to use, one would also need to consider the data they'll be testing against. If you expect things like '123.1234' to be exceedingly rare, then the performance hit of the try/except is beyond negligible. Importantly, anyone concerned with performance of a function should test the function with a sample set of the type of data they most often expect to be checking. – Caboose Nov 26 '19 at 18:33
  • I've tested 3 most popular solutions and regex solution is the slowest. See my answer here: https://stackoverflow.com/a/67500710/1823469 – Konstantin Smolyanin May 12 '21 at 12:57
71

str.isdigit() should do the trick.

Examples:

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

EDIT: As @BuzzMoschetti pointed out, this way will fail for minus number (e.g, "-23"). In case your input_num can be less than 0, use re.sub(regex_search,regex_replace,contents) before applying str.isdigit(). For example:

import re
input_num = "-23"
input_num = re.sub("^-", "", input_num) ## "^" indicates to remove the first "-" only
str.isdigit(input_num) ## True
Chau Pham
  • 4,705
  • 1
  • 35
  • 30
  • 4
    Because -23 yields false. – Buzz Moschetti Jun 04 '19 at 15:11
  • 4
    @BuzzMoschetti you're right. A quick way to fix is removing minus sign by re.replace(regex_search,regex_replace,contents) before applying str.isdigit() – Chau Pham Jun 05 '19 at 16:05
  • 1
    I was looking exactly for this! Trying to parse positive integers from a CSV, and this is very perfect! – Jay Dadhania Oct 25 '20 at 02:51
  • what is the difference with `s.isnumeric()`? – Charlie Parker Nov 26 '21 at 18:13
  • 1
    Let me illustrate with several examples: For byte number, e.g., s = b'123', s.isdigit()=True but s.isnumeric() and s.isdecimal() throws AttributeError. For Chinese or Roman number, e.g., s = '三叁' or s = 'Ⅲ', s.isdigit()=False and s.isdecimal() =False, but s.isnumeric()=True. – K. Symbol Apr 07 '22 at 13:29
30

Use a regular expression:

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

If you must accept decimal fractions also:

def RepresentsInt(s):
    return re.match(r"[-+]?\d+(\.0*)?$", s) is not None

For improved performance if you're doing this often, compile the regular expression only once using re.compile().

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
20
>>> "+7".lstrip("-+").isdigit()
True
>>> "-7".lstrip("-+").isdigit()
True
>>> "7".lstrip("-+").isdigit()
True
>>> "13.4".lstrip("-+").isdigit()
False

So your function would be:

def is_int(val):
   return val.lstrip("-+").isdigit()
Martial P
  • 395
  • 1
  • 12
alkos333
  • 631
  • 6
  • 11
20

The proper RegEx solution would combine the ideas of Greg Hewgill and Nowell, but not use a global variable. You can accomplish this by attaching an attribute to the method. Also, I know that it is frowned upon to put imports in a method, but what I'm going for is a "lazy module" effect like http://peak.telecommunity.com/DevCenter/Importing#lazy-imports

edit: My favorite technique so far is to use exclusively methods of the String object.

#!/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))

And for the less adventurous members of the class, here is the output:

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
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
  • 4
    I'll agree that my test suite is overkill. I like to **prove** that my code works when I write it. But do you think my isInteger function is overkill? Surely not. – Bruno Bronosky Aug 25 '11 at 06:56
  • 2
    I just got a down vote with no comments. What is with people? I understand that millennials are now using "Likes" as "read receipts". But are they now using down votes as "not the method I chose" markers? Maybe they don't realize it subtracts 2 points from **YOUR OWN reputation** to down vote an answer. SO/SE does that to encourage down voting only due to misinformation, in which case I'd hope you'd **leave a comment**. – Bruno Bronosky Apr 16 '18 at 15:15
8

I do this all the time b/c I have a mild but admittedly irrational aversion to using the try/except pattern. I use this:

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

It doesn't accommodate negative numbers, so you could strip out all minus signs on the left side, and then check if the result comprises digits from 0-9:

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

You could also pass x to str() if you're not sure the input is a string:

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

There are some (edge?) cases where this falls apart:

  1. It doesn't work for various scientific and/or exponential notations (e.g. 1.2E3, 10^3, etc.) - both will return False. I don't think other answers accommodated this either, and even Python 3.8 has inconsistent opinions, since type(1E2) gives <class 'float'> whereas type(10^2) gives <class 'int'>.
  2. An empty string input gives True.
  3. A leading plus sign (e.g. "+7") gives False.
  4. Multiple minus signs are ignored so long as they're leading characters. This behavior is similar to the python interpreter* in that type(---1) returns <class int>. However, it isn't completely consistent with the interpreter in that int('---1') gives an error, but my solution returns True with the same input.

So it won't work for every possible input, but if you can exclude those, it's an OK one-line check that returns False if x is not an integer and True if x is an integer. But if you really want behavior that exactly models the int() built-in, you're better off using try/except.

I don't know if it's pythonic, but it's one line, and it's relatively clear what the code does.

*I don't mean to say that the interpreter ignores leading minus signs, just that any number of leading minus signs does not change that the result is an integer. int(--1) is actually interpreted as -(-1), or 1. int(---1) is interpreted as -(-(-1)), or -1. So an even number of leading minus signs gives a positive integer, and an odd number of minus signs gives a negative integer, but the result is always an integer.

mRotten
  • 447
  • 1
  • 4
  • 11
  • This returns `True` for `'12-34'`. – AMC Oct 28 '20 at 23:23
  • @AMC Yeah, good point, I think that's worth excluding. I edited my answer, which introduced another caveat that I think is acceptable. However, this demonstrates that it's a subjective problem, b/c we have to assume what strings are and are not acceptable integer representations. We also don't know what assumptions we can make about inputs. Both `.replace()` and `.lstrip()` implementations are adequate for the OP's examples. – mRotten Oct 29 '20 at 22:39
6

The easiest way, which I use

def is_int(item: str) -> bool:
    return item.lstrip('-+').isdigit()
Grey2k
  • 464
  • 1
  • 7
  • 12
5

Greg Hewgill's approach was missing a few components: the leading "^" to only match the start of the string, and compiling the re beforehand. But this approach will allow you to avoid a try: exept:

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

I would be interested why you are trying to avoid try: except?

Nowell
  • 304
  • 1
  • 5
  • 3
    A matter of style. I think that "try/except" should be used only with actual errors, not with normal program flow. – Adam Matan Aug 12 '09 at 12:39
  • 2
    @Udi Pasmon: Python makes fairly heavy use of try/except for "normal" program flow. For example, every iterator stops with a raised exception. – S.Lott Aug 12 '09 at 13:35
  • 3
    -1 : Although your hint at compiling the regex is right, you're wrong in critizising Greg in the other respect: re.match matches against the **start** of the string, so the ^ in the pattern is actually redundant. (This is different when you use re.search). – ThomasH Aug 12 '09 at 13:40
  • S.Lott - Is this considered reasonable flow in python? How does this differs from other languages? Perhaps it's worth a separate question. – Adam Matan Aug 12 '09 at 14:02
  • 1
    Python's heavy use of try/except has been covered here on SO. Try a search for '[python] except' – S.Lott Aug 12 '09 at 15:01
  • @ThomasH I didn't realize that `re.match` implied a `^` at the beginning. (Not very "explicit" is it?) I don't recall it, but I'm sure that has caused me frustration in the past. For that reason, I am glad this exchange happened. – Bruno Bronosky Apr 16 '18 at 15:20
5

I think

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

would be better to rewrite to:

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

because s[1:] also creates a new string

But much better solution is

s.lstrip('+-').isdigit()
Vladyslav Savchenko
  • 1,282
  • 13
  • 10
5

Can use the below method to check.

def check_if_string_is_int(string1):
    for character in string1:
        if not character.isdigit():
            return "Not a number"
    else:
        return "Is a number"
SuperNova
  • 25,512
  • 7
  • 93
  • 64
  • +1 for the simplicity. Note however that you should remove the `else:` statement. Just write `return "Is a number"` directly where `else` currently stands. – Ronald Souza Aug 17 '22 at 16:32
  • Also, note that blank spaces cause it to fail (e.g. `44` returns True but `44 ` returns False). Adding `string1 = string1.strip()` at the beginning solves it. – Ronald Souza Aug 17 '22 at 16:54
2

I really liked Shavais' post, but I added one more test case ( & the built in isdigit() function):

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

and it significantly consistently beats the times of the rest:

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

using normal 2.7 python:

$ python --version
Python 2.7.10

Both the two test cases I added (isInt_loop and isInt_digit) pass the exact same test cases (they both only accept unsigned integers), but I thought that people could be more clever with modifying the string implementation (isInt_loop) opposed to the built in isdigit() function, so I included it, even though there's a slight difference in execution time. (and both methods beat everything else by a lot, but don't handle the extra stuff: "./+/-" )

Also, I did find it interesting to note that the regex (isInt_re2 method) beat the string comparison in the same test that was performed by Shavais in 2012 (currently 2018). Maybe the regex libraries have been improved?

brw59
  • 502
  • 4
  • 19
2

Preconditions:

  • we are talking about integers (not decimals/floats);
  • behavior of built-in int() is a standard for us (sometimes it's strange: "-00" is correct input for it)

Short answer:

Use the following code. It is simple, correct (while many variants in this thread aren't) and nearly twice outperforms both try/except and regex variants.

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

TL;DR answer:

I've tested 3 main variants (1) try/except, (2) re.match() and (3) string operations (see above). The third variant is about twice faster then both try/except and re.match(). BTW: regex variant is the slowest! See test script below.

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

Output was:

is_int_str_1: 4.314162969589233 seconds
is_int_str_2: 5.7216269969940186 seconds
is_int_str_3: 2.5828163623809814 seconds
Konstantin Smolyanin
  • 17,579
  • 12
  • 56
  • 56
  • I think your regex variant is the slowest because you didn't precompile the regex pattern first. When I precompile the regex, it becomes the 2nd fastest. On Python 3.7: `9.66 | 7.03 | 4.86` , on Python 3.8: `7.78 | 5.56 | 4.57` – pepoluan Jan 05 '22 at 02:24
1

This is probably the most straightforward and pythonic way to approach it in my opinion. I didn't see this solution and it's basically the same as the regex one, but without the regex.

def is_int(test):
    import string
    return not (set(test) - set(string.digits))
rmtheis
  • 5,992
  • 12
  • 61
  • 78
Xenlyte
  • 19
  • 2
  • `set(input_string) == set(string.digits)` if we skip `'-+ '` at the begining and `.0`, `E-1` at the end. – jfs Oct 19 '14 at 06:59
1

I have one possibility that doesn't use int at all, and should not raise an exception unless the string does not represent a number

float(number)==float(number)//1

It should work for any kind of string that float accepts, positive, negative, engineering notation...

agomcas
  • 695
  • 5
  • 12
1

Here is a function that parses without raising errors. It handles obvious cases returns None on failure (handles up to 2000 '-/+' signs by default on 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)

Some tests:

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

Results:

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

For your needs you can use:

def int_predicate(number):
     return get_int(number) is not None
Reut Sharabani
  • 30,449
  • 6
  • 70
  • 88
1

If you want to accept lower-ascii digits only, here are tests to do so:

Python 3.7+: (u.isdecimal() and u.isascii())

Python <= 3.6: (u.isdecimal() and u == str(int(u)))

Other answers suggest using .isdigit() or .isdecimal() but these both include some upper-unicode characters such as '٢' (u'\u0662'):

u = u'\u0662'     # '٢'
u.isdigit()       # True
u.isdecimal()     # True
u.isascii()       # False (Python 3.7+ only)
u == str(int(u))  # False
krubo
  • 5,969
  • 4
  • 37
  • 46
0

I suggest the following:

import ast

def is_int(s):
    return isinstance(ast.literal_eval(s), int)

From the docs:

Safely evaluate an expression node or a string containing a Python literal or container display. The string or node provided may only consist of the following Python literal structures: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None.

I should note that this will raise a ValueError exception when called against anything that does not constitute a Python literal. Since the question asked for a solution without try/except, I have a Kobayashi-Maru type solution for that:

from ast import literal_eval
from contextlib import suppress

def is_int(s):
    with suppress(ValueError):
        return isinstance(literal_eval(s), int)
    return False

¯\_(ツ)_/¯

Jesko Hüttenhain
  • 1,278
  • 10
  • 28
  • 1
    Using your "Kobayashi-Maru" code with python 3.8 I still get an exception for input like `'123abc'` - `SyntaxError: unexpected EOF while parsing` – user9645 Oct 02 '20 at 16:10
0

Cast value to string after checking is integer, then check string first character value is - or + and rest of string isdigit. Finally just check isdigit.

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

Check

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

Result

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
Hmerman6006
  • 1,622
  • 1
  • 20
  • 45
0

As I understand you want to check string convertability in int. To do that you can:

  1. Replace '-' to nothing, cos '-' not digit and '-7' can also be converted in int.
  2. Check is it digit.
def is_string_convertable_to_int(value: str) -> bool:
    return value.replace('-', '').isdigit()

P.S. You can easy modify this def for checking string convertability in float, just add replace('.', '') and check one '.' existance using value.count('.') = 1.

-1

I guess the question is related with speed since the try/except has a time penalty:

 test data

First, I created a list of 200 strings, 100 failing strings and 100 numeric strings.

from random import shuffle
numbers = [u'+1'] * 100
nonumbers = [u'1abc'] * 100
testlist = numbers + nonumbers
shuffle(testlist)
testlist = np.array(testlist)

 numpy solution (only works with arrays and unicode)

np.core.defchararray.isnumeric can also work with unicode strings np.core.defchararray.isnumeric(u'+12') but it returns and array. So, it's a good solution if you have to do thousands of conversions and have missing data or non numeric data.

import numpy as np
%timeit np.core.defchararray.isnumeric(testlist)
10000 loops, best of 3: 27.9 µs per loop # 200 numbers per loop

try/except

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

Seems that numpy solution is much faster.

Carlos Vega
  • 1,341
  • 2
  • 13
  • 35
-4

Uh.. Try this:

def int_check(a):
    if int(a) == a:
        return True
    else:
        return False

This works if you don't put a string that's not a number.

And also (I forgot to put the number check part. ), there is a function checking if the string is a number or not. It is str.isdigit(). Here's an example:

a = 2
a.isdigit()

If you call a.isdigit(), it will return True.

HaulCozen
  • 33
  • 4