From: Thomas Kluyver Date: Fri, 8 Jun 2018 19:28:37 +0000 (+0200) Subject: bpo-33375: Get filename for warnings from frame.f_code.co_filename (GH-6622) X-Git-Tag: v3.8.0a1~1616 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=11a896652ee98aa44e59ed25237f9efb56635dcf;p=python bpo-33375: Get filename for warnings from frame.f_code.co_filename (GH-6622) More consistent with how other parts of Python find the filename (e.g. tracebacks and pdb). --- diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index b48debd31f..71f6a30034 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -441,78 +441,14 @@ class WarnTests(BaseTest): self.assertEqual(len(w), 1) self.assertEqual(w[0].filename, __file__) - def test_missing_filename_not_main(self): - # If __file__ is not specified and __main__ is not the module name, - # then __file__ should be set to the module name. - filename = warning_tests.__file__ - try: - del warning_tests.__file__ - with warnings_state(self.module): - with original_warnings.catch_warnings(record=True, - module=self.module) as w: - warning_tests.inner("spam8", stacklevel=1) - self.assertEqual(w[-1].filename, warning_tests.__name__) - finally: - warning_tests.__file__ = filename - - @unittest.skipUnless(hasattr(sys, 'argv'), 'test needs sys.argv') - def test_missing_filename_main_with_argv(self): - # If __file__ is not specified and the caller is __main__ and sys.argv - # exists, then use sys.argv[0] as the file. - filename = warning_tests.__file__ - module_name = warning_tests.__name__ - try: - del warning_tests.__file__ - warning_tests.__name__ = '__main__' - with warnings_state(self.module): - with original_warnings.catch_warnings(record=True, - module=self.module) as w: - warning_tests.inner('spam9', stacklevel=1) - self.assertEqual(w[-1].filename, sys.argv[0]) - finally: - warning_tests.__file__ = filename - warning_tests.__name__ = module_name - - def test_missing_filename_main_without_argv(self): - # If __file__ is not specified, the caller is __main__, and sys.argv - # is not set, then '__main__' is the file name. - filename = warning_tests.__file__ - module_name = warning_tests.__name__ - argv = sys.argv - try: - del warning_tests.__file__ - warning_tests.__name__ = '__main__' - del sys.argv - with warnings_state(self.module): - with original_warnings.catch_warnings(record=True, - module=self.module) as w: - warning_tests.inner('spam10', stacklevel=1) - self.assertEqual(w[-1].filename, '__main__') - finally: - warning_tests.__file__ = filename - warning_tests.__name__ = module_name - sys.argv = argv - - def test_missing_filename_main_with_argv_empty_string(self): - # If __file__ is not specified, the caller is __main__, and sys.argv[0] - # is the empty string, then '__main__ is the file name. - # Tests issue 2743. - file_name = warning_tests.__file__ - module_name = warning_tests.__name__ - argv = sys.argv - try: - del warning_tests.__file__ - warning_tests.__name__ = '__main__' - sys.argv = [''] - with warnings_state(self.module): - with original_warnings.catch_warnings(record=True, - module=self.module) as w: - warning_tests.inner('spam11', stacklevel=1) - self.assertEqual(w[-1].filename, '__main__') - finally: - warning_tests.__file__ = file_name - warning_tests.__name__ = module_name - sys.argv = argv + def test_exec_filename(self): + filename = "" + codeobj = compile(("import warnings\n" + "warnings.warn('hello', UserWarning)"), + filename, "exec") + with original_warnings.catch_warnings(record=True) as w: + exec(codeobj) + self.assertEqual(w[0].filename, filename) def test_warn_explicit_non_ascii_filename(self): with original_warnings.catch_warnings(record=True, @@ -1245,9 +1181,7 @@ class A: a=A() """ rc, out, err = assert_python_ok("-c", code) - # note: "__main__" filename is not correct, it should be the name - # of the script - self.assertEqual(err.decode(), '__main__:7: UserWarning: test') + self.assertEqual(err.decode(), ':7: UserWarning: test') def test_late_resource_warning(self): # Issue #21925: Emitting a ResourceWarning late during the Python diff --git a/Lib/warnings.py b/Lib/warnings.py index 81f9864778..6830b602de 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -303,28 +303,16 @@ def warn(message, category=None, stacklevel=1, source=None): raise ValueError except ValueError: globals = sys.__dict__ + filename = "sys" lineno = 1 else: globals = frame.f_globals + filename = frame.f_code.co_filename lineno = frame.f_lineno if '__name__' in globals: module = globals['__name__'] else: module = "" - filename = globals.get('__file__') - if filename: - fnl = filename.lower() - if fnl.endswith(".pyc"): - filename = filename[:-1] - else: - if module == "__main__": - try: - filename = sys.argv[0] - except AttributeError: - # embedded interpreters don't have sys.argv, see bug #839151 - filename = '__main__' - if not filename: - filename = module registry = globals.setdefault("__warningregistry__", {}) warn_explicit(message, category, filename, lineno, module, registry, globals, source) diff --git a/Misc/NEWS.d/next/Library/2018-04-28-08-11-35.bpo-33375.Dbq1fz.rst b/Misc/NEWS.d/next/Library/2018-04-28-08-11-35.bpo-33375.Dbq1fz.rst new file mode 100644 index 0000000000..f3f9d2a508 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-04-28-08-11-35.bpo-33375.Dbq1fz.rst @@ -0,0 +1,4 @@ +The warnings module now finds the Python file associated with a warning from +the code object, rather than the frame's global namespace. This is +consistent with how tracebacks and pdb find filenames, and should work +better for dynamically executed code. diff --git a/Python/_warnings.c b/Python/_warnings.c index 29e475d67d..9c7a6e17b3 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -671,7 +671,7 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno, { PyObject *globals; - /* Setup globals and lineno. */ + /* Setup globals, filename and lineno. */ PyFrameObject *f = PyThreadState_GET()->frame; // Stack level comparisons to Python code is off by one as there is no // warnings-related stack level to avoid. @@ -688,10 +688,13 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno, if (f == NULL) { globals = PyThreadState_Get()->interp->sysdict; + *filename = PyUnicode_FromString("sys"); *lineno = 1; } else { globals = f->f_globals; + *filename = f->f_code->co_filename; + Py_INCREF(*filename); *lineno = PyFrame_GetLineNumber(f); } @@ -726,71 +729,6 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno, goto handle_error; } - /* Setup filename. */ - *filename = PyDict_GetItemString(globals, "__file__"); - if (*filename != NULL && PyUnicode_Check(*filename)) { - Py_ssize_t len; - int kind; - void *data; - - if (PyUnicode_READY(*filename)) - goto handle_error; - - len = PyUnicode_GetLength(*filename); - kind = PyUnicode_KIND(*filename); - data = PyUnicode_DATA(*filename); - -#define ascii_lower(c) ((c <= 127) ? Py_TOLOWER(c) : 0) - /* if filename.lower().endswith(".pyc"): */ - if (len >= 4 && - PyUnicode_READ(kind, data, len-4) == '.' && - ascii_lower(PyUnicode_READ(kind, data, len-3)) == 'p' && - ascii_lower(PyUnicode_READ(kind, data, len-2)) == 'y' && - ascii_lower(PyUnicode_READ(kind, data, len-1)) == 'c') - { - *filename = PyUnicode_Substring(*filename, 0, - PyUnicode_GET_LENGTH(*filename)-1); - if (*filename == NULL) - goto handle_error; - } - else - Py_INCREF(*filename); - } - else { - *filename = NULL; - if (*module != Py_None && _PyUnicode_EqualToASCIIString(*module, "__main__")) { - PyObject *argv = _PySys_GetObjectId(&PyId_argv); - /* PyList_Check() is needed because sys.argv is set to None during - Python finalization */ - if (argv != NULL && PyList_Check(argv) && PyList_Size(argv) > 0) { - int is_true; - *filename = PyList_GetItem(argv, 0); - Py_INCREF(*filename); - /* If sys.argv[0] is false, then use '__main__'. */ - is_true = PyObject_IsTrue(*filename); - if (is_true < 0) { - Py_DECREF(*filename); - goto handle_error; - } - else if (!is_true) { - Py_SETREF(*filename, PyUnicode_FromString("__main__")); - if (*filename == NULL) - goto handle_error; - } - } - else { - /* embedded interpreters don't have sys.argv, see bug #839151 */ - *filename = PyUnicode_FromString("__main__"); - if (*filename == NULL) - goto handle_error; - } - } - if (*filename == NULL) { - *filename = *module; - Py_INCREF(*filename); - } - } - return 1; handle_error: