From c26c1853eeb4c173d2a83e14d5f50d7edb58d30d Mon Sep 17 00:00:00 2001 From: Martin Teichmann Date: Thu, 23 Feb 2017 15:23:33 +0100 Subject: [PATCH 1/3] bpo-30306: release arguments of contextmanager The arguments of a function which was declared as a contextmanager are stored inside the context manager, and thus are kept alive. This is a possible unnecessary memory leak. --- Lib/contextlib.py | 3 +++ Lib/test/test_contextlib.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 962cedab490eb22..2f00dabf6ff38b7 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -83,6 +83,9 @@ def _recreate_cm(self): return self.__class__(self.func, self.args, self.kwds) def __enter__(self): + # do not keep args and kwds alive unnecessarily + # they are only needed for recreation, which is not possible anymore + del self.args, self.kwds, self.func try: return next(self.gen) except StopIteration: diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 64b6578ff94eb30..ad0f0d92e0a2430 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -7,6 +7,7 @@ import unittest from contextlib import * # Tests __all__ from test import support +import weakref class TestAbstractContextManager(unittest.TestCase): @@ -218,6 +219,21 @@ def woohoo(self, func, args, kwds): with woohoo(self=11, func=22, args=33, kwds=44) as target: self.assertEqual(target, (11, 22, 33, 44)) + def test_nokeepref(self): + class A: + pass + + @contextmanager + def woohoo(a, b): + a = weakref.ref(a) + b = weakref.ref(b) + self.assertIsNone(a()) + self.assertIsNone(b()) + yield + + with woohoo(A(), b=A()): + pass + class ClosingTestCase(unittest.TestCase): From 0be196e6530bc9bcee68221d88e9421186352db7 Mon Sep 17 00:00:00 2001 From: Martin Teichmann Date: Wed, 10 May 2017 08:31:59 +0200 Subject: [PATCH 2/3] add test for early creation of generator --- Lib/test/test_contextlib.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index ad0f0d92e0a2430..3deaa8be90a5ab4 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -234,6 +234,18 @@ def woohoo(a, b): with woohoo(A(), b=A()): pass + def test_param_errors(self): + @contextmanager + def woohoo(a, *, b): + yield + + with self.assertRaises(TypeError): + woohoo() + with self.assertRaises(TypeError): + woohoo(3, 5) + with self.assertRaises(TypeError): + woohoo(b=3) + class ClosingTestCase(unittest.TestCase): From 33ed03e40b469326fb766978b53c04a0cfc76ffc Mon Sep 17 00:00:00 2001 From: Martin Teichmann Date: Wed, 10 May 2017 09:58:15 +0200 Subject: [PATCH 3/3] add test for recursive context manager decorator --- Lib/test/test_contextlib.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index 3deaa8be90a5ab4..76ff0d2204a9425 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -246,6 +246,25 @@ def woohoo(a, *, b): with self.assertRaises(TypeError): woohoo(b=3) + def test_recursive(self): + depth = 0 + @contextmanager + def woohoo(): + nonlocal depth + before = depth + depth += 1 + yield + depth -= 1 + self.assertEqual(depth, before) + + @woohoo() + def recursive(): + if depth < 10: + recursive() + + recursive() + self.assertEqual(depth, 0) + class ClosingTestCase(unittest.TestCase):