python 中抽象基类简介

简介:

python中虽然鸭子类型很强大,但鸭子类型也有着不足。具体关于分类可以参照水禽的分类理解。

因此,参照水禽的分类学演化,我建议在鸭子类型的基础上增加白鹅类型(goose typing)。白鹅类型指,只要 cls 是抽象基类,即 cls 的元类是 abc.ABCMeta,就可以使用 isinstance(obj, cls)

collections.abc 中有很多有用的抽象类(Python 标准库的 numbers 模块中还有一些)。

与具体类相比,抽象基类有很多理论上的优点(例如,参阅 Scott Meyer 写的《More Effective C++:35 个改善编程与设计的有效方法(中文版)》的“条款 33:将非尾端类设计为抽象类”,英文版见 http://ptgmedia.pearsoncmg.com/images/020163371x/items/item33.html),Python 的抽象基类还有一个重要的实用优势:可以使用 register 类方法在终端用户的代码中把某个类“声明”为一个抽象基类的“虚拟”子类(为此,被注册的类必须满足抽象基类对方法名称和签名的要求,最重要的是要满足底层语义契约;但是,开发那个类时不用了解抽象基类,更不用继承抽象基类)。
这大大地打破了严格的强耦合,与面向对象编程人员掌握的知识有很大出入,因此使用继承时要小心。

有时,为了让抽象基类识别子类,甚至不用注册。

其实,抽象基类的本质就是几个特殊方法。例如:
python 中抽象基类简介_第1张图片
可以看出,无需注册,abc.Sized 也能把 A 识别为自己的子类,只要实现了特殊方法 __len__ 即可。(要使用正确的句法和语义实现,前者要求没有参数,后者要求返回一个非负整数,指明对象的长度;如果不使用规定的句法和语义实现特殊方法,如 __len__,会导致非常严重的问题)。

最后我想说的是:如果实现的类体现了 numbers、collections.abc 或其他框架中抽象基类的概念,要么继承相应的抽象基类(必要时),要么把类注册到相应的抽象基类中。开始开发程序时,不要使用提供注册功能的库或框架,要自己动手注册;如果必须检查参数的类型(这是最常见的),例如检查是不是“序列”,那就这样做:

isinstance(the_arg, collections.abc.Sequence)

此外,不要在生产代码中定义抽象基类(或元类)……如果你很想这样做,我打赌可能是因为你想“找茬”,刚拿到新工具的人都有大干一场的冲动。如果你能避开这些深奥的概念,你(以及未来的代码维护者)的生活将更愉快,因为代码会变得简洁明了。

除了提出“白鹅类型”之外,Alex 还指出,继承抽象基类很简单,只需要实现所需的方法,这样也能明确表明开发者的意图。这一意图还能通过注册虚拟子类来实现。

此外,使用 isinstanceissubclass 测试抽象基类更为人接受。过去,这两个函数用来测试鸭子类型,但用于抽象基类会更灵活。毕竟,如果某个组件没有继承抽象基类,事后还可以注册,让显式类型检查通过。

然而,即便是抽象基类,也不能滥用 isinstance 检查,用得多了可能导致代码异味,即表明面向对象设计得不好。在一连串if/elif/elif 中使用 isinstance 做检查,然后根据对象的类型执行不同的操作,通常是不好的做法;此时应该使用多态,即采用一定的方式定义类,让解释器把调用分派给正确的方法,而不使用 if/elif/elif 块硬编码分派逻辑。(具体使用时,上述建议有一个常见的例外:有些 Python API 接受一个字符串或字符串序列;如果只有一个字符串,可以把它放到列表中,从而简化处理。因为字符串是序列类型,所以为了把它和其他不可变序列区分开,最简单的方式是使用 isinstance(x, str) 检查。)

另一方面,如果必须强制执行 API 契约,通常可以使用 isinstance 检查抽象基类。“老兄,如果你想调用我,必须实现这个”,正如本书技术审校 Lennart Regebro 所说的。这对采用插入式架构的系统来说特别有用。在框架之外,鸭子类型通常比类型检查更简单,也更灵活。

注意:
抽象基类是用于封装框架引入的一般性概念和抽象的,例如“一个序列”和“一个确切的数”。(读者)基本上不需要自己编写新的抽象基类,只要正确使用现有的抽象基类,就能获得 99.9% 的好处,而不用冒着设计不当导致的巨大风险。

下一次来说明抽象基类的继承和定义自己的抽象基类。

你可能感兴趣的:(python,特殊方法)