Kaydet (Commit) dd0e087e authored tarafından Martin Teichmann's avatar Martin Teichmann Kaydeden (comit) Nick Coghlan

bpo-30306: release arguments of contextmanager (GH-1500)

The arguments to a generator function which is declared as a
contextmanager are stored inside the context manager, and
thus are kept alive, even when it is used as a regular context
manager, and not as a function decorator (where it needs
the original arguments to recreate the generator on each
call).

This is a possible unnecessary memory leak, so this changes
contextmanager.__enter__ to release the saved arguments,
as that method being called means that particular CM instance
isn't going to need to recreate the underlying generator.

Patch by Martin Teichmann.
üst c4b12483
...@@ -105,6 +105,9 @@ class _GeneratorContextManager(_GeneratorContextManagerBase, ...@@ -105,6 +105,9 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
return self.__class__(self.func, self.args, self.kwds) return self.__class__(self.func, self.args, self.kwds)
def __enter__(self): 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: try:
return next(self.gen) return next(self.gen)
except StopIteration: except StopIteration:
......
...@@ -8,6 +8,7 @@ import threading ...@@ -8,6 +8,7 @@ import threading
import unittest import unittest
from contextlib import * # Tests __all__ from contextlib import * # Tests __all__
from test import support from test import support
import weakref
class TestAbstractContextManager(unittest.TestCase): class TestAbstractContextManager(unittest.TestCase):
...@@ -219,6 +220,52 @@ def woohoo(): ...@@ -219,6 +220,52 @@ def woohoo():
with woohoo(self=11, func=22, args=33, kwds=44) as target: with woohoo(self=11, func=22, args=33, kwds=44) as target:
self.assertEqual(target, (11, 22, 33, 44)) 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
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)
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): class ClosingTestCase(unittest.TestCase):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment