Converting Strings to Integers in Python

Converting a string to an integer is one of the operations you will perform most often in Python — and it is also one of the easiest to get subtly wrong. This guide covers every aspect of the conversion: the mechanics of int(), base conversions, validation strategies, error handling, the security-driven digit limit introduced in Python 3.11, and the cases where a safer alternative like ast.literal_eval() is worth reaching for.

Python is a strongly typed language. That means you cannot treat a string and an integer as interchangeable, even when the string contains nothing but digits. The value "42" and the value 42 are fundamentally different objects: one is a sequence of characters, the other is a numeric type that supports arithmetic. Conversion bridges that gap, and understanding exactly what happens during conversion will save you from bugs that can be surprisingly hard to trace.

Why Strings Hold Numbers

Before getting into the mechanics, it is worth asking: where do numeric strings come from? The answer reveals why conversion is such a routine task.

User input is always a string. When you call input(), Python returns whatever the user typed as a str, regardless of content. Reading a CSV, a config file, an environment variable, or a command-line argument produces the same result — raw text. HTTP request parameters, JSON parsed from an API response (when the field was serialized without type information), and database drivers that return untyped text columns all hand you strings. If you want to do arithmetic on any of those values, you have to convert them first.

This is not a quirk of Python; it reflects how computers represent external input. The conversion step is deliberate: it forces you to acknowledge that parsing can fail and decide what to do when it does.

The int() Function in Detail

The primary tool for this job is the built-in int() function. According to the official Python documentation, int() is defined with the signature:

int(x=0)
int(x, base=10)

When called with a string as its first argument, int() parses the string and returns an integer object. The default base is 10, which means the string is treated as a decimal number unless you specify otherwise.

# Basic conversion
age_str = "29"
age = int(age_str)
print(age)         # 29
print(type(age))  # <class 'int'>

The return type is always a Python int. Unlike languages with fixed-width integer types (such as C's int32_t or Java's int), Python integers can be arbitrarily large. The official documentation for the int type describes it as having unlimited precision, bounded only by available memory. This means there is no overflow to worry about for large numeric strings — with one important caveat covered in the security section below.

"If x is not a number and base is not given, then x must be a string, bytes, or bytearray instance representing an integer literal in radix base." — Python 3.13 documentation, docs.python.org

That quote highlights something easy to miss: int() also accepts bytes and bytearray objects, not just strings. The parsing rules are the same.

What int() Accepts

When parsing a string in base 10, int() accepts the following forms:

  • A plain sequence of decimal digits: "42", "1000"
  • A leading sign: "-7", "+3"
  • Leading and trailing whitespace: " 42 " is valid
  • Underscore separators between digits, since Python 3.6: "1_000_000"
int("  -42  ")    # Returns -42  (whitespace is stripped)
int("+99")       # Returns 99   (leading + is accepted)
int("1_000")     # Returns 1000 (underscore grouping, Python 3.6+)
Note

The underscore grouping behavior was added in Python 3.6 to mirror the way numeric literals can be written in code. int("1_000_000") returns 1000000. The Python changelog documents this addition under the 3.6 release notes for built-in functions.

Base Conversions: Binary, Octal, Hex

The second parameter of int() is base. It accepts any integer between 2 and 36 inclusive, or the special value 0. When you supply a base, you are telling Python how to interpret the digits in the string.

# Binary (base 2)
int("1010", 2)   # Returns 10

# Octal (base 8)
int("17", 8)     # Returns 15

# Hexadecimal (base 16)
int("1F", 16)    # Returns 31
int("ff", 16)    # Returns 255 (case-insensitive)
int("0xFF", 16)  # Returns 255 (0x prefix accepted)

# Base 36 (digits 0-9 and letters a-z)
int("z", 36)     # Returns 35

The output is always a Python integer in base 10, regardless of the input base. The conversion works by assigning each character a numeric value within its base and computing the positional sum. There is no "hex integer" or "binary integer" type in Python — there is only int, and the base you supply tells the parser how to read the input string.

Pro Tip

