diff --git a/Doc/library/runpy.rst b/Doc/library/runpy.rst index ce8aa990e4c..cfaab94f123 100644 --- a/Doc/library/runpy.rst +++ b/Doc/library/runpy.rst @@ -52,11 +52,9 @@ The :mod:`runpy` module provides a single function: If the argument *alter_sys* is supplied and evaluates to ``True``, then ``sys.argv[0]`` is updated with the value of ``__file__`` and - ``sys.modules[__name__]`` is updated with a new module object for the module - being executed. Note that neither ``sys.argv[0]`` nor ``sys.modules[__name__]`` - are restored to their original values before the function returns - if client - code needs these values preserved, it must either save them explicitly or else - avoid enabling the automatic alterations to :mod:`sys`. + ``sys.modules[__name__]`` is updated with a temporary module object for the + module being executed. Both ``sys.argv[0]`` and ``sys.modules[__name__]`` + are restored to their original values before the function returns. Note that this manipulation of :mod:`sys` is not thread-safe. Other threads may see the partially initialised module, as well as the altered list of arguments. diff --git a/Lib/runpy.py b/Lib/runpy.py index 8096aac40c2..d2f18d37e36 100755 --- a/Lib/runpy.py +++ b/Lib/runpy.py @@ -33,21 +33,36 @@ def _run_code(code, run_globals, init_globals, return run_globals def _run_module_code(code, init_globals=None, - mod_name=None, mod_fname=None, - mod_loader=None, alter_sys=False): + mod_name=None, mod_fname=None, + mod_loader=None, alter_sys=False): """Helper for run_module""" # Set up the top level namespace dictionary if alter_sys: - # Modify sys.argv[0] and sys.modules[mod_name] + # Modify sys.argv[0] and sys.module[mod_name] + temp_module = imp.new_module(mod_name) + mod_globals = temp_module.__dict__ + saved_argv0 = sys.argv[0] + restore_module = mod_name in sys.modules + if restore_module: + saved_module = sys.modules[mod_name] sys.argv[0] = mod_fname - module = imp.new_module(mod_name) - sys.modules[mod_name] = module - mod_globals = module.__dict__ + sys.modules[mod_name] = temp_module + try: + _run_code(code, mod_globals, init_globals, + mod_name, mod_fname, mod_loader) + finally: + sys.argv[0] = saved_argv0 + if restore_module: + sys.modules[mod_name] = saved_module + else: + del sys.modules[mod_name] + # Copy the globals of the temporary module, as they + # may be cleared when the temporary module goes away + return mod_globals.copy() else: # Leave the sys module alone - mod_globals = {} - return _run_code(code, mod_globals, init_globals, - mod_name, mod_fname, mod_loader) + return _run_code(code, {}, init_globals, + mod_name, mod_fname, mod_loader) # This helper is needed due to a missing component in the PEP 302 diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 7076df79665..9de98118341 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -26,7 +26,8 @@ class RunModuleCodeTest(unittest.TestCase): " module_in_sys_modules = globals() is sys.modules[__name__].__dict__\n" "# Check nested operation\n" "import runpy\n" - "nested = runpy._run_module_code('x=1\\n', mod_name='')\n" + "nested = runpy._run_module_code('x=1\\n', mod_name='',\n" + " alter_sys=True)\n" ) @@ -37,44 +38,35 @@ class RunModuleCodeTest(unittest.TestCase): loader = "Now you're just being silly" d1 = dict(initial=initial) saved_argv0 = sys.argv[0] - try: - d2 = _run_module_code(self.test_source, - d1, - name, - file, - loader, - alter_sys=True) - self.failUnless("result" not in d1) - self.failUnless(d2["initial"] is initial) - self.failUnless(d2["result"] == self.expected_result) - self.failUnless(d2["nested"]["x"] == 1) - self.failUnless(d2["nested"]["__name__"] == "") - self.failUnless(d2["__name__"] is name) - self.failUnless(d2["__file__"] is file) - self.failUnless(d2["__loader__"] is loader) - self.failUnless(d2["run_argv0"] is file) - self.failUnless(d2["run_name_in_sys_modules"]) - self.failUnless(d2["module_in_sys_modules"]) - self.failUnless(sys.argv[0] is not saved_argv0) - self.failUnless(name in sys.modules) - finally: - sys.argv[0] = saved_argv0 - if name in sys.modules: - del sys.modules[name] + d2 = _run_module_code(self.test_source, + d1, + name, + file, + loader, + True) + self.failUnless("result" not in d1) + self.failUnless(d2["initial"] is initial) + self.failUnless(d2["result"] == self.expected_result) + self.failUnless(d2["nested"]["x"] == 1) + self.failUnless(d2["__name__"] is name) + self.failUnless(d2["run_name_in_sys_modules"]) + self.failUnless(d2["module_in_sys_modules"]) + self.failUnless(d2["__file__"] is file) + self.failUnless(d2["run_argv0"] is file) + self.failUnless(d2["__loader__"] is loader) + self.failUnless(sys.argv[0] is saved_argv0) + self.failUnless(name not in sys.modules) def test_run_module_code_defaults(self): saved_argv0 = sys.argv[0] d = _run_module_code(self.test_source) self.failUnless(d["result"] == self.expected_result) - self.failUnless(d["nested"]["x"] == 1) - self.failUnless(d["nested"]["__name__"] == "") self.failUnless(d["__name__"] is None) self.failUnless(d["__file__"] is None) self.failUnless(d["__loader__"] is None) self.failUnless(d["run_argv0"] is saved_argv0) - self.failUnless(not d["run_name_in_sys_modules"]) + self.failUnless("run_name" not in d) self.failUnless(sys.argv[0] is saved_argv0) - self.failUnless(None not in sys.modules) class RunModuleTest(unittest.TestCase): diff --git a/Python/compile.c b/Python/compile.c index eed13792d28..cb6555ed384 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -1616,10 +1616,14 @@ compiler_while(struct compiler *c, stmt_ty s) orelse = NULL; ADDOP_JREL(c, SETUP_LOOP, end); - compiler_use_next_block(c, loop); if (!compiler_push_fblock(c, LOOP, loop)) return 0; + compiler_use_next_block(c, loop); if (constant == -1) { + /* XXX(ncoghlan): SF bug #1750076 + Use same special casing as is used in for loops + A test case for this would be nice... */ + c->u->u_lineno_set = false; VISIT(c, expr, s->v.While.test); ADDOP_JREL(c, JUMP_IF_FALSE, anchor); ADDOP(c, POP_TOP); @@ -3521,7 +3525,8 @@ assemble_lnotab(struct assembler *a, struct instr *i) /* XXX(nnorwitz): is there a better way to handle this? for loops are special, we want to be able to trace them each time around, so we need to set an extra line number. */ - if (d_lineno == 0 && i->i_opcode != FOR_ITER) + /* XXX(ncoghlan): while loops need this too */ + if (d_lineno == 0) return 1; if (d_bytecode > 255) {