How to check if a string is a number in Python

Learn how to check if a string is a number in Python. Discover various methods, tips, real-world uses, and how to debug common errors.

How to check if a string is a number in Python
Published on: 
Tue
Mar 3, 2026
Updated on: 
Wed
Mar 4, 2026
The Replit Team Logo Image
The Replit Team

You often need to determine if a string is a number in Python. This validation is essential for clean data and smooth type conversions. Python provides several built-in methods to get the job done.

In this article, you’ll explore techniques from simple methods to robust solutions. You'll get practical tips, see real-world applications, and learn how to debug common issues to write more reliable code.

Using the isdigit() method

text = "12345"
result = text.isdigit()
print(f"Is '{text}' a number? {result}")--OUTPUT--Is '12345' a number? True

The isdigit() method offers a quick way to verify if a string contains only digits. As a built-in string method, you can call it directly on your string variable. It's perfect for simple validation scenarios where you expect a positive integer.

However, isdigit() has its limits. It returns False for strings that include characters other than digits, such as:

  • Negative signs (e.g., "-100")
  • Decimal points (e.g., "99.9")

This makes it unsuitable for validating floating-point numbers or signed integers. For those cases, you'll need a more robust approach.

Basic string validation methods

When isdigit() isn’t enough, Python offers more powerful tools for handling a wider range of numeric formats, including decimals and negative values.

Using isnumeric() and isdecimal() for number detection

text1, text2, text3 = "123", "½", "⑦"
print(f"{text1}: isdigit={text1.isdigit()}, isnumeric={text1.isnumeric()}, isdecimal={text1.isdecimal()}")
print(f"{text2}: isdigit={text2.isdigit()}, isnumeric={text2.isnumeric()}, isdecimal={text2.isdecimal()}")
print(f"{text3}: isdigit={text3.isdigit()}, isnumeric={text3.isnumeric()}, isdecimal={text3.isdecimal()}")--OUTPUT--123: isdigit=True, isnumeric=True, isdecimal=True
½: isdigit=False, isnumeric=True, isdecimal=False
⑦: isdigit=True, isnumeric=True, isdecimal=False

While they seem similar, isdecimal() and isnumeric() handle different character sets. The key is understanding their scope, from most restrictive to most inclusive.

  • isdecimal() only returns True for strings containing basic digits (0-9).
  • isdigit() is broader, also accepting special digit characters like circled numbers ().
  • isnumeric() is the most permissive, recognizing everything the others do plus characters that represent numbers, like vulgar fractions (½).

None of these methods, however, can handle negative signs or decimal points.

Using try/except to validate numeric strings

def is_number(text):
try:
float(text)
return True
except ValueError:
return False

print(is_number("123")) # Integer
print(is_number("123.45")) # Float
print(is_number("abc")) # Not a number--OUTPUT--True
True
False

For a more versatile solution, you can use a try/except block. This approach attempts to convert a string to a number using the float() function. If the conversion is successful, the function returns True. If the string isn't a valid number, a ValueError occurs, which the except block catches and returns False.

  • It correctly validates integers and floating-point numbers.
  • It also handles negative values, like "-123.45".

This makes it one of the most reliable methods for general-purpose number validation in Python.

Using regular expressions for number validation

import re

def is_number_regex(text):
pattern = r'^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$'
return bool(re.match(pattern, text))

print(is_number_regex("123")) # Integer
print(is_number_regex("-123.45")) # Negative float
print(is_number_regex("1.23e-4")) # Scientific notation--OUTPUT--True
True
True

Regular expressions offer the most powerful way to validate numeric strings. By defining a specific pattern, you can control exactly what formats are considered valid. The re.match() function then checks if the input string matches this pattern from the beginning. This approach is incredibly flexible.

  • It can handle integers, floats, and negative numbers.
  • It also supports more complex formats like scientific notation.

While more complex to write, regex is ideal when you need to enforce strict formatting rules beyond what a try/except block with float() can offer.

Advanced validation techniques

Beyond the built-in methods and regex, you can implement safer and more structured validation by creating custom functions and classes for your specific needs.

Using ast.literal_eval() for safe type checking

import ast

def is_number_literal(text):
try:
value = ast.literal_eval(text)
return isinstance(value, (int, float, complex))
except (ValueError, SyntaxError):
return False

print(is_number_literal("123")) # Integer
print(is_number_literal("12.3")) # Float
print(is_number_literal("'123'")) # String of digits (not a number)--OUTPUT--True
True
False

The ast.literal_eval() function offers a secure way to parse strings into Python data types. Unlike the risky eval() function, it only processes literal structures like numbers, strings, and lists, preventing arbitrary code execution. This makes it a great choice for handling untrusted input safely.

  • The function first attempts to evaluate the string with literal_eval().
  • It then uses isinstance() to confirm the evaluated value is a numeric type—specifically an int, float, or complex.
  • This is why it correctly identifies "123" as a number but rejects "'123'", which evaluates to a string.

Creating a flexible number validation function