Passing base=0 is a special case: it tells Python to infer the base from the string's prefix. A string starting with 0b or 0B is treated as binary, 0o or 0O as octal, 0x or 0X as hexadecimal, and a plain string of decimal digits as base 10. This mirrors how Python parses integer literals in source code.

int("0b1010", 0)  # Returns 10  (binary inferred from 0b prefix)
int("0o17", 0)    # Returns 15  (octal inferred from 0o prefix)
int("0xff", 0)    # Returns 255 (hex inferred from 0x prefix)
int("42", 0)      # Returns 42  (decimal, no prefix)

Note that passing base=0 means a plain string like "042" (without a prefix) will raise a ValueError. In Python 2, a string starting with zero was interpreted as octal, but Python 3 eliminates that ambiguity entirely. Without the 0o prefix, the intent is unclear and Python refuses to guess.

Error Handling and Input Validation

When int() cannot parse the string you give it, it raises a ValueError. This is the correct exception type: the value is of the right type (a string), but its content is not a valid integer representation.

int("hello")   # ValueError: invalid literal for int() with base 10: 'hello'
int("3.14")    # ValueError: invalid literal for int() with base 10: '3.14'
int("")        # ValueError: invalid literal for int() with base 10: ''
int("1 2")     # ValueError: spaces within the digit sequence are not allowed

Notice the third example: an empty string raises ValueError. This catches many developers off guard when reading from sources that may produce empty fields.

Also worth noting: "3.14" fails even though the string represents a valid number. int() does not perform float parsing. If you have a string like "3.14" and you want the integer 3, you need to convert to float first:

result = int(float("3.14"))  # Returns 3 (truncates toward zero)

Using try/except for Robust Conversion

The standard pattern for safe conversion is a try/except block targeting ValueError:

def safe_to_int(value, default=None):
    try:
        return int(value)
    except (ValueError, TypeError):
        return default

safe_to_int("42")        # Returns 42
safe_to_int("hello")     # Returns None
safe_to_int(None)         # Returns None (TypeError caught)
safe_to_int("bad", 0)    # Returns 0 (custom default)

The TypeError catch in the function above is intentional. If you pass a non-string type that int() cannot handle — like None, a list, or a dict — Python raises TypeError, not ValueError. A utility function meant to handle untrusted input should catch both.

Pre-validation with str.isdigit() and str.isnumeric()

You can also check whether a string is convertible before calling int(). The string methods isdigit() and isnumeric() are commonly used for this, but they have a limitation worth understanding.

"42".isdigit()     # True
"-42".isdigit()    # False — the minus sign is not a digit
"42.0".isdigit()  # False — the dot is not a digit
"".isdigit()      # False

isdigit() returns True only when every character in the string is a Unicode digit character. It does not account for a leading sign. For input that may include a sign, you need a slightly more careful check:

def is_valid_int_string(s):
    s = s.strip()
    if s.startswith(('+', '-')):
        s = s[1:]
    return bool(s) and s.isdigit()

is_valid_int_string("-42")    # True
is_valid_int_string("+7")     # True
is_valid_int_string("3.14")  # False
is_valid_int_string("")       # False
Watch Out

isnumeric() accepts a wider range of Unicode characters than isdigit(), including numeric characters from other writing systems and fractions like ½. These characters are technically numeric in Unicode but are not valid inputs for int() in base 10. Rely on isdigit() or a try/except approach rather than isnumeric() for pre-validation of integer strings.

Edge Cases Worth Knowing

Leading Zeros

In base 10, a string with leading zeros converts just fine — the zeros are discarded:

int("007")   # Returns 7
int("0042")  # Returns 42

However, using base=0 with a zero-padded decimal string raises ValueError, because Python 3 eliminates the Python 2 octal ambiguity. If you are using base=0, strings representing decimal numbers must have no leading zeros (unless the number is just "0").

Whitespace Handling

Leading and trailing whitespace is silently stripped before parsing. This is intentional and documented behavior. However, whitespace embedded within the digit sequence causes a ValueError:

int("  42  ")  # Returns 42  (outer whitespace stripped)
int("4 2")     # ValueError  (internal space)

Negative Numbers

