Python Check if a Variable Is an Integer (and the bool Trap)

Python has no isint() function. To check if a variable is an integer, use isinstance:

isinstance(value, int)        # True for 42, False for 3.14 and "42"

There’s one catch that bites almost everyone: isinstance(True, int) is also True, because bool is a subclass of int.

If booleans should not count, the check becomes isinstance(value, int) and not isinstance(value, bool).

Every result on this page was produced by running the code on Python 3.12.5, NumPy 2.5.3.

Python has no isint() function

It’s worth repeating, because a lot of people go looking for it: there is no isint(), no isInt and no is_int.

isinstance is the replacement, and you can wrap it in your own isint if the name helps:

value = 42

print(isinstance(value, int))        # True
print(isinstance(3.14, int))         # False
print(isinstance("42", int))         # False, it is a string

# there is no isint() built in, so this is what people mean by it
def isint(x):
    return isinstance(x, int) and not isinstance(x, bool)

for item in (42, 3.14, "42", True, -7):
    print(f"{str(item):<6} -> {isint(item)}")

Output:

True
False
False
42     -> True
3.14   -> False
42     -> False
True   -> False
-7     -> True
Command Prompt showing isinstance used to check whether Python variables are integers
A four-line isint that behaves the way people expect.

The string "42" returns False, which is correct. It holds digits, but its type is str.

Checking the contents of a string is a different job, covered in checking whether a string is an integer or a float.

Why isinstance(True, int) returns True

Booleans are integers in Python. Not similar to integers, literally a subclass of them:

# bool is a SUBCLASS of int, so booleans pass an integer check
print("isinstance(True, int) :", isinstance(True, int), "  <- surprising")
print("type(True) is int     :", type(True) is int, "  <- and this disagrees")

print("\nbool inherits from:", [cls.__name__ for cls in bool.__mro__])
print("True + True =", True + True)
print("True * 10   =", True * 10)

print("\nexcluding booleans explicitly:")
for item in (42, True, False, 0):
    strict = isinstance(item, int) and not isinstance(item, bool)
    print(f"  {str(item):<6} isinstance={isinstance(item, int)!s:<6} strict={strict}")

Output:

isinstance(True, int) : True   <- surprising
type(True) is int     : False   <- and this disagrees

bool inherits from: ['bool', 'int', 'object']
True + True = 2
True * 10   = 10

excluding booleans explicitly:
  42     isinstance=True   strict=True
  True   isinstance=True   strict=False
  False  isinstance=True   strict=False
  0      isinstance=True   strict=True
Command Prompt showing that isinstance True int returns True while type True is int returns False
bool inherits from int, so True passes an integer check.

That is why True + True evaluates to 2. Under the hood True is 1 and False is 0.

It matters in real code. A function that validates a quantity with isinstance(qty, int) will happily accept True and then treat it as 1.

  • isinstance(value, int) — accepts booleans, usually not what you want.
  • isinstance(value, int) and not isinstance(value, bool) — rejects them.
  • type(value) is int — also rejects them, but rejects int subclasses too.

isinstance vs type() for integer checks

isinstance accepts subclasses. type() is demands an exact match:

class Count(int):            # a subclass of int
    pass

n = Count(5)

print("isinstance(n, int) :", isinstance(n, int), " <- accepts subclasses")
print("type(n) is int     :", type(n) is int, " <- exact type only")

print()
print("type(n) :", type(n).__name__)
print("n + 10  :", n + 10, "(it behaves exactly like an int)")

Output:

isinstance(n, int) : True  <- accepts subclasses
type(n) is int     : False  <- exact type only

type(n) : Count
n + 10  : 15 (it behaves exactly like an int)
CheckPlain intTrueint subclass
isinstance(x, int)YesYesYes
type(x) is intYesNoNo
isinstance(x, int) and not isinstance(x, bool)YesNoYes

Prefer isinstance as a rule. Subclasses of int behave like integers, so rejecting them is usually a bug rather than a feature.

The exception is exactly the boolean case, which is why the third row exists.

Checking if a variable is a number, not just an integer

When floats should pass too, name both types or reach for the numbers module:

import numbers

# int OR float OR anything else numeric
for value in (42, 3.14, True, complex(1, 2), "42", None):
    is_number = isinstance(value, numbers.Number)
    is_integral = isinstance(value, numbers.Integral)
    print(f"{str(value):<8} Number={is_number!s:<6} Integral={is_integral}")

print()
# the plain-tuple version, which is enough most of the time
print("isinstance(3.14, (int, float)) :", isinstance(3.14, (int, float)))
print("isinstance(42, (int, float))   :", isinstance(42, (int, float)))

Output:

42       Number=True   Integral=True
3.14     Number=True   Integral=False
True     Number=True   Integral=True
(1+2j)   Number=True   Integral=False
42       Number=False  Integral=False
None     Number=False  Integral=False

