Python ValueError: Causes, Handling, Raising, Logging, and Best Practices
Python raises a ValueError when a function receives an argument with the expected type but an unsuitable value. This exception appears in many situations, including type conversions, mathematical operations, unpacking, and custom input validation, making it one of the exceptions Python developers encounter most often in everyday development.
This tutorial explains what causes a ValueError, how it fits into Python’s exception hierarchy, and how to handle, raise, and log it correctly. The practical examples cover failed conversions, unpacking problems, real-world input validation, and production-ready logging. It also concludes with best practices that can be applied across Python projects.
Key Takeaways
ValueErroroccurs when a function receives the expected type but an unacceptable value, such as giving"hello"toint().- It inherits directly from
Exception, andUnicodeErroris its only built-in subclass. - Prefer
except ValueError:instead ofexcept Exception:or a bareexcept:so unrelated problems are not hidden. - When raising
ValueErroryourself, provide a clear message that includes the actual value responsible for the failure. - Use
raise ValueError(...) from efor exception chaining so the original traceback remains available for debugging. - In production applications, use
logging.exception()instead ofprint()when you need both the error message and complete stack trace. - Python 3.11 added
ExceptionGroupandexcept*for situations where several exceptions occur together in asynchronous contexts. NormalValueErrorhandling remains the same. - Python applications can be deployed from a Git repository using a managed application platform, allowing the hosting platform to handle application scaling.
Prerequisites
To follow this tutorial, you will need:
- Python 3.7 or newer installed on a local computer or server.
- Basic familiarity with Python syntax, including functions, imports, and executing scripts from the command line.
What Is a Python ValueError?
A ValueError is raised when a function receives a value with the correct type, but the value itself is not suitable for the requested operation. A common example is attempting to convert a non-numeric string with int().
try:
number = int(“hello”)
except ValueError as e:
print(f”Caught a ValueError: {e}”)
The output is:
Caught a ValueError: invalid literal for int() with base 10: ‘hello’
The value "hello" has the expected type, str, for int(), but its contents do not represent a valid integer. This difference between an incorrect type and an incorrect value is what distinguishes ValueError from TypeError.
Where ValueError Appears in the Python Exception Hierarchy
Knowing where ValueError belongs in Python’s exception hierarchy makes it easier to create precise exception handlers. Every Python exception derives from BaseException. Most exceptions used by applications inherit from Exception, which itself directly inherits from BaseException.
The following simplified hierarchy focuses on exceptions commonly encountered together with ValueError:
BaseException
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError, OverflowError, FloatingPointError
├── LookupError
│ └── IndexError, KeyError
├── TypeError
├── ValueError
│ └── UnicodeError
│ └── UnicodeDecodeError, UnicodeEncodeError, UnicodeTranslateError
└── (many others)
ValueError is a direct subclass of Exception. Its only built-in subclass is UnicodeError, which groups errors related to Unicode encoding and decoding. An except ValueError: handler therefore catches both ValueError itself and its subclasses, including the UnicodeError family. Exceptions such as KeyError are siblings in other branches, such as LookupError.
The complete structure is available in the Python Built-in Exceptions documentation.
ValueError vs TypeError vs AttributeError: Main Differences
ValueError, TypeError, and AttributeError are often confused. The following table summarizes the difference between them.
| Exception | When It Is Raised | Example |
|---|---|---|
ValueError |
The type is correct, but the value is unsuitable. | int("abc"), math.sqrt(-1) |
TypeError |
The supplied type is incorrect. | "hello" + 5, len(42) |
AttributeError |
The requested attribute or method does not exist on the object. | "hello".push("x") |
A practical rule is that passing a string where a string is accepted but its contents are invalid usually results in ValueError. Passing a string where an integer is required generally results in TypeError.
Common Causes of ValueError in Python
The following sections describe several of the most frequent situations in which Python raises ValueError.
Type Conversion Errors with int(), float(), and complex()
One of the most common causes of ValueError is attempting to convert a string that does not contain a valid numeric representation.
try:
int(“hello”)
except ValueError as e:
print(f”int(): {e}”)
try:
float(“not a number”)
except ValueError as e:
print(f”float(): {e}”)
try:
complex(“abc”)
except ValueError as e:
print(f”complex(): {e}”)
The output is:
int(): invalid literal for int() with base 10: ‘hello’
float(): could not convert string to float: ‘not a number’
complex(): complex() arg is a malformed string
Unpacking Errors with Too Many or Too Few Values
Python also raises ValueError when the number of items in an iterable does not correspond to the number of variables used on the left side of an assignment.
try:
a, b = 1, 2, 3
except ValueError as e:
print(f”Too many: {e}”)
try:
x, y, z = [1, 2]
except ValueError as e:
print(f”Too few: {e}”)
The output is:
Too many: too many values to unpack (expected 2)
Too few: not enough values to unpack (expected 3, got 2)
Math Domain Errors with math.sqrt() and math.log()
The math module generates ValueError when an argument falls outside the valid mathematical domain of an operation.
import math
try:
math.sqrt(-1)
except ValueError as e:
print(f”sqrt: {e}”)
try:
math.log(-1)
except ValueError as e:
print(f”log: {e}”)
The output is:
sqrt: math domain error
log: math domain error
math.sqrt(-1) raises ValueError instead of producing a complex result because the math module operates only with real numbers. For complex-number calculations, use the cmath module.
Invalid Literals for int() with a Numeric Base
The int() function allows an optional second argument that defines the numeric base. Python raises ValueError when the supplied string is not valid for that base.
try:
result = int(“FF”, 16)
print(f”int(‘FF’, 16) = {result}”)
int(“FF”, 10)
except ValueError as e:
print(f”int(‘FF’, 10): {e}”)
try:
int(“2″, 2)
except ValueError as e:
print(f”int(‘2’, 2): {e}”)
The output is:
int(‘FF’, 16) = 255
int(‘FF’, 10): invalid literal for int() with base 10: ‘FF’
int(‘2’, 2): invalid literal for int() with base 2: ‘2’
'FF' is valid hexadecimal, or base 16, but it is not valid as a base-10 number. In the same way, '2' cannot be used as a binary digit because base 2 permits only 0 and 1.
How to Handle ValueError with try and except
After examining the common causes of ValueError, the next step is learning how to handle these exceptions correctly.
Basic try/except ValueError Pattern
The standard approach is to place the operation that may fail inside a try block and respond to the failure with an except ValueError: block.
import math
try:
x = int(input(“Please enter a positive number: “))
result = math.sqrt(x)
print(f”Square root of {x} is {result}”)
except ValueError:
print(“Invalid input. Please enter a positive integer.”)
If you need access to the original exception message, assign the exception to a variable using as.
try:
number = int(input(“Enter a number: “))
except ValueError as e:
print(f”Could not convert input to an integer: {e}”)
Catching Multiple Exceptions in a Single Block
If several exception types may occur and all of them should receive the same treatment, place the exception classes in a tuple within one except clause.
import math
try:
x = int(input(“Enter a positive number: “))
result = math.sqrt(x)
print(f”Square root of {x} is {result}”)
except (ValueError, TypeError) as e:
print(f”Invalid input: {e}”)
When the handling behavior is identical, this approach is more concise than maintaining separate except blocks.
Using else and finally with try/except
The else block executes only when the try block finishes without an exception. The finally block executes in every case, whether an exception occurs or not. Both blocks are optional.
try:
number = int(input(“Enter a number: “))
except ValueError:
print(“That is not a valid integer.”)
else:
print(f”Successfully parsed: {number}”)
finally:
print(“Input processing complete.”)
Use else for operations that should happen only after successful execution. Use finally for cleanup activities, such as closing files or database connections.
Comparing except ValueError, except Exception, and Bare except
Selecting an appropriate exception scope is important because overly broad exception handlers can conceal errors unrelated to the failure you intended to manage.
| Pattern | What It Catches | When to Use It |
|---|---|---|
except ValueError: |
Only ValueError and its subclasses |
When the expected failure is known precisely |
except Exception: |
All exceptions that do not terminate the system | Top-level error boundaries and general logging handlers |
bare except: |
Everything, including SystemExit and KeyboardInterrupt |
Almost never |
A bare except: can intercept signals such as KeyboardInterrupt, preventing a user from terminating the program with Ctrl+C. Prefer except ValueError: whenever possible, or use except Exception: only when a broader boundary is genuinely required.
How to Raise ValueError in Your Own Python Code
Your own functions may need to indicate that an argument or input is invalid even though its data type is correct. In such cases, raising ValueError clearly communicates that the supplied value itself is the problem.
Raising ValueError with a Custom Error Message
You can explicitly raise ValueError with the raise statement. A descriptive message should explain both the reason for the failure and the value that caused it.
def set_discount(percent):
if percent < 0 or percent > 100:
raise ValueError(f”Discount must be between 0 and 100, got {percent}”)
return percent
try:
set_discount(150)
except ValueError as e:
print(e)
print(set_discount(20))
The output is:
Discount must be between 0 and 100, got 150
20
Raising ValueError Inside Functions for Input Validation
A dependable design pattern is to validate function arguments before performing the main work. This keeps successful execution straightforward and makes invalid data easier to diagnose.
import math
def calculate_square_root(number):
if not isinstance(number, (int, float)):
raise TypeError(f”Expected a number, got {type(number).__name__}”)
if number < 0:
raise ValueError(f”Cannot calculate square root of a negative number: {number}”)
return math.sqrt(number)
print(calculate_square_root(25))
try:
calculate_square_root(-4)
except ValueError as e:
print(e)
try:
calculate_square_root(“25”)
except TypeError as e:
print(e)
The output is:
5.0
Cannot calculate square root of a negative number: -4
Expected a number, got str
This function raises TypeError when the supplied data type is wrong and ValueError when a correctly typed value violates the allowed mathematical domain. Maintaining this distinction allows callers to handle each type of failure independently when necessary.
Exception Chaining with raise … from
When a low-level exception is caught and replaced by a clearer high-level exception, raise ... from can preserve the original exception as context. Python then displays both exceptions in the traceback and indicates that the earlier exception directly caused the later one, which makes debugging easier.
def parse_user_id(raw_input):
try:
user_id = int(raw_input)
except ValueError as e:
raise ValueError(
f”Invalid user ID ‘{raw_input}’: must be a whole number”
) from e
if user_id <= 0:
raise ValueError(f”User ID must be a positive integer, got {user_id}”)
return user_id
try:
parse_user_id(“abc”)
except ValueError as e:
print(f”Error: {e}”)
print(f”Caused by: {e.__cause__}”)
print(parse_user_id(“42”))
The output is:
Error: Invalid user ID ‘abc’: must be a whole number
Caused by: invalid literal for int() with base 10: ‘abc’
42
Python ValueError in Real-World Scenarios
In practical software development, ValueError commonly appears while validating user input or processing information from files, APIs, and other external sources. The following examples demonstrate reliable approaches for these everyday situations.
Handling User Input Validation
A reliable input loop continues requesting data until the user supplies a valid value instead of allowing the application to terminate after the first invalid entry.
def get_positive_integer(prompt):
while True:
try:
value = int(input(prompt))
if value <= 0:
raise ValueError(f”Expected a positive integer, got {value}”)
return value
except ValueError as e:
print(f”Invalid input: {e}. Please try again.”)
quantity = get_positive_integer(“Enter quantity: “)
print(f”You ordered {quantity} item(s).”)
This loop handles both forms of ValueError: the exception generated by int() for non-numeric input and the explicitly raised exception for numbers that are zero or negative. In both situations, it asks for another value.
Parsing and Converting Data from Files or APIs
External data often arrives as strings that must be converted into appropriate data types. Malformed or missing data should be handled in a controlled way.
def extract_price(api_response):
try:
price = float(api_response[“price”])
except (ValueError, KeyError) as e:
raise ValueError(f”Could not extract price from response: {e}”) from e
if price < 0:
raise ValueError(f”Price cannot be negative, got {price}”)
return price
print(extract_price({“price”: “19.99”}))
try:
extract_price({“price”: “N/A”})
except ValueError as e:
print(e)
The output is:
19.99
Could not extract price from response: could not convert string to float: ‘N/A’
If api_response["price"] is unavailable or contains a string that cannot be interpreted as a number, the function raises a descriptive ValueError and retains the original lower-level exception as context.
Using ValueError for Class and Method Validation
Checking values inside __init__ prevents an object from being created in an invalid state. This can eliminate more difficult runtime problems later in the application.
class Rectangle:
def __init__(self, width, height):
if width <= 0:
raise ValueError(f”Width must be positive, got {width}”)
if height <= 0:
raise ValueError(f”Height must be positive, got {height}”)
self.width = width
self.height = height
def area(self):
return self.width * self.height
r = Rectangle(10, 5)
print(r.area())
try:
r = Rectangle(-3, 5)
except ValueError as e:
print(e)
The output is:
50
Width must be positive, got -3
Logging ValueError Exceptions in Production
Using print() to display errors can be adequate during development, but it is usually insufficient in production. Python’s standard logging module provides control over severity levels, destinations, and formatting while allowing the existing exception-handling structure to remain unchanged. A general introduction is available in Python logging documentation and tutorials.
Using the logging Module Instead of print()
logging.error() records a message at the error severity level. It can include information such as timestamps and severity labels and sends the output to the configured logging handler.
import logging
logging.basicConfig(
level=logging.ERROR,
format=”%(asctime)s %(levelname)s %(message)s”
)
def parse_quantity(value):
try:
return int(value)
except ValueError:
logging.error(“Failed to parse quantity from value: %r”, value)
return None
result = parse_quantity(“abc”)
print(f”Result: {result}”)
The output is:
2024-01-15 10:23:45,123 ERROR Failed to parse quantity from value: ‘abc’
Result: None
Capturing Stack Traces with logging.exception()
logging.exception() behaves similarly to logging.error() but additionally records the complete stack trace. Use it inside an except block when the log should show exactly where the exception occurred and what caused it.
import logging
logging.basicConfig(
level=logging.DEBUG,
format=”%(asctime)s %(levelname)s %(message)s”
)
def process_order(raw_quantity):
try:
quantity = int(raw_quantity)
print(f”Processing order for {quantity} item(s)”)
except ValueError:
logging.exception(“Invalid quantity received: %r”, raw_quantity)
process_order(“two”)
The output is:
2024-01-15 10:23:45,123 ERROR Invalid quantity received: ‘two’
Traceback (most recent call last):
File “example.py”, line 11, in process_order
quantity = int(raw_quantity)
ValueError: invalid literal for int() with base 10: ‘two’
Calling process_order("two") records the error message together with the complete traceback, making it easier to identify the exact failing line in production logs.
Python Exception Handling Best Practices
Strong exception handling is an important part of building dependable Python applications. The following practices make code safer, clearer, and easier to troubleshoot, particularly when working with value-related failures.
Catch Specific Exceptions Instead of All Exceptions
Catch the most specific exception that matches the expected problem. Catching Exception or using a bare except: can conceal bugs by intercepting failures that were never intended to be handled there.
# Do this
try:
value = int(user_input)
except ValueError:
print(“Please enter a valid integer.”)
# Avoid this
try:
value = int(user_input)
except Exception:
print(“Something went wrong.”)
Always Use Meaningful Error Messages
Whether a ValueError is being raised or logged, the message should provide enough information for someone to understand and correct the problem without having to inspect the source code first.
# Not helpful
raise ValueError(“Invalid input”)
# Much better
raise ValueError(f”Expected a positive integer for ‘quantity’, got {quantity!r}”)
Avoid Silently Suppressing Exceptions
Do not catch an exception and then ignore it completely. Using pass to suppress errors silently is a common source of difficult-to-explain bugs in Python applications.
# Avoid this
try:
result = int(user_input)
except ValueError:
pass
# Do this instead
try:
result = int(user_input)
except ValueError as e:
logging.error(“Failed to parse user input: %s”, e)
result = None
Python 3.11+ ExceptionGroup and except* Syntax
Python 3.11 introduced ExceptionGroup and the except* syntax through PEP 654. These features are intended for concurrent code, including asynchronous tasks, where several independent exceptions may happen at the same time. Standard try/except ValueError behavior remains unchanged.
# Python 3.11+ only
def validate_form(data):
errors = []
if not data.get(“name”):
errors.append(ValueError(“Name is required”))
if not isinstance(data.get(“age”), int):
errors.append(ValueError(“Age must be an integer”))
if errors:
raise ExceptionGroup(“Form validation failed”, errors)
try:
validate_form({“name”: “”, “age”: “not-a-number”})
except* ValueError as eg:
for exc in eg.exceptions:
print(f”Validation error: {exc}”)
The output is:
Validation error: Name is required
Validation error: Age must be an integer
When only one exception type is being handled, except* does not need parentheses, as in except* ValueError:. Parentheses are needed only when several exception types are grouped in a tuple, such as except* (ValueError, TypeError):. This syntax is unavailable in Python 3.10 and earlier versions.
Python ValueError FAQs
1. What Is the Difference Between except ValueError and except Exception in Python?
except ValueError: catches only ValueError and subclasses derived from it. except Exception: catches all exceptions that do not terminate the system. Using except ValueError: is more precise and reduces the risk of hiding unrelated problems.
2. When Should I Raise ValueError in My Own Python Functions?
Raise ValueError when a function receives an argument with the correct data type but an unacceptable value. Examples include supplying a negative number to a function that expects a positive integer or providing a string whose contents do not follow the expected format.
3. How Can I Catch ValueError and Still View the Complete Error Message?
Assign the exception to a variable using except ValueError as e:. You can then use str(e) or provide e to a logging function. To record the complete stack trace, call logging.exception() from inside the except block.
4. Can ValueError and TypeError Be Caught at the Same Time?
Yes. Put both exception classes in a tuple within the except clause: except (ValueError, TypeError):. The same block will then handle either exception.
5. What Causes a “Too Many Values to Unpack” ValueError in Python?
This error appears when the number of assignment variables does not equal the number of items in the iterable. For example, a, b = 1, 2, 3 raises ValueError: too many values to unpack (expected 2).
6. How Can I Log a ValueError Instead of Printing It?
Call logging.exception("your message") inside the except block. It sends the error message and complete stack trace to the configured logging handler while allowing the program to continue according to your exception-handling logic.
7. What Is Exception Chaining and When Should It Be Used with ValueError?
Exception chaining uses raise ValueError("message") from original_exception to preserve the exception that originally caused the failure. It is useful when converting a lower-level exception into a more descriptive ValueError because the traceback retains the complete chain of causes and becomes easier to debug.
8. Does Python 3.11 Change the Way ValueError Is Handled?
Python 3.11 added ExceptionGroup and except* for cases where several exceptions are raised together. Traditional ValueError handling with try and except ValueError is unchanged. The newer syntax is relevant only when working with ExceptionGroup objects, which are mainly used in asynchronous and concurrent code.
Conclusion
You now understand what causes a ValueError, where it belongs in Python’s exception hierarchy, and how to handle it precisely with try and except. You have also seen how to raise ValueError with useful messages in your own functions, use exception chaining to retain context, and replace print() with logging.exception() when complete production stack traces are needed. Catching specific exception types, writing informative error messages, and avoiding silent exception suppression can significantly reduce debugging effort as Python projects become larger.


