python面对对象之继承
继承
继承的概念
在python中,当类之间的差别比较大时,或其中一个类是另一个类的组件时(如Point类Square类的关系),组合就完全可以胜任。但当要设计的新类有相同功能却又有些扩展时,这时候就需要继承了。新建的类称为子类或派生类(Subclass), 被继承的类被称为父类、基类、超类(Base class、Super class)。
继承的作用
子类继承父类后,就继承了父类的全部功能,而且自己可以扩展功能,覆盖重写父类已有功能,大大提高了代码的重用率。
单继承
单继承很好理解,即只继承单一父类。子类直接调用父类的功能
class Planet(object): """创建一个行星类""" def __init__(self, name): self.name = name def rotation(self): print('%s在自转' % self.name) class Earth(Planet): """创建一个地球类""" def __init__(self, name, star): super().__init__(name) # 初始化要调用super()的初始化方法 self.star = star def revolution(self): print('%s正在绕%s公转' % (self.name, self.star)) earth = Earth('地球', '太阳') earth.rotation() earth.revolution()
地球在自转
地球正在绕太阳公转
多继承
多继承是子类同时继承多个父类。
查看继承的方法
>>> class A: ... pass ... >>> class B: ... pass ... >>> class C(A, B): ... pass ... >>> A.__base__() # 默认继承object类 <object object at 0x7f1cb33460a0> >>> C.__base__() # 只能查看第一个继承的父类 <__main__.A object at 0x7f1cb1641e10> >>> C.__bases__ # 查看直接继承的所有类 (<class '__main__.A'>, <class '__main__.B'>)
多继承的问题
在编程过程中,应该尽量避免使用多继承,多继承增加了可选方案和系统的复杂度,如会增加潜在的命名冲突问题,就如下面的"菱形问题"又称为"钻石继承问题"。python的解决方法是它内部会按照一定顺序遍历继承图,确定调用哪个方法。这个顺序叫作方法顺序解析顺序(Method Resolution Order, MRO)。在python通过__mro__或mro()方法查看。

