During a class definition, a class variable defined as a dictionary is used in the construction of a second dictionary class variable, a subset pared down from the first, like this:
class C(object):
ALL_ITEMS = dict(a='A', b='B', c='C', d='D', e='E')
SUBSET_X = {k: v for k, v in ALL_ITEMS.items() if k in ('a', 'b', 'd')} # (this works)
SUBSET_Y = {k: ALL_ITEMS[k] for k in ('a', 'b', 'd')} # (this fails)
Pretty simple stuff, but the net effect of executing this code is quite surprising to me. My first approach was the code on line 4, but I had to resort to the solution on line 3 instead. There is something subtle about dictionary comprehension scoping rules that I'm clearly failing to grasp.
Specifically, the error raised in the failing case is:
File "goofy.py", line 4, in <dictcomp>
SUBSET_Y = {k: ALL_ITEMS.get(k) for k in ('a', 'b', 'd')}
NameError: name 'ALL_ITEMS' is not defined
The nature of this error is baffling to me for a few different reasons:
- The assignment to
SUBSET_Y
is a well-formed dictionary comprehension, and references a symbol which should be in-scope and accessible. - In the succeeding case (the assignment to
SUBSET_X
), which is also a dictionary comprehension, the symbolALL_ITEMS
is perfectly well-defined and accessible. Thus, the fact that the raised exception is aNameError
in the failing case seems manifestly wrong. (Or misleading, at best.) - Why would the scoping rules differ for
items()
vs.__getitem__
orget()
? (The same exception occurs replacingALL_ITEMS[k]
withALL_ITEMS.get(k)
in the failure case.)
(Even as a Python developer for over a decade, I've never run into this failure before, which either means I've been lucky or have lived a sheltered existence :^)
The same failure occurs in various 3.6.x CPython versions as well as 2.7.x versions.
EDIT: No, this is not a duplicate of a previous question. That pertained to list comprehensions, and even if one were to project the same explanation to dictionary comprehensions, it doesn't explain the difference between the two cases I cited. And also, it is not a Python 3-only phenomenon.