how are dictionary literals handled by the interpreter?
Jason
tenax.raccoon at gmail.com
Wed Sep 13 16:41:37 EDT 2006
akameswaran at gmail.com wrote:
> I wrote up a quick little set of tests, I was acutally comparing ways
> of doing "case" behavior just to get some performance information. Now
> two of my test cases had almost identical results which was not at all
> what I expected. Ultimately I realized I don't really know how
> literals are treated within the interpreter.
>
> The two implementations I was looking at were:
>
> class caseFunction(object):
> def __init__(self):
> self.caseDict = {'a':"retval = 'a'",
> 'b':"retval='b'","c":"retval='c'","d":"retval='d'",
>
> "e":"retval='e'","f":"retval='f'","g":"retval='g'","h":"retval='h'",
> "i":"retval='i'"}
>
> def doIt(self,a):
> exec(self.caseDict.get(a))
> return retval
>
>
>
> def caseFunc3(a):
> exec( {'a':"retval = 'a'",
> 'b':"retval='b'","c":"retval='c'","d":"retval='d'",
>
> "e":"retval='e'","f":"retval='f'","g":"retval='g'","h":"retval='h'",
> "i":"retval='i'"}.get(a))
> return retval
>
>
> I had expected caseFunc3 to be slower. I had thought the interpreter
> would be recreating the dictionary each time, but that doesn't seem to
> be the case since performance of the class version and the function
> version are nearly identical on most runs. If i rewrite caseFunc3 as:
>
> def caseFunc4(a):
> exec( dict({'a':"retval = 'a'",
> 'b':"retval='b'","c":"retval='c'","d":"retval='d'",
>
> "e":"retval='e'","f":"retval='f'","g":"retval='g'","h":"retval='h'",
> "i":"retval='i'"}).get(a))
> return retval
>
> now with the explicit use of dict, i see the performace of the
> functional version decline as I initially expected.
>
> So what is happeneing in caseFunc3. It seems as though the literal is
> "cached". The other hypothesis I came up with is the name lookup for
> self.caseDict takes the same amount of time as creating the dictionary
> literal - but that doesn't make sense to me.
>
> Thanks
Why not check to see what the interpreter is doing? Rather than
dealing with your overly complicated dictionaries, I've made a simple,
one case dictionary. I've also done a similar bit to replicate your
doIt method.
>>> def case3(a):
... exec( {'a': "retval = 'a'"}.get(a) )
... return retval
...
>>> case3('a')
'a'
>>> def case4(a):
... exec( dict({'a': "retval = 'a'"}).get(a) )
... return retval
...
>>> case4('a')
'a'
>>> class caseFunction(object):
... def doIt(self, a):
... exec(self.caseDict.get(a))
... return retval
...
Then, use the dis module to disassemble the function objects:
>>> import dis
>>> dis.dis(case3)
2 0 BUILD_MAP 0
3 DUP_TOP
4 LOAD_CONST 1 ('a')
7 LOAD_CONST 2 ("retval = 'a'")
10 ROT_THREE
11 STORE_SUBSCR
12 LOAD_ATTR 0 (get)
15 LOAD_FAST 0 (a)
18 CALL_FUNCTION 1
21 LOAD_CONST 0 (None)
24 DUP_TOP
25 EXEC_STMT
3 26 LOAD_NAME 2 (retval)
29 RETURN_VALUE
>>> dis.dis(case4)
2 0 LOAD_NAME 0 (dict)
3 BUILD_MAP 0
6 DUP_TOP
7 LOAD_CONST 1 ('a')
10 LOAD_CONST 2 ("retval = 'a'")
13 ROT_THREE
14 STORE_SUBSCR
15 CALL_FUNCTION 1
18 LOAD_ATTR 1 (get)
21 LOAD_FAST 0 (a)
24 CALL_FUNCTION 1
27 LOAD_CONST 0 (None)
30 DUP_TOP
31 EXEC_STMT
3 32 LOAD_NAME 3 (retval)
35 RETURN_VALUE
>>> dis.dis(caseFunction.doIt)
3 0 LOAD_FAST 0 (self)
3 LOAD_ATTR 1 (caseDict)
6 LOAD_ATTR 2 (get)
9 LOAD_FAST 1 (a)
12 CALL_FUNCTION 1
15 LOAD_CONST 0 (None)
18 DUP_TOP
19 EXEC_STMT
4 20 LOAD_NAME 4 (retval)
23 RETURN_VALUE
>>>
Take a look at what happens before the 'get' attribute is loaded in
each case. In case 3, you've simply created a dictionary literal,
which is a very fast operation under Python. In case 4, you've created
a dictionary literal, then you call the dict() function. The dict
function will create a dictionary from the supplied dictionary, and
return the shallow copy.
Case 3 is slower, but the Python developers have worked to make
dictionary creation and look-up very fast. Did you use the timeit
module to test your functions?
--Jason
More information about the Python-list
mailing list