13

I have the following snippet:

a, b = 1, 2
params = ['a', 'b']
res = {p: vars()[p] for p in params}

Which gives me KeyError: 'a' whereas the following code works fine:

a, b = 1, 2
params = ['a', 'b']
res = {}
for p in params:
    res[p] = vars()[p] 

What's the difference here?

Ulysses
  • 357
  • 1
  • 13

4 Answers4

10

vars() without any argument acts like locals() and since a dictionary comprehension has its own scope it has no variable named a or b.

You can use eval() here. Without any argument it will execute in LEGB manner, or specify globals() dict explicitly to eval:

>>> res = {p: eval(p) for p in params}
>>> res
{'a': 1, 'b': 2}

But then again the correct way will be to create a dictionary from the start if you want to access variables using their names.

Community
  • 1
  • 1
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • What would be the way to fix it if I don't want to go through a multi-line loop? – Ulysses Jun 24 '15 at 07:20
  • 3
    The best way to fix it is to avoid messing with `vars()`/`locals()` altogether, because they will give you issues such as this one. – TigerhawkT3 Jun 24 '15 at 07:22
  • 1
    I only use `vars()` with `format`: it's just too attractive to write `'foo {bar} baz {spam}'.format(**vars())` rather than `.format(bar=bar,spam=spam)`. But it does go wrong inside a comprehension. @TigerhawkT3 what would you recommend as the Pythonic alternative to my `.format(**vars())` code? – gerrit Jun 24 '15 at 11:26
  • How about `'foo {} baz {}'.format(bar, spam)`? The typing effort is O(n) instead of O(1) (a comma to separate each element instead of an unpacked dictionary) with an intersection at n=8, but it works in a comprehension. – TigerhawkT3 Jun 24 '15 at 18:58
5

Its because of that in your code vars returns a dictionary contains the local variables.actually based on documentation :

Without an argument, vars() acts like locals() .

see the following example :

>>> def a():
...   print vars()
... 
>>> a()
{}

As you can see we have not any local variable within function a so vars returns an empty dictionary.

And in your case as a more pythonic way you can create a dictionary of your objects :

d={'a':1,'b': 2,'params' : ['a', 'b']}
example_list : ['a', 'b']
res = {p: d[p] for p in example_list}
Mazdak
  • 105,000
  • 18
  • 159
  • 188
3

It seems Python makes a closure in dictionary comprehension (say, dictcomp)

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <dictcomp>
KeyError: 'a'
Wonjung Kim
  • 1,873
  • 15
  • 18
3

Use vars() in the for loop performs as the second code you gave.

# come out with {'a': 1, 'b': 2}
res = {p: v for p, v in vars().iteritems() if p in params}
res = {'a': vars()['a'], 'b': vars()['b']}

We can figure out the new locals/vars within for loop in dict comprehension:

>>> {i: list(vars().viewkeys()) if i == 0 else list(vars().viewvalues()) for i in range(2)}
{0: ['i', '.0'], 1: [1, <listiterator at 0x6fffe458550>]}
>>> {list(vars().viewkeys())[i]: list(vars().viewvalues())[i] for i in range(2)}
{'.0': <listiterator at 0x6fffe458710>, 'i': 0}
LittleQ
  • 1,860
  • 1
  • 12
  • 14