Definition
A Python function often expects more than a type: it expects a value that makes sense. Hand it a string where it wants one, and it will not complain, the type is right. But if the content does not match anything the function knows how to handle, it needs another way to say so: that is the role of this exception, ValueError.
age = int("twelve") # a number was expected, a word was typed instead
# ValueError: invalid literal for int() with base 10: 'twelve'The string "twelve" is indeed a string, and int accepts strings just fine as an argument. That is not where the problem lies: this particular piece of text simply holds no number that can be recognised. This distinction changes where to look when something breaks. Passing a list to the same function would have produced a TypeError instead, since the complaint would then be about the nature of the argument rather than its content.
The difference with TypeError
Mixing the two errors up sends you looking in the wrong place for long minutes, because you end up fixing the type of a value that was already the right type. Three calls are enough to see exactly where the line falls.
| Call | Result | Reason |
|---|---|---|
int("12") | No error | A string that does hold a number |
int("twelve") | ValueError | The right type, content that cannot convert |
int([1, 2]) | TypeError | A type the function cannot handle at all |
The distinction becomes almost automatic once these three cases are clear: if fixing the content of the value is enough to make the call succeed, it is a ValueError; if its nature has to change, it is a TypeError. The traceback always shows the offending value on its last line, no need to guess it.
Where it shows up most often
Converting user input comes first by a wide margin, for a simple reason: an input always hands back text, whatever the person actually typed. Converting that text with int or float makes the whole program depend on whatever happens to be on the keyboard at that moment.
Unpacking comes next, for a related reason. Multiple assignment demands an exact number of items on both sides of the equals sign, and a split on a malformed line does not always supply that many.
surname, first = "Smith".split(";") # the expected line contained a semicolon
# ValueError: not enough values to unpack (expected 2, got 1)Then come failed lookups, such as items.index(value) or items.remove(value) called on a missing element, followed by maths functions asked to work outside their domain, such as a square root on a negative number. These situations share the same root cause: the data comes from outside the program, and nobody checked it before using it.
Catching it, or raising it yourself
Catching is done with try and except, targeting the precise error rather than too broad a class.
while True:
entry = input("Your age: ")
try:
age = int(entry)
break
except ValueError:
print("Digits only, please.")Writing except Exception instead of except ValueError looks safer, but it also swallows typos in a variable name or a missing key lookup. The program keeps running in silence, and the real bug only surfaces much later, somewhere else.
The other half of the job is raising ValueError inside your own functions with raise. A function receiving a negative duration or a month of 13 has every reason to refuse right away, with a message naming the value it got, rather than letting absurd data travel across ten more calls.
def apply_discount(price, percent):
if not 0 <= percent <= 100:
raise ValueError(f"percent out of bounds: {percent}")
return price * (1 - percent / 100)The real fix sits upstream
A ValueError caught in silence has fixed nothing at all, it has only hidden the symptom. It almost always signals that a piece of data entered the program without being checked, and the place where it blows up is rarely the place where the problem was born.
The method that saves the most time is validating at the border, at the moment the text is typed, the file is read, or the answer from an outside service is decoded. Past that point the rest of the program works on data whose shape is guaranteed, and the error disappears from the deeper layers of the code, exactly where it costs the most to understand.
Frequently asked questions
How can input be converted without risking the error?
There is no forgiving conversion in Python: int either succeeds or raises. The only reliable approach is wrapping the call in a try, or validating the string beforehand with a method such as isdigit, which handles neither a negative sign nor decimals.
Why does my unpacking fail on a single line of the file?
Because that particular line does not match the expected shape: a missing separator, an empty field at the end, or a trailing blank line. Setting a maximum number of splits with split(";", 1), or checking the length of the result before assigning, settles the problem for good.
Should this error be raised, or should a custom exception be created?
As long as the problem boils down to a rejected value, the standard error stays the better choice: everyone already knows it and knows how to catch it. A custom exception is only justified when the caller has to tell this exact case apart from the others in order to react differently, which is rarer than it looks.