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)
As of Python 3.6, f-strings are a great new way to format strings. Not only are they more readable, more concise, and less prone to error than other ways of formatting, but they are also faster!
F-strings provide a way to embed expressions inside string literals, using a minimal syntax. It should be noted that an f-string is really an expression evaluated at run time, not a constant value. In Python source code, an f-string is a literal string, prefixed with 'f', which contains expressions inside braces.
Summary: f-string is more readable and easier to implement than % and . format() string formatting styles. Furthermore, using f-strings is suggested for Python 3.6 and above while . format() is best suited for Python 2.6 and above.
The most obvious usage of F strings is to interpolate simple values inside of throws, output, what have you. This is done using the {} syntax and just providing that values name. The value will also be implicitly casted if it is possible, of course.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With