0

I have a problem with the namespaces defined in the nested functions. Consider the following code:

def fun1():
    CHARS = ['C','H','A','R','S']
    
    def fun2():
        C = 1
        H = 2
        A = 3
        R = 4
        S = 5

        # just for warp-up, to ensure oneliners indeed work
        a_list = [c for c in CHARS]
        
        # the following structure works: makes [1,2,3,4,5]
        eval_list = []
        for c in CHARS:
            eval_list.append(eval(c))
        
        # this doesn't work. why?
        oneliner_eval_list = [eval(c) for c in CHARS] # NameError: name 'C' is not defined
    
        print('func2 ran without problem')

    fun2()
    
    print('func1 ran without problem')
fun1()

When I run this the oneliner for loop cannot find the CHARS in the local namespace and thus goes one level up and finds CHARS in the enclosed namespace (of fun1). As it does so, however, it forgets its own namespace and cannot find C.

  • Why is that?
  • Why the multi-line for loop doesn't have such a problem?

I use Python3.

arash
  • 161
  • 13

2 Answers2

1

you need to make them as global variables. please find the updated code here,

def fun1():
    CHARS = ['C', 'H', 'A', 'R', 'S']

    def fun2():
        global C, H, A,R,S # here is the update 
        C = 1
        H = 2
        A = 3
        R = 4
        S = 5

        # just for warp-up, to ensure oneliners indeed work
        a_list = [c for c in CHARS]

        # the following structure works: makes [1,2,3,4,5]
        eval_list = []
        for c in CHARS:
            eval_list.append(eval(c))

        # this doesn't work. why?
        oneliner_eval_list = [eval(c) for c in CHARS]  # NameError: name 'C' is not defined

        print('func2 ran without problem')

    fun2()

    print('func1 ran without problem')


fun1()

This will print

func2 ran without problem
func1 ran without problem
Tamil Selvan
  • 1,600
  • 1
  • 9
  • 25
  • Thanks. Would you please also elaborate on why the other one works without a global declaration? – arash Dec 08 '21 at 09:54
1

The reason is because the list comp sets up it's own local scope when it runs. So if you have something like.

END = 1
def func():
    x = 0
    lst = [locals() for i in range(END)]
    print(lst)

func()

You get

[{'.0': <range_iterator object at 0x000001ACC5BF79D0>, 'i': 0}]

When you call eval without supplying a value to use as the locals it will use the locals in the current scope

eval(source, globals=None, locals=None, /)

Which in this case does not have your C,H,A,R,S So you can solve it by doing.

def fun1():
    CHARS = ['C','H','A','R','S']
    
    def fun2():
        C = 1
        H = 2
        A = 3
        R = 4
        S = 5

        local_vars = locals()
        global_vars = globals()

        # just for warp-up, to ensure oneliners indeed work
        a_list = [c for c in CHARS]
        
        # the following structure works: makes [1,2,3,4,5]
        eval_list = []
        for c in CHARS:
            eval_list.append(eval(c))
        
        # this doesn't work. why?
        oneliner_eval_list = [eval(c, global_vars, local_vars) for c in CHARS]
    
        print('func2 ran without problem')

    fun2()
    
    print('func1 ran without problem')
fun1()
Steven Summers
  • 5,079
  • 2
  • 20
  • 31