A single leading minus sign is supported. Multiple signs or a minus inside the digit sequence are not:

int("-100")   # Returns -100
int("--100")  # ValueError
int("1-00")   # ValueError

Large Numbers

Python's arbitrary-precision integers mean there is no numeric overflow, but there is a conversion length limit. Outside of that limit (discussed in the next section), int() handles very large numbers correctly:

big = int("9" * 50)  # 50-digit integer, no problem
print(big)
# 99999999999999999999999999999999999999999999999999

The Python 3.11 Security Limit

This section covers something that few tutorials mention: a security-motivated change to int() that was introduced across multiple Python versions in September 2022.

Converting a very long string to an integer has quadratic time complexity — O(n²) — because of how Python's internal integer representation works. An attacker who can control a string being passed to int() could construct an extremely long numeric string and trigger a denial-of-service condition by consuming excessive CPU time. This vulnerability was tracked as CVE-2020-10735.

"int string inputs and string representations can be limited to help avoid denial of service attacks. A ValueError is raised when the limit is exceeded while converting a string to an int or when converting an int into a string would exceed the limit." — Python 3.13 Built-in Functions documentation

Starting with the versions released on September 7, 2022 — Python 3.11, 3.10.7, 3.9.14, 3.8.14, and 3.7.14 — Python enforces a default limit of 4300 digits for integer-string conversions in base 10. Attempting to convert a string longer than 4300 digits raises a ValueError.

# This will raise ValueError in Python 3.11+ (default limit is 4300 digits)
huge_str = "9" * 5000
int(huge_str)
# ValueError: Exceeds the limit (4300 digits) for integer string conversion

If you legitimately need to work with integers larger than 4300 digits — for example, in cryptographic research — you can raise or disable the limit using sys.set_int_max_str_digits():

import sys

sys.set_int_max_str_digits(10000)  # Raise to 10,000 digits
sys.set_int_max_str_digits(0)      # 0 disables the limit entirely

The limit can also be set via the environment variable PYTHONINTMAXSTRDIGITS or the command-line option -X int_max_str_digits=N. Notably, the limit applies only to base-10 conversions. Bases that are powers of 2 — binary, octal, and hexadecimal — are exempt from this restriction because their conversion algorithms are linear-time.

Note

The 4300-digit default is not arbitrary. It corresponds to integers just under 214300, chosen to be large enough for typical use while still providing meaningful protection. The Python security team documented the rationale in the CPython issue tracker under issue #95778.

ast.literal_eval() as a Safer Alternative

Python's ast module provides literal_eval(), which safely evaluates an expression string containing only Python literals: strings, bytes, numbers, tuples, lists, dicts, sets, booleans, and None.

import ast

ast.literal_eval("42")     # Returns 42 (int)
ast.literal_eval("3.14")   # Returns 3.14 (float)
ast.literal_eval("'hi'")   # Returns 'hi' (str)

When you pass ast.literal_eval() a numeric string like "42", it parses it as a Python integer literal and returns an int. Unlike eval(), literal_eval() refuses to execute arbitrary code. Passing "__import__('os').system('rm -rf /')" to literal_eval() raises a ValueError rather than executing the command.

However, there are two reasons int() remains the right choice for converting numeric strings in the general case. First, int() is significantly faster — benchmarks consistently show int() completing in a fraction of the time required by literal_eval(), because literal_eval() invokes the full parser and AST machinery. Second, int() gives you explicit control over the base, which literal_eval() does not expose in the same way.

Where ast.literal_eval() earns its place is in situations where the string might represent multiple possible types and you want to recover the correct one without eval(), or when the source of the string is particularly untrusted and you want the strongest possible barrier against code injection.

Practical Patterns

Converting User Input

The single most common use case. Always wrap input() conversion in error handling:

while True:
    raw = input("Enter a whole number: ")
    try:
        number = int(raw)
        break
    except ValueError:
        print(f"'{raw}' is not a valid integer. Try again.")

Converting a List of Strings

When you have a list of numeric strings — from a CSV row, for example — a list comprehension converts all of them at once:

raw_values = ["10", "20", "30", "40"]
values = [int(v) for v in raw_values]
# [10, 20, 30, 40]

