0

So I have two simple Python modules: test1.py:

def main():
    def fmt(*args):
        r = ""
        for x in args:
            r += eval("f'"+x+"'")
        return r
    a = "alpha"
    b = "beta"
    d = "delta"
    msg = "Hello {d} one, this is {a} {b}"
    print(msg)
    print(fmt(msg))

if __name__ == "__main__":
    main()

And test2.py:

def fmt(*args):
    r = ""
    for x in args:
        r += eval("f'"+x+"'")
    return r
a = "alpha"
b = "beta"
d = "delta"
msg = "Hello {d} one, this is {a} {b}"
print(msg)
print(fmt(msg))

So, basically identical, except the first has the code wrapped in a function, while the second does not. The first, when executed, gives the following output:

Hello {d} one, this is {a} {b}
Traceback (most recent call last):
  File "test1.py", line 16, in <module>
    main()
  File "test1.py", line 13, in main
    print(fmt(msg))
  File "test1.py", line 6, in fmt
    r += eval("f'"+x+"'")
  File "<string>", line 1, in <module>
NameError: name 'd' is not defined

The second one does as I expected:

Hello {d} one, this is {a} {b}
Hello delta one, this is alpha beta

So the first one doesn't think it knows about the d name. But, if I insert print(dir(d)) into test1.py right after the def fmt(*args): statement, now it finds d, and then doesn't know about the next name, a:

Hello {d} one, this is {a} {b}
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
Traceback (most recent call last):
  File "test1.py", line 17, in <module>
    main()
  File "test1.py", line 14, in main
    print(fmt(msg))
  File "test1.py", line 7, in fmt
    r += eval("f'"+x+"'")
  File "<string>", line 1, in <module>
NameError: name 'a' is not defined

I'm sure someone understands what is happening, but I'm at a loss.

cteljr
  • 25
  • 4

1 Answers1

0

It is, indeed, a matter of identifiers' scope. In your test1.py, the identifiers a, b and d are not known in the scope of the function fmt, because they are neither global (which is what makes them known in your test2.py), nor local. You can work around this by using the nonlocal statement inside fmt:

def main():
    def fmt(*args):
        nonlocal a, b, d # <----------- only added line of code
        r = ""
        for x in args:
            r += eval("f'"+x+"'")
        return r
    a = "alpha"
    b = "beta"
    d = "delta"
    msg = "Hello {d} one, this is {a} {b}"
    print(msg)
    print(fmt(msg))

if __name__ == "__main__":
    main()
Amitai Irron
  • 1,973
  • 1
  • 12
  • 14
  • Thanks! @Dallan's comment also pointed in that same direction. Still, an ugly hack (IMO), because now I have to specify all the possible non-local variables that might go into the function, when all I want is for it to take the string and apply the formatting. Obviously, my application is more complex than this (much simplified) example. Thanks, again! – cteljr Jun 04 '20 at 21:19