Is this evil eval or what?

Hi all,

I encountered some unexpected behaviour and would like to know if it’s just me. It concerns the usage of eval. In the following code I create a dict of nm → lambda’s. The only difference between the classes is the way the lambda is generated in init. In short: eval(‘lambda: ‘) vs lambda: eval(’’):

class G1:
    def __init__(self, d_config):
        self.kwargs = {nm: eval(f'lambda: {s_fn}') for nm, s_fn in d_config.items()}
            
    def __call__(self):
        return {nm: fn() for nm, fn in self.kwargs.items()}

class G2:
    def __init__(self, d_config):
        self.kwargs = {nm: lambda: eval(f'{s_fn}') for nm, s_fn in d_config.items()}
            
    def __call__(self):
        return {nm: fn() for nm, fn in self.kwargs.items()}


config = dict(
        pi1='"********"',
        pi2='"gen_pi12"',
)
for G in [G1, G2]:
    gen = G(config)
    print(gen())

The results are surprisingly different:

{'pi1': '********', 'pi2': 'gen_pi12'}
{'pi1': 'gen_pi12', 'pi2': 'gen_pi12'}

I expected that somehow the lambda’s where the same, but they are not (at least they have different id’s). Although I solved the problem (I started with G2), I don’t understand this.

My question: can anybody explain what is happening here? If only to avoid this kind of problems in the future?

Nothing to do with eval() (which is still evil, nonetheless):

You’re creating multiple lambda closures, each of which captures nonlocal s_fn by name. If you want to capture by value, you can do the old

lamdba s_fn=s_fn: ...

trick.

Thanks for the reply, Thomas. Nice trick.