1

I have a module that defines a class and a function:

#barbaz.py
class Bar:
    pass

def baz():
    return "hello"

In another module, I have a function with an eval statement:

#foo.py (version 1)
def foo(baz: callable):
    bazstr: str = baz.__name__
    print(bazstr)
    try:
        f=eval(f'{bazstr}()')
        print(f'result: {f}')
    except Exception as e:
        print(f"eval error: {e}")

And finally, I have a test in another module:

#test.py
from evalf import foo
from barbaz import baz, Bar

#This works...
foo(baz)
#This doesn't
foo(Bar)

The baz works, but the Bar doesn't. I get the output:

baz
result: hello
Bar
eval error: name 'Bar' is not defined

It seems that eval cannot use classes that have been imported from a module, unless it is imported directly into the same module as the foo() function. i.e, this works:

#foo.py (version 2)
from barbaz import Bar

def foo(baz: callable):
    bazstr: str = baz.__name__
    print(bazstr)
    try:
        f=eval(f'{bazstr}()')
        print(f'result: {f}')
    except Exception as e:
        print(f"eval error: {e}")

foo(Bar)

Why does version 2 of foo.py work, and version 1 thrown the shown error?

How can I get around this, and use an imported class in an eval statement that lives in it's own module?

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271

2 Answers2

2

foo.py doesn't work as expected because it doesn't have either the class or the function in the global namespace (which is where eval looks for names not found in local scope). It works on the function because, by sheer coincidence, the outside function (baz) and name of the parameter (baz) are the same. It didn't see the def baz function at all, it saw the parameter you named (coincidentally) baz.

The solution is to just use the name of the parameter you received unconditionally; if you must use eval, just hardcode the name of the callable in it to baz, because that's always what it's called in your local scope. You probably shouldn't use eval (in your code, f = baz() would do the job just fine), but if you must, that's the solution.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • >`foo.py doesn't work as expected because it doesn't have either the class or the function in the global namespace`. Is it possible to add a function or class to the global namespace? Alternatively, can I manually pass it into the `eval` namespace with the seccond argument, i.e something like `eval("code", {'Baz' : Baz})` – Craig Hickman Dec 07 '20 at 19:01
  • @CraigHickman: You definitely don't want `foo` polluting its own global namespace on every call just to do this. If `eval` is an absolute *must*, you can explicitly pass `dict`s for the `globals` and `locals` arguments to `eval` that will let you make whatever names you like available in the scope of the `eval` (make sure you include whatever stuff from actual globals/locals you rely on too). The insistence on `eval` [smells of an XY problem](https://meta.stackexchange.com/q/66377/322040), given the lack of need for it in the code provided, but yes, that's how you'd do it if you needed to. – ShadowRanger Dec 07 '20 at 19:15
0

The problem is that you have baz in your foo function, and no Bar.

def foo(baz: callable):
    bazstr: str = baz.__name__
    print(bazstr)
    try:
        f = eval(f'{bazstr}()')
        print(f'result: {f}')
    except Exception as e:
        print(f"eval error: {e}")

Using eval is dangerous, you can simply add () beside the callable parameter. I placed the code from your three files into one, as the separate files doesn't change what it does:

class Bar:
    pass

def baz():
    return "hello"

def foo(baz: callable):
    try:
        f = baz()
        print(f'result: {f}')
    except Exception as e:
        print("Not callable.")

foo(baz)
foo(Bar)

Output:

result: hello
result: <__main__.Bar object at 0x0000020EA3496E48>
Red
  • 26,798
  • 7
  • 36
  • 58
  • >`The problem is that you have baz in your foo function, and no Bar.` That's just the name of the function parameter. I've eddited it to make it clearer – Craig Hickman Dec 07 '20 at 18:17
  • >`Using eval is dangerous`. I know, but in this case I really need it. The example in the question is just a minimal representation of the problem. – Craig Hickman Dec 07 '20 at 18:18
  • @CraigHickman: No, it's not "just the name of the parameter"; it's critical to why one call worked and the other didn't. *Neither* version of your call works when you change the parameter name to `bar` (because now neither `baz` nor `Bar` is visible in either local or global scope). Your edited code no longer reproduces the behavior you claim to see (and it's still got a typo; you claim it's `foo.py`, but you import from `evalf`). – ShadowRanger Dec 07 '20 at 18:30