def is_numeric(text, allow_scientific=True, allow_negative=True, allow_complex=False):
if not text:
return False

try:
value = complex(text) if allow_complex else float(text)
if not allow_negative and value.real < 0:
return False
if not allow_scientific and ('e' in text.lower() or 'E' in text):
return False
return True
except ValueError:
return False

print(is_numeric("123"), is_numeric("-456", allow_negative=False))--OUTPUT--True False

For tailored validation, you can build a flexible function like is_numeric. It gives you precise control by using boolean parameters to define what qualifies as a number, making it highly adaptable to your project's needs.

  • allow_negative: Lets you decide whether to accept or reject negative values.
  • allow_scientific: Toggles validation for numbers in scientific notation.
  • allow_complex: Determines if complex numbers are permitted.

The function first attempts to convert the string to a float or complex type, then checks it against the rules you've set with the flags.

Implementing a NumberValidator class

class NumberValidator:
@staticmethod
def is_integer(text):
if not text or (text[0] == '-' and len(text) == 1):
return False
return text.lstrip('-').isdigit()

@staticmethod
def is_float(text):
try:
float(text)
return '.' in text or 'e' in text.lower()
except ValueError:
return False

validator = NumberValidator()
print(f"Is integer: {validator.is_integer('-123')}, Is float: {validator.is_float('45.67')}")--OUTPUT--Is integer: True, Is float: True

For more structured validation, you can create a NumberValidator class. This approach bundles related logic into a single, reusable unit, making your code cleaner. Since the methods are static, you can call them directly on the class without creating an object.

  • The is_integer method handles negative numbers by stripping a leading - sign before checking if the rest of the string contains only digits.
  • is_float uses a try/except block to attempt a conversion. It also confirms the string contains a . or e to distinguish it from a whole number.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

The validation techniques in this article can be the foundation for real-world applications. Replit Agent can turn these concepts into production tools:

  • Build a data cleaning utility that validates and sanitizes user-uploaded CSV files, ensuring numeric columns contain valid integers or floats.
  • Create an interactive unit converter that accepts decimals and negative values for inputs like temperature or currency.
  • Deploy a log file analyzer that uses strict patterns to parse and validate numerical data from system logs, including scientific notation.

You can turn your own ideas into reality. Describe your app to Replit Agent, and it will write, test, and deploy the code for you, all from your browser.

Common errors and challenges

Even with the right tools, you can run into tricky edge cases and subtle bugs when validating numeric strings in Python.

  • Handling non-English numerals with isdigit(): The isdigit() method can be misleading because it recognizes more than just the standard 0-9 digits. It returns True for various Unicode characters, like circled numbers or superscripts, which might not be what you want. If your application requires only Western Arabic numerals, relying solely on isdigit() can introduce unexpected data.
  • Avoiding silent failures in try/except validation: A generic try/except ValueError block can sometimes hide problems by failing silently. For instance, it might correctly reject non-numeric text but also catch empty strings or whitespace without distinction. It's better to add explicit checks for these edge cases before the try block to ensure your validation is both accurate and intentional.
  • Fixing common regex pattern mistakes for number validation: Crafting the perfect regular expression is tricky, and common mistakes can lead to flawed validation. A pattern might be too permissive, incorrectly matching partial numbers or multiple decimal points. Conversely, it could be too strict, failing to account for valid formats like scientific notation. Always anchor your pattern with ^ and $ to ensure it matches the entire string, not just a portion of it.

Handling non-English numerals with isdigit()

While isdigit() seems straightforward, its definition of a "digit" is broader than you might expect. It recognizes various Unicode numerals, not just 0-9, which can cause validation logic to pass unexpectedly. The following code demonstrates this with a simple validation function.

def validate_user_age(age_input):
if not age_input.isdigit():
return "Error: Please enter digits only"
return f"Valid age: {age_input}"

print(validate_user_age("30")) # Regular digits
print(validate_user_age("३०")) # Hindi numerals
print(validate_user_age("三十")) # Chinese numerals (not digits)

The validate_user_age function accepts Hindi numerals because isdigit() recognizes more than just 0-9. This can corrupt your data if you only expect standard digits. The code below shows how to enforce stricter validation for your inputs.

def validate_user_age(age_input):
if not age_input.isnumeric():
return "Error: Please enter numeric characters"
return f"Valid age: {age_input}"

print(validate_user_age("30")) # Regular digits
print(validate_user_age("३०")) # Hindi numerals
print(validate_user_age("三十")) # Chinese numerals (not digits)

The updated function swaps isdigit() for isnumeric() to correctly validate a broader range of numeric characters, including those from other languages like Hindi ("३०"). The isnumeric() method recognizes any character that represents a number in Unicode, not just the standard 0-9 digits. This makes it a better choice for international applications. It still correctly rejects non-numeric text like "三十", ensuring you only accept valid numeric inputs from a global user base.

Avoiding silent failures in try/except validation

