0

EDIT: This question is NOT ANSWERED BY THE LINKS ABOVE that a mod added. As I said before in a comment, Python 3 brought changes, and the examples given in those answers were for Python 2. If I compile those in my Python 3 environment, I get the same error as here.

Consider

str = "x = [113, 223]"
exec(str)
print(x[0]) #113

This works perfectly. But if I want this code to be executed in a function, it returns an error NameError: name 'x' is not defined. Here's a minimal working example:

def some_code():
    str = "x = [1, 2]"
    exec(str)
    print(x)

some_code()

What is going on here?

I need a solution to

  • use exec inside the function (because ultimately its a tkinter function -see the first edit history of this question- and I'm reading this from a file that should be executed

  • I would like to easily be able to refer to x, because I will need to do that in a lot of places. So using a long line of code to refer to x will be cumbersome.

l7ll7
  • 1,309
  • 1
  • 10
  • 20
  • This has _nothing_ to do with tkinter. It's a python issue. – Nae Mar 11 '18 at 15:00
  • 1
    @Nae You are right about not being tkinters fault. I edited my question. Though why it doesn't work is still open, as in that question what is claimed to be working is almost the same as my code, which does not work. Perhaps CPython has changed so much in the meantime, that now this throws an error? – l7ll7 Mar 11 '18 at 15:20
  • 1
    @nicht the second link _is_ python 3 – anthony sottile Mar 11 '18 at 20:32
  • Just wanna mention that the first link should be removed, because the solution there is not applicable anymore for Python 3. – l7ll7 Mar 18 '18 at 07:39

1 Answers1

1

Naively moving the relevant code to first level scope solved it.

string = "x = [113, 223]"
exec(string)

def some_code():
    print(x[0]) #113

Another approach: I started toying around with exec() more and from what I can see exec() writes its results (in this case x) into the locals() and globals() builtin dictionaries. Therefore, the following is another solution to the problem, but it seems rather hacky:

def some_code():
    string = "x = [113, 223]"
    exec(string)
    print(locals()['x'][0]) #113

some_code()

In the same manner, you can define your own dictionary for use instead of locals() where exec() stores x, which in my opinion is much cleaner:

exec_results = {}

def some_code():
    string = "x = [113, 223]"
    exec(string, None, exec_results)
    print(exec_results['x'][0]) #113

some_code()

I highly discourage using exec() for really simple cases such as this, but if you wish to use it in the future, I highly suggest checking out other threads on the same topic that were created prior to this question, such as running-exec-inside-function and globals and locals in python exec(). Check out the Python docs on exec() to read more about exec() as well.

Xay
  • 276
  • 2
  • 10
  • Hm, I will meditate on this answer. I'm not sure if I can actually throw the first two lines out of the function, as you did on the top, because this problem arose in the context of a tkinter function, where I'm reading stuff from a while, and the read-in stuff is what I want to execute (see my edit), so it will have to be in function... – l7ll7 Mar 11 '18 at 16:12
  • There is also a second issue, namely in my actual project I will want to use `x` very very often. Having to use `locales` each time would be very cumbersome. I'm really surprised that Python maks this so difficult. – l7ll7 Mar 11 '18 at 16:16
  • (I added these requirements to my question, as they are essential for me) – l7ll7 Mar 11 '18 at 16:24
  • The main issue with `exec()` here is definitely that it does not seem to be working on a local scope (i.e. inside a function as seen in your question). Calling `locals` or `globals` each time you want to access `x` is indeed very cumbersome. The only "clean" solution I can think of for your problem is using your own dictionary to store the results of `exec()` (in your case `x`) for use across an entire class and/or several functions. The 3rd alternative I provided in my answer makes use of this because I, too, thought that using `locals()` is overdoing it for accessing just one variable. – Xay Mar 11 '18 at 16:32
  • However, I do not know if there are any downsides of using said custom dictionary for storing the results of `exec()` (especially in tkinter). I simply lack the experience in this regard, my apologies.. – Xay Mar 11 '18 at 16:33
  • Thanks for the input. Just a point to clarify: Isn't using `exec_results['x']` in the 3rd solution just as cumbersome as using `locals`? I unfortunately also am not knowledgeable enough to clearly grasp the difference between the two. – l7ll7 Mar 11 '18 at 17:21
  • Objectively, there is no difference in `locals()['x']` or `exec_results['x']`. Both elements are essentially dictionaries. In terms of the amount of times you will access the dictionary (i.e. when changing 'x' or similar), they will both be on equal terms. The only difference is that `locals()` is also a builtin object used by Python which possesses values (depending on scope/namespace) that should not be modified (see note: [doc](https://docs.python.org/3/library/functions.html#locals)). Thus I think using your own `exec_result` dictionary to save results from `exec()` to may be safer. – Xay Mar 11 '18 at 18:32
  • Also, if by 'cumbersome' you mean the use of dictionaries to access values coming from `exec()` inside functions is strange in general, I have to say that I do not know if it is possible to entirely avoid using dictionaries to access results (i.e. `x`) from `exec()` in your specific case (from different scopes). I would consider this whole dictionary thing as a workaround even rather than an actual 'intended' solution, because the behavior `exec()` is showing here seems indeed strange. – Xay Mar 11 '18 at 18:49