专栏名称: VPointer
软件开发
51好读  ›  专栏  ›  VPointer

Python学习之路31-继承的利弊

VPointer  · 掘金  ·  · 2018-07-08 09:58

正文

阅读 8

Python学习之路31-继承的利弊

《流畅的Python》笔记

本篇是“面向对象惯用方法”的第五篇,我们将继续讨论继承,重点说明两个方面:继承内置类型时的问题以及多重继承。概念比较多,较为枯燥。

1. 继承内置类型

内置类型(C语言编写)的方法通常会忽略用户重写的方法 ,这种行为体现在两方面:

  • 内置类型 A 的子类 ChildA 即使重写了 A 中的方法,当 ChildA 调用这些方法时,也不一定调用的就是重写的版本,而依然可能调用 A 中的版本;
  • 内置类型 B 调用 ChildA 的方法时,调用的也不一定是被 ChildA 重写的方法,可能依然会调用 A 的版本。

dict __getitem__ 方法为例,即使这个方法被子类重写了,内置类型的 get() 方法也不一定调用重写的版本:

# 代码1.1
>>> class MyDict(dict):
...     def __getitem__(self, key):
...         return "Test"   # 不管要获取谁,都返回"Test"
...    
>>> child = MyDict({"one":1, "two":2})
>>> child
{'one': 1, 'two': 2}    # 正常
>>> child["one"]
'Test'    # 此时也是正常的
>>> child.get("one")
1   # 这里就不正常了,按理说应该返回"Test"
>>> b = {}
>>> b.update(child)
>>> b  # 并没有调用child的__getitem__方法
{'one': 1, 'two': 2}

这是在CPython中的情况,这些行为其实违背了面向对象编程的一个基本原则,即应该始终从实例所属的类开始搜索方法,即使在超类实现的类中调用也应该如此。但实际是可能直接调用基类的方法,而不先搜索子类。这种设定并不能说是错误的,这只是一种取舍,毕竟这也是CPython中的内置类型运行得快的原因之一,但这种方式就给我们出了难题。这种问题的解决方法有两个:

  • 重写从内置类型继承来的所有方法(要真这样,那我还继承干啥?),或者查看源码,把相关的方法都给重写了(谁的记性能这么好?);
  • 第二种方法才是 推荐 的方法: 如果要继承内置类型,请从 collections 模块中继承 ,比如继承自 UserList UserDict UserString 。这些类不是用C语言写的,而是用纯Python写的,并且严格遵循了上述面向对象的原则。如果上述代码中的 MyDict 继承自 UserDict ,行为则会合乎预期。

强调 :本节所述问题只发生在C语言实现的内置类型内部的方法委托上,而且只影响直接继承内置类型的自定义类。如果子类继承自纯Python编写的类,则不会有此问题。







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