Using a generic try/except block can be a double-edged sword. While it prevents your program from crashing, catching all exceptions without distinction can hide bugs. Returning 0 for any failed conversion, for instance, masks the original error. The following code demonstrates this silent failure.

def convert_to_number(value):
try:
return float(value)
except:
return 0 # Silent failure

data = ["42", "3.14", "error", "98.6"]
converted = [convert_to_number(item) for item in data]
print(converted)

The broad except block catches the error from "error" and returns 0, masking the invalid input. This makes it impossible to tell a real zero from a failed conversion. The following code demonstrates a more explicit approach.

def convert_to_number(value):
try:
return float(value)
except ValueError as e:
print(f"Couldn't convert '{value}': {e}")
return None

data = ["42", "3.14", "error", "98.6"]
converted = [convert_to_number(item) for item in data]
print([x for x in converted if x is not None])

The improved function catches a specific ValueError instead of all exceptions. It returns None on failure—a clear signal that something went wrong—unlike returning 0. This lets you distinguish between a failed conversion and a legitimate zero.

The code also logs the specific error and filters out the None values from the final list. This leaves you with only clean, valid numbers, which is crucial when processing datasets where you can't afford to mix up errors with actual data.

Fixing common regex pattern mistakes for number validation

Regular expressions offer precise control, but they're notoriously tricky to get right. A flawed pattern can be too strict, rejecting valid numbers, or too permissive, accepting incorrect formats. This can silently corrupt your data. The code below shows this common pitfall.

import re

def validate_decimal(number_str):
pattern = r'^\d+\.\d+$' # Only matches numbers with decimal points
return bool(re.match(pattern, number_str))

print(validate_decimal("42")) # Integer
print(validate_decimal("3.14159")) # Decimal
print(validate_decimal(".5")) # Leading decimal

The pattern r'^\d+\.\d+$' is too rigid, requiring digits on both sides of the decimal. This causes it to incorrectly reject whole numbers like "42" and decimals starting with a period like ".5". The following code demonstrates a more flexible pattern.

import re

def validate_decimal(number_str):
pattern = r'^(\d+(\.\d*)?|\.\d+)$' # Matches integers and decimals
return bool(re.match(pattern, number_str))

print(validate_decimal("42")) # Integer
print(validate_decimal("3.14159")) # Decimal
print(validate_decimal(".5")) # Leading decimal

The updated pattern r'^(\d+(\.\d*)?|\.\d+)$' is far more flexible. It uses an OR operator (|) to define two valid formats. The first part, \d+(\.\d*)?, accepts both whole numbers and standard decimals. The second, \.\d+, correctly validates decimals that start with a period, like ".5". By anchoring the pattern with ^ and $, you ensure it matches the entire string, making your validation logic much more reliable against unexpected inputs.

Real-world applications

Beyond just avoiding errors, these validation techniques are essential for practical tasks like cleaning user input and processing data files.

Validating user input in a registration form with isdigit()

In a registration form, isdigit() serves as a quick initial check to confirm an age input contains only digits before you proceed with further validation, like ensuring the number is within an acceptable range.

def validate_age(age_str):
if not age_str.isdigit():
return "Error: Age must contain only digits"

age = int(age_str)
if age < 18 or age > 120:
return "Error: Age must be between 18 and 120"

return f"Success: {age} is a valid age"

print(validate_age("25"))
print(validate_age("seventeen"))
print(validate_age("150"))

The validate_age function demonstrates a common two-step validation pattern. It's an efficient way to handle user input before processing it further.

  • First, it uses isdigit() to confirm the input string contains only digits. This prevents errors from trying to convert non-numeric text.
  • If that check passes, it converts the string to an integer and then verifies that the number is within a logical range—in this case, between 18 and 120.

This layered approach ensures both the data's format and its value are correct before accepting it.

Cleaning numeric data from CSV files with try/except

A try/except block is perfect for cleaning datasets, such as those from a CSV file, by letting you gracefully handle and skip rows with invalid numeric entries.

def process_price_data(data_lines):
valid_prices = []

for line in data_lines:
if ',' not in line:
continue

product, price = line.split(',')
try:
valid_prices.append((product, float(price)))
except ValueError:
print(f"Skipping invalid price for {product}: {price}")

return valid_prices

data = ["apple,2.50", "banana,1.99", "orange,invalid", "grape,3.75"]
print(process_price_data(data))

The process_price_data function demonstrates a robust pattern for parsing raw data. It iterates through each line and attempts to convert the price portion to a float within a try block. This approach is resilient to errors.

  • If a price is invalid, the except ValueError block catches the error, preventing a crash. It prints a message and simply moves on.
  • This ensures that only correctly formatted product-price pairs are collected, making your data processing scripts more reliable when dealing with imperfect input files.

Get started with Replit

Turn these validation techniques into a real tool. Describe your idea to Replit Agent, like "build a currency converter that validates decimal inputs" or "create a script to clean non-numeric data from a CSV".

The agent writes the code, tests for errors, and deploys your app right from your browser. Start building with Replit to turn your concept into a working application.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.