21

I was playing around with f-strings (see PEP 498), and I decided to check the speed of the f-string parse, (e.g. f"{1}") in comparison with the usual str parse (e.g str(1)). But to my surprise, when I checked the speed of both methods with the timeit function, I found out that f-strings are faster.

>>> from timeit import timeit
>>> timeit("f'{1}'")
0.1678762999999961

whereas

>>> timeit("str(1)")
0.3216999999999999

or even the repr func, which in most of the cases is faster than str cast

>>> timeit("repr(1)")
0.2528296999999995

I wonder why is that? I thought that the f-strings called str internally, but now, I'm a bit confused, any ideas? Thanks in advance!

PD: Just if anyone is wondering:

assert f"{1}" == str(1) == repr(1)
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Davichete
  • 415
  • 7
  • 14

1 Answers1

29

The simple answer is because f-strings are part of the language's grammar and syntax. The str() call on the other hand requires a symbol table lookup, followed by a function call.

Here's a similar example which interpolates an integer variable, contrast this with the constant value interpolation.

x = 1

%timeit f'{1}'
%timeit f'{x}'
%timeit str(1)
%timeit str(x)

113 ns ± 2.25 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
166 ns ± 4.71 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
342 ns ± 23.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
375 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

The difference in the behaviour is obvious when you look at the disassembled byte code with dis.

import dis

dis.dis("f'{x}'")
  1           0 LOAD_NAME                0 (x)
              2 FORMAT_VALUE             0
              4 RETURN_VALUE

dis.dis("str(x)")
  1           0 LOAD_NAME                0 (str)
              2 LOAD_NAME                1 (x)
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

The heavy lifting is all in the CALL_FUNCTION instruction, an overhead which f-strings certainly don't have -- at least in this case, as nothing needs to be eval'd.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
cs95
  • 379,657
  • 97
  • 704
  • 746