专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
51好读  ›  专栏  ›  Python开发者

Python 类不要再写 __init__ 方法了

Python开发者  · 公众号  · Python  · 2025-05-20 11:03

正文

请到「今天看啥」查看全文


fileio 模块中:

  • open(path: str) -> int
  • read(fileno: int, length: int)
  • close(fileno: int)

我们假设 fileio.open 返回一个表示文件描述符的整数【注1】, fileio.read 从打开的文件描述符中读取 length 个字节,而 fileio.close 则关闭该文件描述符,使其失效。

根据我们写了无数个 __init__ 方法所形成的思维习惯,我们可能会这样定义 FileReader 类:

class FileReader:
    def __init__(self, path: str) -> None:
        self._fd = fileio.open(path)
    def read(self, length: int) -> bytes:
        return fileio.read(self._fd, length)
    def close(self) -> None:
        fileio.close(self._fd)

对于我们的初始用例,这没问题。客户端代码通过执行类似 FileReader("./config.json") 的操作,来创建一个 FileReader ,它会将文件描述符 int 作为私有状态维护起来。这正是我们期望的;我们不希望用户代码看到或篡改 _fd ,因为这可能会违反 FileReader 的不变性。构造有效 FileReader 所需的所有必要工作——即调用 open ——都由 FileReader.__init__ 处理好了。

然而,随着需求增加, FileReader.__init__ 变得越来越尴尬。

最初我们只关心 fileio.open ,但后来,我们可能需要适配一个库,它因为某种原因需要自己管理对 fileio.open 的调用,并想要返回一个 int 作为我们的 _fd ,现在我们不得不采用像这样的奇怪变通方法:

def reader_from_fd(fd: int) -> FileReader:
    fr = object.__new__(FileReader)
    fr._fd = fd
    return fr

这样一来,我们之前通过规范对象创建过程所获得的所有优势都丢失了。 reader_from_fd 的类型签名接收的只是一个普通的 int ,它甚至无法向调用者建议该如何传入的正确的 int 类型。

测试也变得麻烦多了,因为当我们想要在测试中获取 FileReader 的实例而不做实际的文件 I/O 时,都必须打桩替换自己的 fileio.open 副本,即使我们可以(例如)为测试目的在多个 FileReader 之间共享一个文件描述符。

上述例子都假定 fileio.open 是同步操作。但有许多网络资源实际上只能通过异步(因此:可能缓慢,可能容易出错)API 获得,虽然这可能是一个 假设性 [2] 问题。如果你曾经想要写出 async def __init__(self): ... ,那么你已经在实践中碰到了这种限制。

要全面描述这种方法的所有问题,恐怕得写一本关于面向对象设计哲学的专著。所以我简单总结一下:所有这些问题的根源其实是相同的——我们把“创建数据结构”这个行为与“这个数据结构常见的副作用”紧密地绑定在了一起。既然说是“常见的”,那就意味着它们并非“总是”相关联的。而在那些并不相关的情况下,代码就会变得笨重且容易出问题

总而言之,定义 __init__ 是一种反模式,我们需要一个替代方案。







请到「今天看啥」查看全文