A curious bit of code...

Zachary Ware zachary.ware+pylist at gmail.com
Thu Feb 13 20:59:41 CET 2014


On Thu, Feb 13, 2014 at 12:37 PM,  <forman.simon at gmail.com> wrote:
> I ran across this and I thought there must be a better way of doing it, but then after further consideration I wasn't so sure.
>
>   if key[:1] + key[-1:] == '<>': ...
>
>
> Some possibilities that occurred to me:
>
>   if key.startswith('<') and key.endswith('>'): ...
>
> and:
>
>   if (key[:1], key[-1:]) == ('<', '>'): ...
>
>
> I haven't run these through a profiler yet, but it seems like the original might be the fastest after all?

In a fit of curiosity, I did some timings:

'and'ed indexing:

C:\tmp>py -m timeit -s "key = '<test>'" "key[0] == '<' and key[-1] == '>'"
1000000 loops, best of 3: 0.35 usec per loop

C:\tmp>py -m timeit -s "key = '<test'" "key[0] == '<' and key[-1] == '>'"
1000000 loops, best of 3: 0.398 usec per loop

C:\tmp>py -m timeit -s "key = 'test>'" "key[0] == '<' and key[-1] == '>'"
1000000 loops, best of 3: 0.188 usec per loop

C:\tmp>py -m timeit -s "key = 'test'" "key[0] == '<' and key[-1] == '>'"
10000000 loops, best of 3: 0.211 usec per loop

C:\tmp>py -m timeit -s "key = ''" "key[0] == '<' and key[-1] == '>'"
Traceback (most recent call last):
  File "P:\Python34\lib\timeit.py", line 292, in main
    x = t.timeit(number)
  File "P:\Python34\lib\timeit.py", line 178, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
    key[0] == '<' and key[-1] == '>'
IndexError: string index out of range


Slice concatenation:

C:\tmp>py -m timeit -s "key = '<test>'" "key[:1] + key[-1:] == '<>'"
1000000 loops, best of 3: 0.649 usec per loop

C:\tmp>py -m timeit -s "key = '<test'" "key[:1] + key[-1:] == '<>'"
1000000 loops, best of 3: 0.7 usec per loop

C:\tmp>py -m timeit -s "key = 'test>'" "key[:1] + key[-1:] == '<>'"
1000000 loops, best of 3: 0.663 usec per loop

C:\tmp>py -m timeit -s "key = 'test'" "key[:1] + key[-1:] == '<>'"
1000000 loops, best of 3: 0.665 usec per loop

C:\tmp>py -m timeit -s "key = ''" "key[:1] + key[-1:] == '<>'"
1000000 loops, best of 3: 0.456 usec per loop


String methods:

C:\tmp>py -m timeit -s "key = '<test>'" "key.startswith('<') and
key.endswith('>')"
1000000 loops, best of 3: 1.03 usec per loop

C:\tmp>py -m timeit -s "key = '<test'" "key.startswith('<') and
key.endswith('>')"
1000000 loops, best of 3: 1.02 usec per loop

C:\tmp>py -m timeit -s "key = 'test>'" "key.startswith('<') and
key.endswith('>')"
1000000 loops, best of 3: 0.504 usec per loop

C:\tmp>py -m timeit -s "key = 'test'" "key.startswith('<') and
key.endswith('>')"
1000000 loops, best of 3: 0.502 usec per loop

C:\tmp>py -m timeit -s "key = ''" "key.startswith('<') and key.endswith('>')"
1000000 loops, best of 3: 0.49 usec per loop


Tuple comparison:

C:\tmp>py -m timeit -s "key = '<test>'" "(key[:1], key[-1:]) == ('<', '>')"
1000000 loops, best of 3: 0.629 usec per loop

C:\tmp>py -m timeit -s "key = '<test'" "(key[:1], key[-1:]) == ('<', '>')"
1000000 loops, best of 3: 0.689 usec per loop

C:\tmp>py -m timeit -s "key = 'test>'" "(key[:1], key[-1:]) == ('<', '>')"
1000000 loops, best of 3: 0.676 usec per loop

C:\tmp>py -m timeit -s "key = 'test'" "(key[:1], key[-1:]) == ('<', '>')"
1000000 loops, best of 3: 0.675 usec per loop