# 钻石继承问题 class A(object): def ping(self): print('ping', self) class B(A): def pong(self): print('pong', self) class C(A): def pong(self): print('PONG', self) class D(B, C): def ping(self): print('pingpong', self) def pingpong(self): self.ping() # 优先寻找自己的ping方法 super().ping() # super按照__mro__ or mro()方法列表的顺序寻找方法,从B->C->A寻找, 此处应该寻找到B的方法 self.pong() # 从自己的继承树寻找, 以自己为节点开始寻找 super().pong() # 从自己的直系第一个父类B开始寻找pong方法 C.pong(self) # 直接调用C类的pong方法,并把D自己作为显式参数传进去 d = D() d.pingpong() print(D.__mro__)
pingpong <__main__.D object at 0x7f5246967978> ping <__main__.D object at 0x7f5246967978> pong <__main__.D object at 0x7f5246967978> pong <__main__.D object at 0x7f5246967978> PONG <__main__.D object at 0x7f5246967978> (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
六边形问题,以F为节点,找寻顺序如上右图所示。
# 六边形问题 class A(object): def run(self): print('A is running') class B(A): # def run(self): # print('B is running') pass class C(A): def run(self): print('C is running') class D(B): # def run(self): # print('D is running') pass class E(C): def run(self): print('E is running') class F(D, E): # def run(self): # print('F is running') pass f = F() f.run()
E is running
抽象类
抽象类特征:抽象类必须通过继承的方式才能使用,抽象类本身无法被实例化,抽象类中的子类必须实现抽象方法。
抽象类是对类的抽象,主要定义了基本类和基础方法,它可以不实现任何方法(当然也可以实现,子类可以通过super()调用),不实现任何方法的抽象类就类似Java里的接口,可以规范子类的编程,也为代码提供了逻辑和解耦能力。以下例子来自流畅的python第十一章。
1 import abc 2 import random 3 4 5 # class Container(metaclass=abc.ABCMeta): # 此处可以也可以如此实现抽象类 6 class Container(abc.ABC): 7 """容器类""" 8 9 @abc.abstractmethod 10 def load(self, iterable): 11 """从一个可迭代对象添加元素""" 12 13 @abc.abstractmethod 14 def pick(self): 15 """删除一个元素并返回值""" 16 17 def has_element(self): 18 """抽象类具体方法, 至少有一个元素,返回真""" 19 return bool(self.get_elements()) 20 21 def get_elements(self): 22 """返回所有元素, 放在一个列表中""" 23 items = [] 24 while 1: 25 try: 26 items.append(self.pick()) 27 except LookupError: 28 break 29 self.load(items) 30 return items 31 32 33 # 创建Container的子类 34 class NumbersBalls(Container): 35 """创建一个数字球类, 可以模拟不放回的抽奖一类的问题""" 36 def __init__(self, iterable): 37 self._balls = list(iterable) # 对象内部不能直接引用外部传来的可迭代对象,要自己单独维护一份 38 39 def load(self, iterable): 40 self._balls.extend(iterable) 41 42 def pick(self): 43 """返回随机的一个元素""" 44 try: 45 position = random.randrange(len(self._balls)) # 如果元素为空,会报ValueError异常 46 except ValueError: 47 raise LookupError('当前元素为空') 48 return self._balls.pop(position) 49 50 def has_element(self): 51 """重写抽象类的方法,太费力了""" 52 return bool(self._balls) # 判断元素是否为空委托给列表的内部方法 53 54 def get_elements(self): 55 """重写父类方法""" 56 return tuple(sorted(self._balls)) # 返回一般也不能直接返回对象维护的数据,要重新创建一份,防止外部修改 57 58 59 balls = NumbersBalls(range(10)) 60 print(balls.pick()) 61 print(balls.pick()) 62 print(balls.get_elements())
1
8
(0, 2, 3, 4, 5, 6, 7, 9)
虚拟子类
虚拟子类是不用继承抽象基类,即不会从抽象基类获得任何属性和方法,但是可以被isinstance和issubclass识别的类。注册虚拟子类的方法是调用抽象类的register方法或是在register方法作为类装饰器装饰子类。python内部是使用register方法注册虚拟子类的,如Sequece.register(tuple)等等。
1 # class Container(metaclass=abc.ABCMeta): # 此处可以也可以如此实现抽象类 2 class Container(abc.ABC): 3 """容器类""" 4 5 @abc.abstractmethod 6 def load(self, iterable): 7 """从一个可迭代对象添加元素""" 8 9 @abc.abstractmethod 10 def pick(self): 11 """删除一个元素并返回值""" 12 13 def has_element(self): 14 """抽象类具体方法, 至少有一个元素,返回真""" 15 return bool(self.get_elements()) 16 17 def get_elements(self): 18 """返回所有元素, 放在一个列表中""" 19 items = [] 20 while 1: 21 try: 22 items.append(self.pick()) 23 except LookupError: 24 break 25 self.load(items) 26 return items 27 28 29 @Container.register # 注册为Container的虚拟子类 30 class BallList(list): 31 32 load = list.extend # 列表的方法 33 34 def pick(self): 35 if self: 36 return self.pop(random.randrange(len(self))) 37 else: 38 raise LookupError('列表为空') 39 40 def get_elements(self): # 获取所有元素 41 return tuple(self) 42 43 def has_element(self): # 判断是否有值 44 return bool(self) 45 46 47 # Container.register(BallList) # 虚拟子类也可以如此注册 48 print(issubclass(BallList, Container)) 49 print(isinstance(BallList(), Container)) 50 b = BallList(range(10)) 51 print(b.pick()) 52 print(b.get_elements()) 53 print(BallList.mro()) # 虚拟子类不在mro表中, 表明不是继承
True True 0 (1, 2, 3, 4, 5, 6, 7, 8, 9) [<class '__main__.BallList'>, <class 'list'>, <class 'object'>]
鸭子类型
"鸭子类型"并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。python作为一种动态语言很崇尚鸭子类型,像java里接口(虽然python没有interface这个关键字,但是python也有抽象基类这种类似接口的替代品)这种严格要求子类规范的代码运用其实不算多。python里处处遵循的是协议,它是文档定义和约定形成,属于一种非正式的接口,所以它也没有正式接口和抽象基类的强制规定约束力。
遵循协议有很大的灵活性,可以部分实现协议,就可以利用协议带来的好处,例如最常见的序列协议。 如下面的例子,自定义的类只是实现了__getitem__和__len__方法,立刻可以调用内置函数len()和对对象进行切片。
class MyList(object): """没什么用,只是为了实现序列协议""" def __init__(self, iterator): self.lst = list(iterator) def __getitem__(self, pos): return self.lst[pos] def __len__(self): return len(self.lst) mylst = MyList(range(10)) print(len(mylst)) print(mylst[::-1])

浙公网安备 33010602011771号