diff --git a/Lib/test/test_warnings.py b/Lib/test/test_warnings.py --- a/Lib/test/test_warnings.py +++ b/Lib/test/test_warnings.py @@ -254,16 +254,49 @@ class FilterTests(BaseTest): L = [("default",X(),UserWarning,X(),0) for i in range(2)] with original_warnings.catch_warnings(record=True, module=self.module) as w: self.module.filters = L self.module.warn_explicit(UserWarning("b"), None, "f.py", 42) self.assertEqual(str(w[-1].message), "b") + def test_filterwarnings_duplicate_filters(self): + with original_warnings.catch_warnings(module=self.module): + initial_len = len(self.module.filters) + self.module.filterwarnings("error", category=UserWarning) + self.assertEqual(len(self.module.filters), initial_len + 1) + self.module.filterwarnings("ignore", category=UserWarning) + self.module.filterwarnings("error", category=UserWarning) + self.assertEqual( + len(self.module.filters), initial_len + 2, + "filterwarnings inserted duplicate filter" + ) + self.assertEqual( + self.module.filters[0][0], "error", + "filterwarnings did not promote filter to " + "the beginning of list" + ) + + def test_simplefilter_duplicate_filters(self): + with original_warnings.catch_warnings(module=self.module): + initial_len = len(self.module.filters) + self.module.simplefilter("error", category=UserWarning) + self.assertEqual(len(self.module.filters), initial_len + 1) + self.module.simplefilter("ignore", category=UserWarning) + self.module.simplefilter("error", category=UserWarning) + self.assertEqual( + len(self.module.filters), initial_len + 2, + "simplefilter inserted duplicate filter" + ) + self.assertEqual( + self.module.filters[0][0], "error", + "simplefilter did not promote filter to the beginning of list" + ) + class CFilterTests(FilterTests, unittest.TestCase): module = c_warnings class PyFilterTests(FilterTests, unittest.TestCase): module = py_warnings class WarnTests(BaseTest): diff --git a/Lib/warnings.py b/Lib/warnings.py --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -47,16 +47,23 @@ def filterwarnings(action, message="", c assert isinstance(message, str), "message must be a string" assert isinstance(category, type), "category must be a class" assert issubclass(category, Warning), "category must be a Warning subclass" assert isinstance(module, str), "module must be a string" assert isinstance(lineno, int) and lineno >= 0, \ "lineno must be an int >= 0" item = (action, re.compile(message, re.I), category, re.compile(module), lineno) + + # Remove possible duplicate filters, so new one will be placed + # in correct place + try: + filters.remove(item) + except ValueError: + pass if append: filters.append(item) else: filters.insert(0, item) _filters_mutated() def simplefilter(action, category=Warning, lineno=0, append=False): """Insert a simple entry into the list of warnings filters (at the front). @@ -68,16 +75,23 @@ def simplefilter(action, category=Warnin 'lineno' -- an integer line number, 0 matches all warnings 'append' -- if true, append to the list of filters """ assert action in ("error", "ignore", "always", "default", "module", "once"), "invalid action: %r" % (action,) assert isinstance(lineno, int) and lineno >= 0, \ "lineno must be an int >= 0" item = (action, None, category, None, lineno) + + # Remove possible duplicate filters, so new one will be placed + # in correct place + try: + filters.remove(item) + except ValueError: + pass if append: filters.append(item) else: filters.insert(0, item) _filters_mutated() def resetwarnings(): """Clear the list of warning filters, so that no filters are active.""" diff --git a/Misc/NEWS b/Misc/NEWS --- a/Misc/NEWS +++ b/Misc/NEWS @@ -61,16 +61,19 @@ Core and Builtins - Issue #24044: Fix possible null pointer dereference in list.sort in out of memory conditions. - Issue #21354: PyCFunction_New function is exposed by python DLL again. Library ------- +- Issue #18383: Avoid creating duplicate filters when using filterwarnings + and simplefilter. Patch by Alex Shkop. + - Issue #23319: Fix ctypes.BigEndianStructure, swap correctly bytes. Patch written by Matthieu Gautier. - Issue #23254: Document how to close the TCPServer listening socket. Patch from Martin Panter. - Issue #19450: Update Windows and OS X installer builds to use SQLite 3.8.11.