我们可以在同一个 with 语句中的另一个 class 中将 contextmanager 装饰器与 __enter__() 和 __exit__() 方法混合使用吗?
Can we mix contextmanager decorator with __enter__() and __exit__() methods in another class inside the same with statement?
在 python3.8 中,我非常熟悉传统的 __enter__
和 __exit__
魔术方法,但对 @contextlib.contextmanager
装饰器还是陌生的。是否可以在单个 with
语句中混合使用两种模式?
下面的(高度设计的)脚本应该能更清楚地解释问题。是否有 ContextClass.enter_context_function()
和 ContextClass.exit_context_function()
的定义(我想 __init__
内部也需要更改)仅使用 context_function()
函数并使单元测试通过?还是这些模式相互排斥?
import contextlib
NUMBERS = []
@contextlib.contextmanager
def context_function():
NUMBERS.append(3)
try:
yield
finally:
NUMBERS.append(5)
class ContextClass:
def __init__(self):
self.numbers = NUMBERS
self.numbers.append(1)
def __enter__(self):
self.numbers.append(2)
self.enter_context_function() # should append 3
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.exit_context_function() # should append 5
self.numbers.append(6)
def function_call(self):
self.numbers.append(4)
def enter_context_function(self):
# FIX ME!
pass
def exit_context_function(self):
# FIX ME!
pass
if __name__ == "__main__":
import unittest
class TestContextManagerFunctionAndClass(unittest.TestCase):
def test_context_function_and_class(self):
with ContextClass() as cc:
cc.function_call()
self.assertEqual(NUMBERS, [1, 2, 3, 4, 5, 6])
unittest.main()
我知道有更好的方法可以解决类似的问题(特别是将 context_function
重写为 class 并使用其自己的 __enter__
和 __exit__
方法,但我'我试图更好地理解 contextmanager 装饰器的工作原理。
无需更改 __init__
。 “使单元测试通过”的手动方式是:
def enter_context_function(self):
self._context_mgr = context_function()
self._context_mgr.__enter__()
def exit_context_function(self):
self._context_mgr.__exit__(None, None, None)
然而,它有点遗漏了 context-managers 的要点。它们旨在用于 with-statement.
另请注意,如所写,如果屈服后的代码加注,则可能无法达到 NUMBERS.append(5)
行(“拆卸”)。应该这样写:
@contextlib.contextmanager
def context_function():
NUMBERS.append(3)
try:
yield
finally:
NUMBERS.append(5)
在 python3.8 中,我非常熟悉传统的 __enter__
和 __exit__
魔术方法,但对 @contextlib.contextmanager
装饰器还是陌生的。是否可以在单个 with
语句中混合使用两种模式?
下面的(高度设计的)脚本应该能更清楚地解释问题。是否有 ContextClass.enter_context_function()
和 ContextClass.exit_context_function()
的定义(我想 __init__
内部也需要更改)仅使用 context_function()
函数并使单元测试通过?还是这些模式相互排斥?
import contextlib
NUMBERS = []
@contextlib.contextmanager
def context_function():
NUMBERS.append(3)
try:
yield
finally:
NUMBERS.append(5)
class ContextClass:
def __init__(self):
self.numbers = NUMBERS
self.numbers.append(1)
def __enter__(self):
self.numbers.append(2)
self.enter_context_function() # should append 3
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.exit_context_function() # should append 5
self.numbers.append(6)
def function_call(self):
self.numbers.append(4)
def enter_context_function(self):
# FIX ME!
pass
def exit_context_function(self):
# FIX ME!
pass
if __name__ == "__main__":
import unittest
class TestContextManagerFunctionAndClass(unittest.TestCase):
def test_context_function_and_class(self):
with ContextClass() as cc:
cc.function_call()
self.assertEqual(NUMBERS, [1, 2, 3, 4, 5, 6])
unittest.main()
我知道有更好的方法可以解决类似的问题(特别是将 context_function
重写为 class 并使用其自己的 __enter__
和 __exit__
方法,但我'我试图更好地理解 contextmanager 装饰器的工作原理。
无需更改 __init__
。 “使单元测试通过”的手动方式是:
def enter_context_function(self):
self._context_mgr = context_function()
self._context_mgr.__enter__()
def exit_context_function(self):
self._context_mgr.__exit__(None, None, None)
然而,它有点遗漏了 context-managers 的要点。它们旨在用于 with-statement.
另请注意,如所写,如果屈服后的代码加注,则可能无法达到 NUMBERS.append(5)
行(“拆卸”)。应该这样写:
@contextlib.contextmanager
def context_function():
NUMBERS.append(3)
try:
yield
finally:
NUMBERS.append(5)