Dict comprehension produces seemingly unwarranted NameError
Question: I'm using `brian2` to run neural-network simulations. In order to record data
during each simulation, I'm creating several instantiations of `brian2`'s
`SpikeMonitor` class. I want to store these monitors in a dict, created using
a dict comprehension.

As a test, I execute the following in an interactive session:

    
    
    In [1]: import brian2
    
    In [2]: pe_mt = brian2.PoissonGroup(1, 100 * brian2.Hz)
    
    In [3]: record_pops = ['pe_mt']
    
    In [4]: {'mon_' + pop: brian2.SpikeMonitor(eval(pop)) for pop in record_pops}
    Out[4]: {'mon_pe_mt': <SpikeMonitor, recording spikemonitor>}
    

Everything looks great. But now when I move this code into the following
function

    
    
    def test_record():
        pe_mt = brian2.PoissonGroup(1, 100 * brian2.Hz)
        record_pops = ['pe_mt']
        return {'mon_' + pop: brian2.SpikeMonitor(eval(pop)) for pop in
                record_pops}
    

and call it, I get the following error

    
    
    In [9]: tests.test_record()
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-9-4d3d585b2c97> in <module>()
    ----> 1 tests.test_record()
    
    /home/daniel/Science/dopa_net/brian/ardid/tests.py in test_record()
         61     record_pops = ['pe_mt']
         62     return {'mon_' + pop: brian2.SpikeMonitor(eval(pop)) for pop in
    ---> 63                 record_pops}
         64     # DEBUG ###################
         65     #monitors = utils.record(['pe_mt'], 'spikes', None, None, pe_mt, None, None)
    
    /home/daniel/Science/dopa_net/brian/ardid/tests.py in <dictcomp>((pop,))
         60     # DEBUG ###################
         61     record_pops = ['pe_mt']
    ---> 62     return {'mon_' + pop: brian2.SpikeMonitor(eval(pop)) for pop in
         63                 record_pops}
         64     # DEBUG ###################
    
    /home/daniel/Science/dopa_net/brian/ardid/tests.py in <module>()
    
    NameError: name 'pe_mt' is not defined
    

What's going on here? 'pe_mt' _is_ defined within the function.

Note that if I change the dict comprehension to a list comprehension, as in

    
    
    return [brian2.SpikeMonitor(eval(pop)) for pop in record_pops]
    

no error is raised! I get a list of `SpikeMonitor` objects, defined
appropriately.

An answer that has now been erased suggested that I use `locals()[pop]`
instead of `eval(pop)`. Note that this raises an equivalent error:

    
    
    In [20]: tests.test_record()
    ---------------------------------------------------------------------------
    KeyError                                  Traceback (most recent call last)
    <ipython-input-20-4d3d585b2c97> in <module>()
    ----> 1 tests.test_record()
    
    /home/daniel/Science/dopa_net/brian/ardid/tests.py in test_record()
         61     record_pops = ['pe_mt']
         62     return {'mon_' + pop: brian2.SpikeMonitor(locals()[pop]) for pop in
    ---> 63                 record_pops}
         64     # DEBUG ###################
         65     #monitors = utils.record(['pe_mt'], 'spikes', None, None, pe_mt, None, None)
    
    /home/daniel/Science/dopa_net/brian/ardid/tests.py in <dictcomp>((pop,))
         60     # DEBUG ###################
         61     record_pops = ['pe_mt']
    ---> 62     return {'mon_' + pop: brian2.SpikeMonitor(locals()[pop]) for pop in
         63                 record_pops}
         64     # DEBUG ###################
    
    KeyError: 'pe_mt'
    


Answer: One: Forget `eval`, because it can cause unexpected things to happen if the
string passed to it is an expression or a function call, rather than an
identifier. If you _really_ need to get a local variable by name you can do it
cleanly using `locals()[name]`.

Docs: [`locals`](https://docs.python.org/2/library/functions.html#locals)

* * *

Two: All comprehensions and generator expressions (except list comprehensions
in python 2.x) [have their own
namespace](http://stackoverflow.com/questions/22485399/python-dictionary-
comprehension-gives-keyerror), so `locals()` inside the comprehension will
refer to that one - the one that doesn't have your variable. Same goes for
`eval` that [captures your local variables by
default](https://docs.python.org/2/library/functions.html#eval):

> If the locals dictionary is omitted it defaults to the globals dictionary.
> If both dictionaries are omitted, the expression is executed in the
> environment where eval() is called.

You can work that around by getting them earlier:

    
    
    def test_record():
        pe_mt = brian2.PoissonGroup(1, 100 * brian2.Hz)
        record_pops = ['pe_mt']
        groups = locals()    
        return {'mon_' + pop: brian2.SpikeMonitor(eval(pop, globals(), groups)) for pop in record_pops}
        # or better
        return {'mon_' + pop: brian2.SpikeMonitor(groups[pop]) for pop in record_pops}
    

Or more conventionally, without `locals`:

    
    
    def test_record():
        groups = {
            "pe_mt": brian2.PoissonGroup(1, 100 * brian2.Hz),
        }
        return {'mon_' + key: brian2.SpikeMonitor(value) for key, value in groups.iteritems()}
    

