chapter8.2、面向对象三要素--继承
继承Inheritance
单继承
个体继承自父母,继承父母的属性,但也会有自己的属性
面向对象里,属性和方法可以从从父类继承,这样能减少代码,能复用就复用,子类复用的属性不能表达他自己的属性时,再为子类增添属性。
父类father,也称为 基类base,超类super,它们意思相同
子类son,派生类sub,
class Creatures: def __init__(self,name): self._name = name def shout(self): print('{} shout'.format(self.__class__.__name__)) @property def name(self): return self._name a = Creatures('monster') a.shout() class Cat(Creatures): pass cat = Cat("garfield") cat.shout() print(cat.name) class Pig(Creatures): pass pig = Pig('peiqi') pig.shout() print(pig.name)
表达方法 :
class 子类名(基类1[,基类2,.....]):
语句块
class Person: pass class Person(object) pass
以上两种等价
object 祖先,根基类,唯一没有父类的,所有的类都继承自object,python3中全部类最终继承自object,如果不写父类,父类就是object,Python3之前不是
python 支持多继承,查看继承的特殊属性和方法有
__base__ 类的基类,只往上找一层,返回类对象
__bases__ 类的基类元祖,不会找到object,但在中间的类都会包装成元组返回
__mro__ 返回继承路线,返回所有元祖,包括object,也可以mro()
mor() 方法,返回的是列表
__subclasses__() 类的子类列表
class Animal: __COUNT = 100 HEIGHT = 0 def __init__(self,age,weight,height): self.__COUNT += 1 self.age = age self.__weight = weight#实例的私有属性,类不会保存 self.HEIGHT = height def eat(self): print('{} eat'.format(self.__class__.__name__)) def __getweight(self): print(self.__weight) @classmethod def showcount1(cls): print(cls) #print(cls.__dict__) print(cls.__COUNT,'--------1') @classmethod def __showcount2(cls): print(cls.COUNT,'---------2') def showcount3(self): print(self.__COUNT,'-------3') class Pig(Animal): NAME = 'PAGE' __COUNT = 300 #p = Pig()###创建实例访问的属性为Pig的类,Pig没有,就去父类Animal里找,要求有参数 p = Pig(5,50,30)#创建实例 p.eat()##实例所属的类Pig没有该方法,但是Animal有,调用 print(p.HEIGHT)##实例自身的属性 #print(p.__COUNT)##对象的属性是储存在自己字典里的,只不过换了名字 #p.__getweight()##该方法属于它的类,但是类隐藏了该属性,所以调不到 p.showcount1()##该方法属于类的父类,且未隐藏,可以调,返回100,该方法是Animal类调用的__count,不会去实例里边找,所以返回类里的隐藏变量100 #p.showcount2()##该方法属于它的类,但是类隐藏了该属性,所以调不到 p.showcount3()##返回101,对象调用类的方法,首先找的是对象的字典,谁调用,去谁那找 ##信息都藏在字典里! print('{}'.format(Animal.__dict__)) print('{}'.format(Pig.__dict__)) print(p.__dict__) print(p.__class__.mro())
继承中的访问控制
只要是继承,自己没有的,就可以到父类中找,私有的和类相关,私有属性的更改就要在类下更改
私有的都是不可访问的,但本质时放在了属性或类的字典__dict__中,可以找到,黑魔法,慎用,
谁调用谁的self和cls
继承时,公有的,子类和实例都可以随意访问;隐藏的,子类和实例都不可直接访问,私有不会和子类父类分享,只有自己用,自己类下的方法都可以访问这个私有变量
子类实例都不可访问私有属性,私有一旦出了类,就不能访问了
其他语言有的严格限制访问
方法的重写,覆盖override
继承修改了属性,就是重写,自己类下的方法也可以覆盖
class Creatures: def shout(self): print('Animal shout') class Pig(Creatures): def shout(self): print('hengheng') def shout(self): print(super()) print(super(Pig,self))##和上一种相同 super().shout()##子类调父类的方法,以下两种与之相同 super(Pig,self).shout() self.__class__.__base__.shout(self)##不推荐使用此方法 c = Creatures() c.shout() pig = Pig() pig.shout() print(c.__dict__) print(pig.__dict__) print(Creatures.__dict__) print(Pig.__dict__)
子类可以使用super()访问父类的属性,super() 等价于 super(父类名,self)
静态方法和类方法都与之相同,都可以覆盖
class Creatures: @classmethod def class_method(cls): print('class method creatures') @staticmethod def static_method(): print('static method creatures') class Pig(Creatures): @classmethod def class_method(cls): print('class method pig') @staticmethod def static_method(): print('static method pig') p = Pig() p.class_method() p.static_method()
继承中的初始化
本身有初始化方法,初始化不会自动调父类的初始化方法。
如果没有,就会调父类的初始化方法 ,父类有init方法,子类也调用是好习惯,不调用实例可能会少父类的实例的属性
class Animal: def __init__(self,age): print('animal init') self.age = age def show(self): print(self.age) class Cat(Animal): def __init__(self,age,weight): super().__init__(age)##放在此处,age返回11,先执行Animal的初始化,在执行Cat的age,所以c的age是11 print('cat init') self.age = age +1##此处执行时age为10加11 self.weight = weight #super().__init__(age) ##如果放在此处,返回的为10,首次执行Cat的初始化,将c的age设置为了11,再执行Animal的初始化,传入的是参数age,不是c的age,所以又覆盖了c的属性,输出10 c = Cat(10,5) c.show()
调用父类的初始化方法的顺序会影响实例的属性,应当注意
class Animal: def __init__(self,age): print('animal init') self.__age = age def show(self): print(self.__age) class Cat(Animal): def __init__(self,age,weight): super().__init__(age) print('cat init') self.__age = age +1 self.__weight = weight #super().__init__(age) c = Cat(10,5) c.show() print(c.__dict__)
实例调用类的私有方法调用时产生的属性放在实例中,但是名字与类有关,最好不要用。
父类的初始化方法调用后,使用的实例是当前类的实例,父类不产生实例,子类调用他父类的的方法
主要原则:自己的私有属性,就用自己的方法读取修改,不要使用父类和派生类或者其他类的方法
python的不同版本的类
python2.2之前,没有类的共同祖先,之后,引入了object 所有类的共同祖先,
python2.2之后,分为古典类和新式类。
python3中都是新式类,都继承自object
dir 所有属性的列表
多继承
OCP原则,多‘继承’,少修改
在子类上对基类的增强,实现多态,
多态:在面向对象中,父类,子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,就是多态
一个类继承自多个类就是多继承,它将具有多个类的特征
多继承弊端
复杂性,只要不是单一继承,就会面对复杂性这一问题
二义性,如果继承自两个类,两个类里都有相同的方法,子类该继承那个方法,就产生了二义性
C++支持多继承,Java舍弃了多继承
解决途径
排除二义性,要遍历,方法是深度优先,或广度优先
菱形选择问题,类D继承自B和C,B和C又继承自A,当然,类的继承顺序可以比这更复杂
2.2之前,最早是深度优先,先根再左在右
2.2古典类继承路线中重复的以最后为准,但不能解决问题
python2.3 之后,C3算法,可以解决二义性问题,还可以抛异常,解决了继承的单调性,出现二义性就阻止它,求得的MRO本质就是为了线性化,且确定顺序,
多继承使用时要注意
python语法是可以多继承,但是它是解释执行,只有执行到时才会发现错误。
尽量避免多继承,不论语言是否支持
团队协作开发,引入多继承,代码可能会不可控
python开发要求较高,只有运行时才能提示错误,所以在编写时要团队遵守相同的规矩
Mixin
抽象方法可以不实现,继承实现,父类不具体实现,子类来覆盖它
基类中只定义,不实现,称为‘抽象方法’,在Python中,如果采用这种定义方式,子类也可以不实现,直到子类使用该方法时才报错。
import math import json import msgpack class Shape: @property def area(self): raise NotImplemented('weishixian') class Triangle(Shape): def __init__(self, a, b, c): self.__a = a self.__b = b self.__c = c @property def area(self): p = (self.__a + self.__b + self.__c) / 2 ret = math.sqrt(p*(p - self.__a)*(p - self.__b)*(p - self.__c)) return ret class Square(Shape): def __init__(self,a,b): self.__a = a self.__b = b @property def area(self): return self.__a*self.__b class Circle(Shape): def __init__(self,r): self.__r = r @property def area(self): return math.pi*(self.__r**2) t = Triangle(3,4,5) print(t.area) s = Square(2,2) print(s.area) r = Circle(1) print(r.area)
临时注入功能,可以用装饰器注入,装饰器为类灵活的增添功能;也可以Mixin,用Mixin时,混入谁,把谁放在前边,Mixin覆盖其他的属性,缺少啥补啥,一般不会太复杂
class SerializableMixin:##Mixin类 def dump(self,t = 'json'):##增添的功能,可序列化 if t == 'json': return json.dumps(self.__dict__) if t == 'msgpack': return msgpack.dumps(self.__dict__) else : return "not realize serialize" class SerializableCircleMixin(SerializableMixin,Circle): pass#多继承,放在第一位 scm = SerializableCircleMixin(4) print(scm.area) s = scm.dump('msgpack') print(s) j = scm.dump('json') print(j)
子类指向父类,画图的规则
继承的层次多了,会笨拙,
Mixin,混入其他功能,Mixin带来了其他属性和方法,Mixin本质是多继承实现的
Mixin体现的是组合的·设计思想,模式,
在面向对象的设计中,复杂功能可以抽取出来,为其他类提供功能,可以装饰或者Mixin,
从设计角度来说,多组合,少继承,为了改变mro列表
使用原则
Mixin类中不能显式的出现初始化方法__init__
Mixin通常不能单独工作,因为它是为混入其他类增加功能的
Mixin的祖先只能是Mixin类,只是为了增添功能,不是强制,但这是原则,一般都这样做
注意Mixin类通常在继承列表的第一个位置
两种方式都可以,如果要继承就使用Mixin,
PEP是python的增强提案,Python Enhancement Proposals
新特性,PEP 572,2018年7月,Guido van Rossum,python之父卸任了BDFL(benevolent dictator for life),没有指定继任者,
PEP 0 文档索引
PEP 1 协议指南
PEP 8 代码规范!!!
Python社区,新的规范的原始文档都在这里,可以访问
https://www.python.org/dev/peps/pep-0008/