diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index 4ce88ab3344..4fec3657162 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -300,7 +300,7 @@ Available Functions ------------------- -.. function:: warn(message, category=None, stacklevel=1) +.. function:: warn(message, category=None, stacklevel=1, source=None) Issue a warning, or maybe ignore it or raise an exception. The *category* argument, if given, must be a warning category class (see above); it defaults to @@ -318,6 +318,12 @@ Available Functions source of :func:`deprecation` itself (since the latter would defeat the purpose of the warning message). + *source*, if supplied, is the destroyed object which emitted a + :exc:`ResourceWarning`. + + .. versionchanged:: 3.6 + Added *source* parameter. + .. function:: warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None) diff --git a/Lib/_pyio.py b/Lib/_pyio.py index c3ad81e6db7..972c082803d 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -1514,7 +1514,7 @@ class FileIO(RawIOBase): if self._fd >= 0 and self._closefd and not self.closed: import warnings warnings.warn('unclosed file %r' % (self,), ResourceWarning, - stacklevel=2) + stacklevel=2, source=self) self.close() def __getstate__(self): diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 9d07673fbad..3a42b10cb1b 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -412,7 +412,8 @@ class BaseEventLoop(events.AbstractEventLoop): if compat.PY34: def __del__(self): if not self.is_closed(): - warnings.warn("unclosed event loop %r" % self, ResourceWarning) + warnings.warn("unclosed event loop %r" % self, ResourceWarning, + source=self) if not self.is_running(): self.close() diff --git a/Lib/asyncio/base_subprocess.py b/Lib/asyncio/base_subprocess.py index 73425d9bbcc..efe08313e7a 100644 --- a/Lib/asyncio/base_subprocess.py +++ b/Lib/asyncio/base_subprocess.py @@ -122,7 +122,8 @@ class BaseSubprocessTransport(transports.SubprocessTransport): if compat.PY34: def __del__(self): if not self._closed: - warnings.warn("unclosed transport %r" % self, ResourceWarning) + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) self.close() def get_pid(self): diff --git a/Lib/asyncio/proactor_events.py b/Lib/asyncio/proactor_events.py index 14c0659ddee..2671a94d55a 100644 --- a/Lib/asyncio/proactor_events.py +++ b/Lib/asyncio/proactor_events.py @@ -86,7 +86,8 @@ class _ProactorBasePipeTransport(transports._FlowControlMixin, if compat.PY34: def __del__(self): if self._sock is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning) + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) self.close() def _fatal_error(self, exc, message='Fatal error on pipe transport'): diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py index 812fac19f86..cbb36250c96 100644 --- a/Lib/asyncio/selector_events.py +++ b/Lib/asyncio/selector_events.py @@ -573,7 +573,8 @@ class _SelectorTransport(transports._FlowControlMixin, if compat.PY34: def __del__(self): if self._sock is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning) + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) self._sock.close() def _fatal_error(self, exc, message='Fatal error on transport'): diff --git a/Lib/asyncio/sslproto.py b/Lib/asyncio/sslproto.py index dde980b68f8..1cea850475d 100644 --- a/Lib/asyncio/sslproto.py +++ b/Lib/asyncio/sslproto.py @@ -324,7 +324,8 @@ class _SSLProtocolTransport(transports._FlowControlMixin, if compat.PY34: def __del__(self): if not self._closed: - warnings.warn("unclosed transport %r" % self, ResourceWarning) + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) self.close() def pause_reading(self): diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py index 7747ff41bb8..2beba3e3abc 100644 --- a/Lib/asyncio/unix_events.py +++ b/Lib/asyncio/unix_events.py @@ -378,7 +378,8 @@ class _UnixReadPipeTransport(transports.ReadTransport): if compat.PY34: def __del__(self): if self._pipe is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning) + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) self._pipe.close() def _fatal_error(self, exc, message='Fatal error on pipe transport'): @@ -567,7 +568,8 @@ class _UnixWritePipeTransport(transports._FlowControlMixin, if compat.PY34: def __del__(self): if self._pipe is not None: - warnings.warn("unclosed transport %r" % self, ResourceWarning) + warnings.warn("unclosed transport %r" % self, ResourceWarning, + source=self) self._pipe.close() def abort(self): diff --git a/Lib/asyncio/windows_utils.py b/Lib/asyncio/windows_utils.py index 870cd13abe6..7c63fb904b3 100644 --- a/Lib/asyncio/windows_utils.py +++ b/Lib/asyncio/windows_utils.py @@ -159,7 +159,8 @@ class PipeHandle: def __del__(self): if self._handle is not None: - warnings.warn("unclosed %r" % self, ResourceWarning) + warnings.warn("unclosed %r" % self, ResourceWarning, + source=self) self.close() def __enter__(self): diff --git a/Lib/asyncore.py b/Lib/asyncore.py index 3b51f0f337f..4b046d67e39 100644 --- a/Lib/asyncore.py +++ b/Lib/asyncore.py @@ -595,7 +595,8 @@ if os.name == 'posix': def __del__(self): if self.fd >= 0: - warnings.warn("unclosed file %r" % self, ResourceWarning) + warnings.warn("unclosed file %r" % self, ResourceWarning, + source=self) self.close() def recv(self, *args): diff --git a/Lib/tempfile.py b/Lib/tempfile.py index ad687b9a03d..61462357c72 100644 --- a/Lib/tempfile.py +++ b/Lib/tempfile.py @@ -797,7 +797,6 @@ class TemporaryDirectory(object): _shutil.rmtree(name) _warnings.warn(warn_message, ResourceWarning) - def __repr__(self): return "<{} {!r}>".format(self.__class__.__name__, self.name) diff --git a/Lib/warnings.py b/Lib/warnings.py index 9fb21a87825..d4f591ee71d 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -233,7 +233,7 @@ def _next_external_frame(frame): # Code typically replaced by _warnings -def warn(message, category=None, stacklevel=1): +def warn(message, category=None, stacklevel=1, source=None): """Issue a warning, or maybe ignore it or raise an exception.""" # Check if message is already a Warning object if isinstance(message, Warning): @@ -283,7 +283,7 @@ def warn(message, category=None, stacklevel=1): filename = module registry = globals.setdefault("__warningregistry__", {}) warn_explicit(message, category, filename, lineno, module, registry, - globals) + globals, source) def warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, diff --git a/Python/_warnings.c b/Python/_warnings.c index 25299fb622f..dcac57bda58 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -787,18 +787,19 @@ do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level, static PyObject * warnings_warn(PyObject *self, PyObject *args, PyObject *kwds) { - static char *kw_list[] = { "message", "category", "stacklevel", 0 }; - PyObject *message, *category = NULL; + static char *kw_list[] = {"message", "category", "stacklevel", + "source", NULL}; + PyObject *message, *category = NULL, *source = NULL; Py_ssize_t stack_level = 1; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|On:warn", kw_list, - &message, &category, &stack_level)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OnO:warn", kw_list, + &message, &category, &stack_level, &source)) return NULL; category = get_category(message, category); if (category == NULL) return NULL; - return do_warn(message, category, stack_level, NULL); + return do_warn(message, category, stack_level, source); } static PyObject *