I've been working on learning python disassembly, and I believe this shows the two calls are not atomic:
>>> dis.dis(separate_kv_fast)
2 0 LOAD_FAST 0 (adict)
3 LOAD_ATTR 0 (keys)
6 CALL_FUNCTION 0
9 LOAD_FAST 0 (adict)
12 LOAD_ATTR 1 (values)
15 CALL_FUNCTION 0
18 BUILD_TUPLE 2
21 RETURN_VALUE
>>>
That it calls keys and values across multiple opcodes I believe demonstrates it is not atomic.
Let's see how your bdict = dict(adict)
works out:
2 0 LOAD_GLOBAL 0 (dict)
3 LOAD_FAST 0 (adict)
6 CALL_FUNCTION 1
9 STORE_FAST 1 (bdict)
LOAD_FAST
pushes a reference to adict
onto the stack. We then call dict
with that argument. What we don't know is if dict()
function is atomic.
bdict = adict.copy()
gives a similar disassembly. adict.copy
can't be disassembled.
Everything I read says that internal types are thread safe. So I believe a single function call into a dictionary would be internally consistent. i.e., items()
, copy()
, values()
, keys()
, etc. Two calls in serial (values()
followed by keys()
aren't necessarilly safe. Neither are iterators.
Is there a reason your not just using items()
?
I was curious, so went ahead and benchmarked:
#!/usr/bin/python
import timeit
import random
D = dict()
for x in xrange(0, 1000):
D[x] = str(x)
def a():
return D.keys(), D.values()
def b():
keys = []
values = []
for k, v in D.items():
keys.append(k)
values.append(v)
return keys, values
def c():
d = D.copy()
return d.keys(), d.values()
def d():
return zip(*D.items())
print timeit.timeit("a()", 'from __main__ import a')
print timeit.timeit("b()", 'from __main__ import b')
print timeit.timeit("c()", 'from __main__ import c')
print timeit.timeit("d()", 'from __main__ import d')
Results:
6.56165385246
145.151810169
19.9027020931
65.4051799774
The copy is the fasted atomic one (and might be slightly faster than using dict()).