Asked  6 Months ago    Answers:  5   Viewed   30 times

I thought this would print 3, but it prints 1:

def f():
    a = 1
    exec("a = 3")
    print(a)

 Answers

44

This issue is somewhat discussed in the Python3 bug list. Ultimately, to get this behavior, you need to do:

def foo():
    ldict = {}
    exec("a=3",globals(),ldict)
    a = ldict['a']
    print(a)

And if you check the Python3 documentation on exec, you'll see the following note:

The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.

That means that one-argument exec can't safely perform any operations that would bind local variables, including variable assignment, imports, function definitions, class definitions, etc. It can assign to globals if it uses a global declaration, but not locals.

Referring back to a specific message on the bug report, Georg Brandl says:

To modify the locals of a function on the fly is not possible without several consequences: normally, function locals are not stored in a dictionary, but an array, whose indices are determined at compile time from the known locales. This collides at least with new locals added by exec. The old exec statement circumvented this, because the compiler knew that if an exec without globals/locals args occurred in a function, that namespace would be "unoptimized", i.e. not using the locals array. Since exec() is now a normal function, the compiler does not know what "exec" may be bound to, and therefore can not treat is specially.

Emphasis is mine.

So the gist of it is that Python3 can better optimize the use of local variables by not allowing this behavior by default.

And for the sake of completeness, as mentioned in the comments above, this does work as expected in Python 2.X:

Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) 
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
...     a = 1
...     exec "a=3"
...     print a
... 
>>> f()
3
Tuesday, June 1, 2021
 
Yarin
answered 6 Months ago
41

exec will always return None. From the documentation:

exec(object[, globals[, locals]])

This function supports dynamic execution of Python code. object must be either a string or a code object. If it is a string, the string is parsed as a suite of Python statements which is then executed (unless a syntax error occurs). [1] If it is a code object, it is simply executed. In all cases, the code that’s executed is expected to be valid as file input (see the section “File input” in the Reference Manual). Be aware that the return and yield statements may not be used outside of function definitions even within the context of code passed to the exec() function. The return value is None.

This is a rather strange request. But you can capture the output like this:

>>> s = """The number {% (c) print(x) %} is a random number between 1 and 6
... inclusive. If we multiply it by 2, we get {% (d) print(2*x) %}.
...
... What's interesting is that the statements may appear out of order in the
... document. {% (a) import random %} Thus I might generate the random
... number in a location in the document well after referencing it.
... {% (b) x = random.randint(1,6) %}"""
>>> import re
>>> stmts = re.findall(r'{%s*((w*))s*(.*)%}',s)
>>> stmts
[('c', 'print(x) '), ('d', 'print(2*x) '), ('a', 'import random '), ('b', 'x = random.randint(1,6) ')]

Now, you have to redirect output to some stream which you can manipulate later:

>>> import io
>>> import sys
>>> stream = io.StringIO()
>>> stdout = sys.stdout # this keeps stdout so we can set it back
>>> sys.stdout = stream
>>> for _, statement in sorted(stmts):
...     exec(statement)
...
>>> sys.stdout = stdout # remember to reset stdout!

And now, you can get the values that were printed:

>>> stream.getvalue()
'5n10n'
>>> stream.getvalue().split()
['5', '10']

Although, I think an easier way is to pass a namespace to the dict:

>>> namespace = {}
>>> for _, statement in sorted(stmts):
...     exec(statement, namespace)
...
5
10
>>> namespace.keys()
dict_keys(['__builtins__', 'random', 'x'])

The namespace will get loaded with the normal __builtins__ unless you provide one yourself. So to get every name created in your executed code, you can find the difference between the namspace.keys dictview and a set contiaining the string "__builtins__"

>>> namespace.keys()
dict_keys(['__builtins__', 'random', 'x'])
>>> vals = namespace.keys() - {'__builtins__'}
>>> vals
{'random', 'x'}
>>> for val in vals:
...    print(namespace[val])
...
<module 'random' from '/Users/juan/anaconda3/lib/python3.5/random.py'>
5
>>>

Although, if you are on python 3.4 >= it's a lot easier to redirect stdout to some stream:

>>> import contextlib
>>> stream = io.StringIO()
>>> with contextlib.redirect_stdout(stream):
...     for _, statement in stmts:
...         exec(statement)
...
>>> stream.getvalue()
'5n10n'
>>> stream.getvalue().split()
['5', '10']
Friday, August 6, 2021
 
RealHowTo
answered 4 Months ago
93

Instead of using exec with function names, just keep the function objects in the list:

fn_lst = [has_at, has_num, ...]

and perform the call directly:

def abc(xyz):
    for i in fn_lst:
        temp= i(xyz)
        print(temp)
Sunday, August 22, 2021
 
miltonb
answered 4 Months ago
50

fork creates a new process, it is called once by the parent but returns twice in the parent and in the child.

In the child process the call execlp executes the specified command ls.

This replaces the child process with the new program file (ls program file) which means following.

When a process calls the execlp or one of the other 7 exec functions, that process is completely replaced by the new program, and the new program starts executing at its main function.

The process ID does not change across an exec, because a new process is not created. exec merely replaces the current process's text, data, heap, and stack segments with a brand new program from disk.

The combination of fork followed by exec is called spawning a new process on some operating systems.

Hopefully it was more or less clear. Let me know if you have more questions.

Sunday, September 12, 2021
 
ChriskOlson
answered 3 Months ago
10

You have this:

@contextmanager
def versioned(file_path, mode):
    # some setup code
    yield versioned_file
    # some teardown code

Your basic problem of course is that what you yield from the context manager comes out of the with statement via as, but is not the object returned by your function. You want a function that returns something that behaves like the object open() returns. That is to say, a context manager object that yields itself.

Whether you can do that depends what you can do with the type of versioned_file. If you can't change it then you're basically out of luck. If you can change it then you need to implement the __enter__ and __exit__ functions as specified in PEP 343.

In your example code, though, it already has it, and your teardown code is the same as what it does itself on context exit already. So don't bother with contextlib at all, just return the result of open().

For other examples where you do need __enter__ and __exit__, if you like the contextlib style (and who doesn't?) you can bridge the two things. Write a function context that's decorated with @contextmanager and yields self. Then implement:

def __enter__(self):
    self.context = context() # if context() is a method use a different name!
    return self.context.__enter__()
def __exit__(self, *args):
    return self.context.__exit__(*args)

It's basically up to you whether you find this better or worse than separating out the setup code into __enter__ and the teardown code into __exit__. I generally find it better.

Sunday, September 26, 2021
 
WooDzu
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share