C:\tmp>py -m timeit -s "key = ''" "(key[:1], key[-1:]) == ('<', '>')"
1000000 loops, best of 3: 0.608 usec per loop


re.match():

C:\tmp>py -m timeit -s "import re;key = '<test>'" "re.match(r'^<.*>$', key)"
100000 loops, best of 3: 3.39 usec per loop

C:\tmp>py -m timeit -s "import re;key = '<test'" "re.match(r'^<.*>$', key)"
100000 loops, best of 3: 3.27 usec per loop

C:\tmp>py -m timeit -s "import re;key = 'test>'" "re.match(r'^<.*>$', key)"
100000 loops, best of 3: 2.94 usec per loop

C:\tmp>py -m timeit -s "import re;key = 'test'" "re.match(r'^<.*>$', key)"
100000 loops, best of 3: 2.97 usec per loop

C:\tmp>py -m timeit -s "import re;key = ''" "re.match(r'^<.*>$', key)"
100000 loops, best of 3: 2.97 usec per loop


Pre-compiled re:

C:\tmp>py -m timeit -s "import re;r = re.compile(r'^<.*>$');key =
'<test>'" "r.match(key)"
1000000 loops, best of 3: 0.932 usec per loop

C:\tmp>py -m timeit -s "import re;r = re.compile(r'^<.*>$');key =
'<test'" "r.match(key)"
1000000 loops, best of 3: 0.79 usec per loop

C:\tmp>py -m timeit -s "import re;r = re.compile(r'^<.*>$');key =
'test>'" "r.match(key)"
1000000 loops, best of 3: 0.718 usec per loop

C:\tmp>py -m timeit -s "import re;r = re.compile(r'^<.*>$');key =
'test'" "r.match(key)"
1000000 loops, best of 3: 0.755 usec per loop

C:\tmp>py -m timeit -s "import re;r = re.compile(r'^<.*>$');key = ''"
"r.match(key)"
1000000 loops, best of 3: 0.731 usec per loop


Pre-compiled re with pre-fetched method:

C:\tmp>py -m timeit -s "import re;m = re.compile(r'^<.*>$').match;key
= '<test>'" "m(key)"
1000000 loops, best of 3: 0.777 usec per loop

C:\tmp>py -m timeit -s "import re;m = re.compile(r'^<.*>$').match;key
= '<test'" "m(key)"
1000000 loops, best of 3: 0.65 usec per loop

C:\tmp>py -m timeit -s "import re;m = re.compile(r'^<.*>$').match;key
= 'test>'" "m(key)"
1000000 loops, best of 3: 0.652 usec per loop

C:\tmp>py -m timeit -s "import re;m = re.compile(r'^<.*>$').match;key
= 'test'" "m(key)"
1000000 loops, best of 3: 0.576 usec per loop

C:\tmp>py -m timeit -s "import re;m = re.compile(r'^<.*>$').match;key
= ''" "m(key)"
1000000 loops, best of 3: 0.58 usec per loop


And the winner is:

C:\tmp>py -m timeit -s "key = '<test>'" "key and key[0] == '<' and
key[-1] == '>'"
1000000 loops, best of 3: 0.388 usec per loop

C:\tmp>py -m timeit -s "key = '<test'" "key and key[0] == '<' and
key[-1] == '>'"
1000000 loops, best of 3: 0.413 usec per loop

C:\tmp>py -m timeit -s "key = 'test>'" "key and key[0] == '<' and
key[-1] == '>'"
1000000 loops, best of 3: 0.219 usec per loop

C:\tmp>py -m timeit -s "key = 'test'" "key and key[0] == '<' and key[-1] == '>'"
1000000 loops, best of 3: 0.215 usec per loop

C:\tmp>py -m timeit -s "key = ''" "key and key[0] == '<' and key[-1] == '>'"
10000000 loops, best of 3: 0.0481 usec per loop


So, the moral of the story?  Use short-circuit logic wherever you can,
don't use re for simple stuff (because while it may be very fast, it's
dominated by attribute lookup and function call overhead), and unless
you expect to be doing this test many many millions of times in a very
short space of time, go for readability over performance.

-- 
Zach



More information about the Python-list mailing list