Nested Dictionary Comprehension Useless?

Yesterday I was stumped by a question about nested dictionary comprehension. I have to admit that I don't use dictionary comprehension often, especially nested ones. My preferred way is
 
dict(zip(list1, list2))


Anyway, I felt the need to go over dictionary comprehension, and I found something really interesting about nested dictionary comprehension -- it probably doesn't have any practical applications.

>>> def my_list_comp():
...     a = range(3)
...     b = range(3, 6)
...     return [(x, y) for x in a for y in b]
... 
>>> def my_dict_comp():
...     a = range(3)
...     b = range(3, 6)
...     return {x: y for x in a for y in b}
... 
>>> my_list_comp()
[(0, 3), (0, 4), (0, 5), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5)]
>>> my_dict_comp()
{0: 5, 1: 5, 2: 5}
>>> 
 
The behavior of list comprehension is straightforward, but what about that of dictionary comprehension? As it stands right now, what would be the legit application of it?

Examing the byte code:
>>> dis.dis(my_list_comp)
  2           0 LOAD_GLOBAL              0 (range)
              3 LOAD_CONST               1 (3)
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 STORE_FAST               0 (a)

  3          12 LOAD_GLOBAL              0 (range)
             15 LOAD_CONST               1 (3)
             18 LOAD_CONST               2 (6)
             21 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             24 STORE_DEREF              0 (b)

  4          27 LOAD_CLOSURE             0 (b)
             30 BUILD_TUPLE              1
             33 LOAD_CONST               3 (<code object <listcomp> at 0xb71a7660, file "<stdin>", line 4>)
             36 LOAD_CONST               4 ('my_list_comp.<locals>.<listcomp>')
             39 MAKE_CLOSURE             0
             42 LOAD_FAST                0 (a)
             45 GET_ITER
             46 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             49 RETURN_VALUE
 

          
          
          
>>> dis.dis(my_dict_comp)
  2           0 LOAD_GLOBAL              0 (range)
              3 LOAD_CONST               1 (3)
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 STORE_FAST               0 (a)

  3          12 LOAD_GLOBAL              0 (range)
             15 LOAD_CONST               1 (3)
             18 LOAD_CONST               2 (6)
             21 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             24 STORE_DEREF              0 (b)

  4          27 LOAD_CLOSURE             0 (b)
             30 BUILD_TUPLE              1
             33 LOAD_CONST               3 (<code object <dictcomp> at 0xb71a76b0, file "<stdin>", line 4>)
             36 LOAD_CONST               4 ('my_dict_comp.<locals>.<dictcomp>')
             39 MAKE_CLOSURE             0
             42 LOAD_FAST                0 (a)
             45 GET_ITER
             46 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             49 RETURN_VALUE
 
The bytecode look exactly the same except for code object being used -- listcomp vs dictcomp. Without the capability of printing embedded code object, my GUESS is that they behaves quite similarly internally with the only difference being LIST_APPEND vs STORE_MAP. 

list comprehension
>>> a = [0, 1, 2]
>>> b = [3, 4, 5]
>>> gen = ((x, y) for x in a for y in b)
>>> for x, y in gen:
    LIST_APPEND((x, y))
 
dictionary comphrension
>>> a = [0, 1, 2]
>>> b = [3, 4, 5]
>>> gen = ((x, y) for x in a for y in b)
>>> for x, y in gen:
    STORE_MAP((x, y)) 

If it's the case, the generation of (0, 3), (0, 4) is only to be overwritten by (0, 5), so my_dict_comp() behaves like dict(my_list_comp()).

The conclusion is that nested dictionary comprehension is not very useful in practice, and should be avoided.

It reminds me of something in C++ -- unordered_map's insert function can take a hint, which is a pretty much useless hint due to the "unorderedness" nature of unordered containers. However it does serve a purpose -- interface capabilities: one can trade unordered map for map without breaking any code, and vice versa.

To dig deeper, I will have to debug the bytecode to see how it executes exactly. I came across a tool that is a Python byte code virtual machine written in Python
https://github.com/nedbat/byterun
As soon as I get it up and running, I will revisit and update the post.

Another way would be to wait for Python 3.5, which introduce the support of printing embedded code objects.
https://bugs.python.org/issue11822




 
 

Popular posts from this blog

LeetCode 68 Text Justification (C++, Python)

How Django Works (4) URL Resolution

Python Class Method and Class Only Method