Note: Before you spend your time (a.k.a your employer's money) on shaving nano- or milli- seconds off of your program, make sure you positively need to optimize it.
Read: Premature optimization is the root of all evil -- DonaldKnuth
TL;DR
Yes reading directly from a variable will always be faster than reading from a dictionary, which requires to compute a hash.
In-Depth
Both algorithms have a complexity of O(1), however, the fundamental operation of a dictionary is to compute a hash, then read an address in memory. This is in comparison with a variable, which simply reads from memory.
hash + read > read
This begs the question "When is worth it allocate a variable in memory to not re-use a dictionary?" or "How large is the difference between both operations?"
Quick and dirty benchmark
If we trust timeit
, then it would always be faster to cache the value, even when we call the value only twice. Tested in JupyterLab:
Initialize a test dictionary:
asdf = dict(a=1, b=2, c=3)
1st test
Read the same dictionary value twice:
%timeit (asdf['a'], asdf['a'])
82.5 ns ± 7.6 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
Read from dictionary once, then cache it:
%timeit (u := asdf['a'], u)
62.2 ns ± 2.54 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
2nd test
If you have to call a value a hundred and one times, you can speed up you calls by x5:
%%timeit [asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a'], asdf['a']]
2.85 µs ± 269 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
And here is the faster variant:
%timeit [u := asdf['a'], u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u, u]
528 ns ± 29.4 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)