isinstance(3.14, (int, float)) : True
isinstance(42, (int, float))   : True

numbers.Number is the widest check. It accepts complex numbers as well, which is rarely what a form validator wants.

numbers.Integral means whole numbers specifically, and isinstance(x, (int, float)) covers the everyday case with no import.

Why isinstance fails on NumPy integers

NumPy has its own integer types, and they are not Python ints:

import numbers
import numpy as np

n = np.int64(5)
print("type        :", type(n))
print("value       :", n, "and it does arithmetic fine:", n + 1)

print()
print("isinstance(n, int)             :", isinstance(n, int), " <- NOT a Python int")
print("isinstance(n, np.integer)      :", isinstance(n, np.integer))
print("isinstance(n, numbers.Integral):", isinstance(n, numbers.Integral), " <- the portable check")

Output:

type        : <class 'numpy.int64'>
value       : 5 and it does arithmetic fine: 6

isinstance(n, int)             : False  <- NOT a Python int
isinstance(n, np.integer)      : True
isinstance(n, numbers.Integral): True  <- the portable check
Command Prompt showing isinstance returning False for a NumPy int64 value while numbers Integral returns True
np.int64 does integer arithmetic but fails isinstance(x, int).

This is a common source of silent bugs. A value pulled out of a NumPy array or a pandas column fails your integer check even though it is obviously an integer.

numbers.Integral is the portable answer, since it covers both. See NumPy data types for why these separate types exist.

Checking if a float holds a whole number

5.0 is a float, but it may still be the whole number you’re after:

# a float that happens to hold a whole number
print("(5.0).is_integer() :", (5.0).is_integer())
print("(5.5).is_integer() :", (5.5).is_integer())

print()
def holds_whole_number(value):
    """True for 5 and 5.0, False for 5.5 and 'five'."""
    if isinstance(value, bool):
        return False
    if isinstance(value, int):
        return True
    if isinstance(value, float):
        return value.is_integer()
    return False


for item in (5, 5.0, 5.5, True, "5"):
    print(f"  {str(item):<6} -> {holds_whole_number(item)}")

Output:

(5.0).is_integer() : True
(5.5).is_integer() : False

  5      -> True
  5.0    -> True
  5.5    -> False
  True   -> False
  5      -> False

is_integer() is a method on float, so call it only after confirming the type. On an int it does not exist in older versions.

The helper above handles all four cases in the order that matters, checking bool first so it can be rejected before the int branch catches it.

Checking if user input is an integer

input() always returns a string, so an isinstance check on it is always False:

# input() always returns a string, so type checks on it are meaningless
values = ["42", "-5", "3.14", " 7 ", "abc"]

print(f"{'text':<8} {'isdigit':>8} {'int() works':>13}")
for text in values:
    try:
        int(text)
        parses = True
    except ValueError:
        parses = False
    print(f"{text!r:<8} {text.isdigit()!s:>8} {parses!s:>13}")

print()
print("note ' 7 ': isdigit() is False but int() accepts it, because int() strips whitespace")

Output:

text      isdigit   int() works
'42'         True          True
'-5'        False          True
'3.14'      False         False
' 7 '       False          True
'abc'       False         False

note ' 7 ': isdigit() is False but int() accepts it, because int() strips whitespace
Command Prompt table comparing isdigit and int conversion on several Python strings
' 7 ' fails isdigit() but converts fine.

int() inside a try is the honest test, because it accepts exactly what Python accepts, including a leading minus and surrounding whitespace.

isdigit() is stricter and rejects negatives. The differences are laid out in the isdigit guide.

Which integer check should you use?

SituationUse
Plain integer checkisinstance(x, int)
Booleans must not passisinstance(x, int) and not isinstance(x, bool)
Integer or floatisinstance(x, (int, float))
NumPy or pandas valuesisinstance(x, numbers.Integral)
A float holding a whole numberx.is_integer()
Text from input()int(text) in a try

Related type-checking guides on Python Guides:

Frequently asked questions

How do I check if a variable is an integer in Python?

Use isinstance(value, int). The function is documented in the Python built-in functions reference.

Is there an isint() function in Python?

No. Python has no isint(), isInt or is_int. Use isinstance(value, int), or define your own one-line helper with that name.

Why does isinstance(True, int) return True?

bool is a subclass of int in Python, so every boolean is also an integer. That is also why True + True equals 2.

How do I check for an integer but exclude booleans?

isinstance(value, int) and not isinstance(value, bool), or type(value) is int if you also want to reject int subclasses.

What is the difference between isinstance and type()?

isinstance accepts subclasses, type(x) is int demands the exact type. isinstance is usually correct, except when you need to exclude booleans.

Why does isinstance fail on a NumPy integer?

np.int64 is not a Python int. Use isinstance(x, numbers.Integral), which accepts both.

How do I check if a string contains an integer?

Call int(text) inside a try block. isdigit() also works but rejects negative numbers.