摘要:本文重点不要试图在内置类型的子类中重写方法,可以继承的可拓展类寻求变通掌握多重继承中的和了解处理多重继承的一些建议。子类化的代码如下输出小结上述问题只发生在语言实现的内置类型子类化情况中,而且只影响直接继承内置类型的自定义类。
导语:本文章记录了本人在学习Python基础之面向对象篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。
本文重点:
1、不要试图在内置类型的子类中重写方法,可以继承collections的可拓展类寻求变通;一、子类化内置类型的缺点 1、内置类型的方法不会调用子类覆盖的方法
2、掌握多重继承中的MRO和Super;
3、了解处理多重继承的一些建议。
内置类可以子类化,但是内置类型的方法不会调用子类覆盖的方法。下面以继承dict的自定义子类重写__setitem__为例说明:
class ModifiedDict(dict): def __setitem__(self, key, value): super().__setitem__(key,[value]*2) a=ModifiedDict(one=1) a["two"]=2 print(a) a.update(three=3) print(a)#输出{"one": 1, "two": [2, 2], "three": 3}
从输出可以看到,键值对one=1和three=3存入a时均调用了dict的__setitem__,只有[]运算符会调用我们预先覆盖的方法。
问题的解决方式在于不去子类化dict,而是子类化colections.UserDict。
用户自定义的类应该继承collections模块,如UserDict,UserList,UserString。这些类做了特殊设计,因此易于拓展。子类化UserDict的代码如下:
from collections import UserDict class ModifiedDict(UserDict): def __setitem__(self, key, value): super().__setitem__(key,[value]*2) b=ModifiedDict(one=1) b["two"]=2 b.update(three=3) print(b)#输出{"one": [1, 1], "two": [2, 2], "three": [3, 3]}
小结:上述问题只发生在C语言实现的内置类型子类化情况中,而且只影响直接继承内置类型的自定义类。相反,子类化使用Python编写的类,如UserDict或MutableMapping就不会有此问题。
二、多重继承 1、方法解析顺序(Method Resolution Order,MRO)在多重继承中存在不相关的祖先类实现同名方法引起的冲突问题,这种问题称作“菱形问题”。Python依靠特定的顺序遍历继承图,这个顺序叫做方法解析顺序。如图,左图是类的UML图,右图中的虚线箭头是方法解析顺序。
提到类的属性__mro__,就会提到super:
super 是个类,既不是关键字也不是函数等其他数据结构。
作用:super是子类用来调用父类方法的。
语法:super(a_type, obj);
a_type是obj的__mro__,当然也可以是__mro__的一部分,同时issubclass(obj,a_type)==true
举个例子, 有个 MRO: [A, B, C, D, E, object]
我们这样调用:super(C, A).foo()
super 只会从 C 之后查找,即: 只会在 D 或 E 或 object 中查找 foo 方法。
下面构造一个菱形问题的多重继承来深化理解:
class A: def ping(self): print("A-ping:",self) class B(A): def pong(self): print("B-pong:",self) class C(A): def pong(self): print("C-PONG:",self) class D(B, C): def ping(self): print("D-ping:",self) super().ping() def pingpong(self): self.ping() super().ping() self.pong() super(B,D).pong(self) d=D() d.pingpong() print(D.mro())
输出如下:
D-ping: <__main__.D object at 0x000001B77096EAC8> A-ping: <__main__.D object at 0x000001B77096EAC8>#前两行对应self.ping()。 A-ping: <__main__.D object at 0x000001B77096EAC8>#此处super调用父类的ping方法。 B-pong: <__main__.D object at 0x000001B77096EAC8> C-PONG: <__main__.D object at 0x000001B77096EAC8>#此处从B之后搜索父类的pong() [, , , , ]#类D的__mro__,数据以元组的形式存储。
分析:d.pingpong()执行super.ping(),super按照MRO查找父类的ping方法,查询在类B到ping之后输出了B.ping()。
3、处理多重继承的建议(1)把接口继承和实现继承区分开;
继承接口:创建子类型,是框架的支柱;
继承实现:通过重用避免代码重复,通常可以换用组合和委托模式。
(2)使用抽象基类显式表示接口;
(3)通过混入重用代码;
混入类为多个不相关的子类提供方法实现,便于重用,但不会实例化。并且具体类不能只继承混入类。
(4)在名称中明确指明混入;
Python中没有把类声明为混入的正规方式,Luciano推荐在名称中加入Mixin后缀。如Tkinter中的XView应变成XViewMixin。
(5)抽象基类可以作为混入,反过来则不成立;
抽象基类与混入的异同:
抽象基类会定义类型,混入做不到;
抽象基类可以作为其他类的唯一基类,混入做不到;
抽象基类实现的具体方法只能与抽象基类及其超类中的方法协作,混入没有这个局限。
(6)不要子类化多个具体类;
具体类可以没有,或者至多一个具体超类。
例如,Class Dish(China,Japan,Tofu)中,如果Tofu是具体类,那么China和Japan必须是抽象基类或混入。
(7)为用户提供聚合类;
聚合类是指一个类的结构主要继承自混入,自身没有添加结构或行为。Tkinter采纳了此条建议。
(8)优先使用对象组合,而不是类继承。
优先使用组合可以令设计更灵活。
组合和委托可以代替混入,但不能取代接口继承去定义类型层次结构。
注:super调用知识引自
作者: mozillazg
链接:https://segmentfault.com/a/11...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/41331.html
摘要:自己定义的抽象基类要继承。抽象基类可以包含具体方法。这里想表达的观点是我们可以偷懒,直接从抽象基类中继承不是那么理想的具体方法。 抽象基类 抽象基类的常见用途: 实现接口时作为超类使用。 然后,说明抽象基类如何检查具体子类是否符合接口定义,以及如何使用注册机制声明一个类实现了某个接口,而不进行子类化操作。 如何让抽象基类自动识别任何符合接口的类——不进行子类化或注册。 接口在动态类...
摘要:组合继承前面两种模式的特点类式继承通过子类的原型对父类实例化实现的,构造函数式继承是通过在子类的构造函数作用环境中执行一次父类的构造函数来实现的。 类有三部分 构造函数内的,供实例化对象复制用的 构造函数外的,直接通过点语法添加的,供类使用,实例化对象访问不到 类的原型中的,实例化对象可以通过其原型链间接地访问到,也是供所有实例化对象所共用的。 类式继承 类的原型对象的作用就是为类...
摘要:先来一张图看看几个名词的关系构造函数原型实例原谅我的狂草字体,我手写比用电脑画快。今天我们只说原型链,所以接下来我就围绕着原型链的几个部分说起。每个函数都有一个属性借用属性存储了的原型对象。 先来一张图看看几个名词的关系 构造函数、原型、实例 showImg(https://segmentfault.com/img/remote/1460000018194082); 原谅我的狂草字体,...
阅读 3695·2021-10-15 09:42
阅读 2548·2021-09-03 10:50
阅读 1572·2021-09-03 10:28
阅读 1751·2019-08-30 15:54
阅读 2461·2019-08-30 12:46
阅读 372·2019-08-30 11:06
阅读 2764·2019-08-30 10:54
阅读 492·2019-08-29 12:59