为何不应在父类/委托对象上调用__enter__方法?
问题:为何封装IO对象时__enter__不能调用父类/委托对象的实现,而__exit__可以?
背景
我在做Exercism的paasio练习,需要分别用子类化和委托两种方式封装IO操作,以下是我的代码片段:
子类化实现(MeteredFile)
class MeteredFile(io.BufferedRandom): """采用子类化模式实现""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._read_bytes = self._write_bytes = self._read_ops = self._write_ops = 0 def __enter__(self) -> Self: return self def __exit__(self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) -> bool | None: return super().__exit__(exc_type, exc_val, exc_tb) # 其他方法未展示
委托模式实现(MeteredSocket)
class MeteredSocket: """采用委托模式实现""" def __init__(self, s: socket): self._socket = s self._recv_bytes = self._send_bytes = self._recv_ops = self._send_ops = 0 def __enter__(self) -> Self: return self def __exit__(self, exc_type: Type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None) -> bool | None: return self._socket.__exit__(exc_type, exc_val, exc_tb) # 其他方法未展示
核心疑问
我发现一个奇怪的差异:
- 如果在
MeteredFile.__enter__中调用super().__enter__(),或者在MeteredSocket.__enter__中调用self._socket.__enter__(),测试会失败——测试用例明确预期这些父类/委托对象的__enter__方法不被调用,直接返回self才能通过测试。 - 但调用父类/委托对象的
__exit__方法却完全符合预期,测试能正常通过。
以下是相关的测试用例代码:
@patch("paasio.super", create=True, new_callable=SuperMock) def test_meteredfile_context_manager(self, super_mock): wrapped = MockFile(ZEN) mock = NonCallableMagicMock(wraps=wrapped, autospec=True) mock.__exit__.side_effect = wrapped.__exit__ super_mock.mock_object = mock with MeteredFile() as file: self.assertEqual(1, super_mock.init_called) self.assertFalse(mock.__enter__.called) file.read() self.assertFalse(mock.__enter__.called) mock.__exit__.assert_called_once_with(None, None, None) self.assertEqual(2, len(mock.mock_calls)) with self.assertRaisesRegex(ValueError, "I/O operation on closed file."): file.read() with self.assertRaisesRegex(ValueError, "I/O operation on closed file."): file.write(b"data") @patch("paasio.super", create=True, new_callable=SuperMock) def test_meteredfile_context_manager_exception_raise(self, super_mock): exception = MockException("Should raise") wrapped = MockFile(ZEN, exception=exception) mock = NonCallableMagicMock(wraps=wrapped, autospec=True) mock.__exit__.side_effect = wrapped.__exit__ super_mock.mock_object = mock with self.assertRaisesRegex(MockException, "Should raise") as err: with MeteredFile() as file: self.assertFalse(mock.__enter__.called) file.read() self.assertFalse(mock.__enter__.called) mock.__exit__.assert_called_once_with( MockException, err.exception, ANY, ) self.assertEqual(exception, err.exception)
我实在搞不懂这种差异的原因,有人能解释一下吗?
内容的提问来源于stack exchange,提问作者Abhijit Sarkar
相关产品推荐
相关产品推荐