If any element might be invalid, combine the comprehension with a helper function or a conditional expression:

raw_values = ["10", "oops", "30"]
values = [int(v) for v in raw_values if v.isdigit()]
# [10, 30]  — "oops" is silently skipped

Parsing Hex Color Codes

A practical application of base-16 conversion: extracting the red, green, and blue components from a CSS hex color string:

def hex_to_rgb(hex_color):
    # Strip the leading # if present
    hex_color = hex_color.lstrip("#")
    r = int(hex_color[0:2], 16)
    g = int(hex_color[2:4], 16)
    b = int(hex_color[4:6], 16)
    return (r, g, b)

hex_to_rgb("#306998")  # Returns (48, 105, 152)
hex_to_rgb("#FFD43B")  # Returns (255, 212, 59)

Reading Environment Variables

Environment variables are always strings. Converting them to integers with a safe fallback is a pattern used in virtually every production Python application:

import os

port = int(os.environ.get("PORT", "8080"))
max_retries = int(os.environ.get("MAX_RETRIES", "3"))

Note that os.environ.get() returns a string (or the default string you supply), so the outer int() call is always converting a string. If the environment variable is set to a non-integer value, this will raise ValueError — which is often the correct behavior, since a misconfigured environment variable should cause an early and visible failure.

Chained Conversion: Float String to Int

If your input might be a float-formatted string like "3.99" and you want the integer, the two-step conversion is straightforward:

price_str = "19.99"
price_cents = int(float(price_str) * 100)  # 1999

Be aware that floating-point representation can introduce small rounding errors in certain values. For financial calculations, the decimal module is more appropriate than chaining float() and int().

Key Takeaways

  1. int() is the correct tool: For converting a numeric string to an integer in Python, int() is the built-in function designed for exactly this purpose. It handles decimal, signed, whitespace-padded, and underscore-grouped strings out of the box.
  2. Always catch ValueError: Any code path that calls int() on externally supplied data should handle ValueError. An unexpected string format will raise it, and an unhandled exception will crash your program. If the input can also be None or a non-string type, catch TypeError as well.
  3. The base parameter extends int() to other number systems: Binary, octal, and hexadecimal strings are all supported. Passing base=0 lets Python infer the base from the string's prefix, mirroring Python's literal syntax.
  4. Know the 4300-digit security limit: Python 3.11 and the corresponding patch releases of 3.10, 3.9, 3.8, and 3.7 enforce a default limit of 4300 digits on base-10 string-to-integer conversions to prevent CVE-2020-10735. This limit is configurable via sys.set_int_max_str_digits().
  5. isdigit() has a sign limitation: The str.isdigit() method returns False for strings with a leading minus sign. It is a useful pre-check for unsigned integer strings, but not a complete substitute for a try/except block when signed input is possible.
  6. ast.literal_eval() is safer in adversarial contexts: When you need to prevent code injection and the input could represent multiple Python literal types, ast.literal_eval() is the right choice. For performance-sensitive or straightforward numeric parsing, prefer int().

Understanding these mechanics is not just theoretical. It directly affects the reliability of anything that touches user input, configuration files, external APIs, or data pipelines. Python's design philosophy holds that explicit is better than implicit — and that principle shows up here in the fact that type conversion is never done for you. You call int(), you decide what to do when it fails, and your code is the better for it.

References

  1. Python Software Foundation. Built-in Functions — Python 3.13 documentation. docs.python.org/3/library/functions.html
  2. Python Software Foundation. Integer string conversion length limitation — Python 3.13 documentation. docs.python.org/3/library/stdtypes.html
  3. Python Software Foundation. ast.literal_eval — ast module. docs.python.org/3/library/ast.html
  4. MITRE Corporation. CVE-2020-10735: Prevent DoS by large int<->str conversions. cve.mitre.org
  5. CPython Issue Tracker. Issue #95778: int string conversion O(n^2). github.com/python/cpython/issues/95778
  6. Python Software Foundation. What's New in Python 3.11. docs.python.org/3/whatsnew/3.11.html
back to articles