diff --git a/Lib/os.py b/Lib/os.py index 710d6f8cfcdf74..97e53a907e9201 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -742,6 +742,15 @@ def __repr__(self): ) return f"environ({{{formatted_items}}})" + def clear(self): + while self._data: + for key in list(self._data): + unsetenv(key) + try: + del self._data[key] + except KeyError: + pass + def copy(self): return dict(self) diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index 371771087aaf88..1a6561d7ff2d7a 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -1494,6 +1494,71 @@ def test_reload_environ(self): self.assertNotIn(b'test_env', os.environb) self.assertNotIn('test_env', os.environ) + def test_clear_empties_environ(self): + os.environ["test_key_to_clear1"] = "test_value_to_clear1" + os.environ["test_key_to_clear2"] = "test_value_to_clear2" + os.environ["test_key_to_clear3"] = "test_value_to_clear3" + + # Test environ.clear() removes the environment variables + os.environ.clear() + self.assertEqual(os.environ, {}) + if os.supports_bytes_environ: + self.assertEqual(os.environb, {}) + + # Repeated calls should be idempotent + os.environ.clear() + self.assertEqual(os.environ, {}) + if os.supports_bytes_environ: + self.assertEqual(os.environb, {}) + + @unittest.skipUnless(os.supports_bytes_environ, "os.environb required for this test.") + def test_clear_empties_environb(self): + os.environb[b"test_key_to_clear1"] = b"test_value_to_clear1" + os.environb[b"test_key_to_clear2"] = b"test_value_to_clear2" + os.environb[b"test_key_to_clear3"] = b"test_value_to_clear3" + + # Test environ.clear() removes the environment variables + os.environb.clear() + self.assertEqual(os.environ, {}) + self.assertEqual(os.environb, {}) + + # Repeated calls should be idempotent + os.environb.clear() + self.assertEqual(os.environ, {}) + self.assertEqual(os.environb, {}) + + def test_clear_empties_process_environment(self): + # Determine if on the current platform os.unsetenv() + # updates process environment. + os.environ['to_remove'] = 'value' + os.unsetenv('to_remove') + os.reload_environ() + if 'to_remove' in os.environ: + self.skipTest("os.unsetenv() doesn't update the process environment on this platform.") + + # Set up two environment variables to be cleared + os.environ["test_env1"] = "some_value1" + os.environ["test_env2"] = "some_value2" + + # Ensure the variables were persisted on process level. + os.reload_environ() + self.assertEqual(os.getenv("test_env1"), "some_value1") + self.assertEqual(os.getenv("test_env2"), "some_value2") + + # Test that os.clear() clears both os.environ and os.environb + os.environ.clear() + self.assertEqual(os.environ, {}) + if os.supports_bytes_environ: + self.assertEqual(os.environb, {}) + + # Test that os.clear() also clears the process environment, + # so that after os.reload_environ() environ and environb are still empty. + os.reload_environ() + self.assertEqual(os.environ, {}) + if os.supports_bytes_environ: + self.assertEqual(os.environb, {}) + + class WalkTests(unittest.TestCase): """Tests for os.walk().""" is_fwalk = False diff --git a/Misc/NEWS.d/next/Library/2025-10-01-17-45-27.gh-issue-139482.yDMeEa.rst b/Misc/NEWS.d/next/Library/2025-10-01-17-45-27.gh-issue-139482.yDMeEa.rst new file mode 100644 index 00000000000000..754e5c8c9318f1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-01-17-45-27.gh-issue-139482.yDMeEa.rst @@ -0,0 +1,2 @@ +Ensure that :data:`os.environ.clear() ` +has linear complexity instead of quadratic complexity.