You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何不应在父类/委托对象上调用__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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 08:22:48