In Python, int() in a try block wins until one string in ten is bad
Parsing a six-digit string with int() inside try/except ValueError took 20 ns in Python 3.14 when every string was valid, against 37 ns for checking s.isdigit() first. But a failed parse cost about 210 ns, so when half the strings were junk the try block took 124 ns per item against 29 for the check. The break-even was near 90% valid.
The test
20,000 strings per run, each a random six-digit number with probability p and the text n/a otherwise. Three loops: try: int(s) except ValueError; int(s) if s.isdigit() else None; and int(s) if re.compile(r"\d+").fullmatch(s) else None. Best of 7 repeats, in a container with 2 CPUs. Nanoseconds per item.
| share valid | try/except | isdigit first | regex first |
|---|---|---|---|
| 100% | 20 | 37 | 82 |
| 99% | 22 | 38 | 82 |
| 90% | 40 | 36 | 80 |
| 50% | 124 | 29 | 71 |
| 10% | 197 | 13 | 50 |
| 0% | 214 | 8 | 45 |
Why
The try block has no cost when nothing is raised, so the all-valid case is just int(). Raising is the expensive part: building the exception, formatting its message with the offending string, and unwinding to the handler adds roughly 190 ns per failure. The pre-check pays a method call on every item (about 17 ns on top of the parse) but a rejected item costs only that call. The regex is slower than both at every mix, since fullmatch is a heavier call than a string method.
So what
If bad input is rare, such as a file of numbers with the odd blank, use the try block. If it is common, such as a column that is half empty or a mixed-type CSV field, check first. In between, the two are within a few nanoseconds of each other, so write whichever reads better.
One trap in the check: "²".isdigit() is True but int("²") raises ValueError, so the pre-check lets some bad strings through. isdecimal() matches what int() accepts better, though neither covers a leading minus sign, or the whitespace and underscores int() tolerates.
Method: one machine, a 25-line script, Python 3.14 in a container. Fixed-width six-digit strings and one kind of junk; I didn't measure longer numbers or other failure messages, and I didn't time isdecimal().