Python3: exec in global vs local scope

Today, I encountered a seemingly strange problem. When using exec to execute a command that you do not want to implement hard into your code, everything works fine and as expected in a global scope. That means, if you execute the exec command in the global part of a script or hack it into your favourite CLI, the following code behaves as expected.

>>> import pandas as pd
>>> df = pd.DataFrame({"a": [1,2,3,4], "b":[4,3,2,1]})
>>> print(df)
   a  b
0  1  4
1  2  3
2  3  2
3  4  1
>>> exec('df=df[df["b"] > 2]')
>>> print(df)
   a  b
0  1  4
1  2  3

The exec command evaluates the filter expression and assigns its value to df again. So far, so good.

However, if you now move the exec command to a local scope, i.e. inside a function, it suddenly ceases to yield the expected result.

>>> import pandas as pd
>>> def lol(df):
...   exec('df=df[df["b"] > 2]')
...   return df
>>> df = pd.DataFrame({"a": [1,2,3,4], "b":[4,3,2,1]})
>>> print(df)
   a  b
0  1  4
1  2  3
2  3  2
3  4  1
>>> print(lol(df))
   a  b
0  1  4
1  2  3
2  3  2
3  4  1

Obviously, I cannot overwrite the df variable here. On the other hand, it is however perfectly possible to create new variables in the local scope:

>>> def some_method():
...     print(locals())
...     exec("never_before_seen_variable = 5")
...     print(locals())
...
>>> some_method()
{}
{'never_before_seen_variable': 5}

As a side note: It generally is not a good idea to use exec anywhere in your code as it can and will introduce unforeseen and unwanted side effects. For example, the code above will not fail since the variable assignment is a perfectly valid expression. However, it does not have the desired effect (to be fair, it does not have any effect). Additionally, exec can execute arbitrary code and whole code blocks. If you don't know exactly who will be using your code (and as a consequence, will have access to your exec statement), better leave it out.