Skip to content

Instantly share code, notes, and snippets.

@mikeckennedy
Created July 9, 2020 18:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikeckennedy/00828db1d49d2cd2dac8fa0295e54c23 to your computer and use it in GitHub Desktop.
Save mikeckennedy/00828db1d49d2cd2dac8fa0295e54c23 to your computer and use it in GitHub Desktop.
Simple example to test how much faster or slower it is to simply try and convert then catch an error vs. testing up front.
import datetime
import random
from typing import List
def main():
random.seed(172)
count = 1_000_000
data = build_data(count)
run_with_except(data)
run_with_test(data)
def build_data(count: int):
letters = "abcdefghijklmnopqrstuv"
numbers = list(range(1, 11))
combined = list(letters) + numbers
return [
str(random.choice(combined))
for _ in range(count)
]
def run_with_except(data: List):
t0 = datetime.datetime.now()
numbers = 0
for item in data:
try:
n = int(item)
numbers += 1
except:
pass
dt = datetime.datetime.now() - t0
print(
f'EXCEPT: Done in {dt.total_seconds() * 1000:,.0f} ms, {numbers:,} numbers and {len(data) - numbers:,} letters.')
def run_with_test(data: List):
t0 = datetime.datetime.now()
numbers = 0
for item in data:
if item.isnumeric():
n = int(item)
numbers += 1
dt = datetime.datetime.now() - t0
print(
f'TEST: Done in {dt.total_seconds() * 1000:,.0f} ms, {numbers:,} numbers and {len(data) - numbers:,} letters.')
if __name__ == '__main__':
main()
@mikeckennedy
Copy link
Author

Output on my Macbook:

EXCEPT: Done in 663 ms, 311,656 numbers and 688,344 letters.
TEST: Done in 107 ms, 311,656 numbers and 688,344 letters.

Process finished with exit code 0

@parberge
Copy link

parberge commented Aug 4, 2020

Hm, this comes as a surprise for me. I thought "It's easier to ask for forgiveness than permission" meant it was always better with try/catch.

Very good example, thanks!

@mikeckennedy
Copy link
Author

You're welcome! It is easier to read and easier to maintain often. And many times you have to account for exceptions at lower levels to sorta require both. Therefore, code-wise, it's easier, but in raw perf, it's not necessarily faster. :)

@madig
Copy link

madig commented Oct 3, 2020

Mh, int() can do more than isnumeric() lets through, e.g. int("-1") does the right thing, "-1".isnumeric() won't. All I want is a fast way of going str -> either int or float without using exceptions :(

@eznix86
Copy link

eznix86 commented Nov 4, 2021

M1 Macbook Air:

EXCEPT: Done in 315 ms, 311,656 numbers and 688,344 letters.
TEST: Done in 59 ms, 311,656 numbers and 688,344 letters.

This is an interesting case, Make me thing about Golang, instead of "try catch" it returns the error until it is handled. Maybe Python should be coded such a way if performance is an issue.

@mikeckennedy
Copy link
Author

mikeckennedy commented Nov 4, 2021

@madig Other languages (like C#) have non-exception based versions in addition to this. For example:

// similar style
int x = int.Parse(text)

// exception free (but clumsier) style
int x;
if (!int.TryParse(text, out x))
{
    // error, not an int
}

@theArtechnology Perhaps :) But major changes to the syntax are probably not worth it until this because one of the slower parts of Python. There are many more pieces of low hanging fruit before worrying about this TOO much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment