类的组合与继承
类的组合
类的组合是什么呢,就是在一个类中调用另一个类,主要的实现方法是将一个类对象传给另一个类的构造函数,让另一个类的对象属性之中存在前一个类的对象
------------注意哈,如果传参数时传的是对象,那传入的是对象的内存地址,一下举个例子
class a1: def __init__(self,b): self.b = b self.b.test = 10 class b1: def __init__(self): self.test = 1 b = b1() print(b.test) a = a1(b) //传入的是类b1的对象 --- 实际传的是对象b的内存地址 print(b.test)
结果
1
10 //因为在类a1中添加了一个属性 self.b ,它接收的是传入的对象b的内存地址,然后 self.b.test = 10 ,相当于对对象b的属性进行了修改,所以后面输出 b.test 时,输出是10
下面的例子是组合的例子
class School: def __init__(self,addr,name): self.addr = addr self.name = name class Teacher: def __init__(self,name,cls,age,sch): self.name = name self.cls = cls self.age = age self.sch = sch s1 = School('石牌','华师本部') s2 = School('番禺','华师番禺大学城分校') s3 = School('大沥','华师狮山大学城分校') t = Teacher('djh','python',19,s1) //你看,这里传的是School类的对象,实际传的是对象s1的内存地址 print('''这个老师叫%s, 教的科目是%s, 今年%s岁, 就职于%s的%s''' % (t.name,t.cls,t.age,t.sch.addr,t.sch.name))
结果
这个老师叫djh,
教的科目是python,
今年19岁,
就职于石牌的华师本部
类的继承
python类的继承和c++类的继承有挺大区别的,pyhton的继承其实不叫继承,因为子类里面根本没有从父类里面获得任何东西,所谓的继承只是给子类多了一个查找的字典属性,先在自己的类属性里找,找不到,再按顺序去父类的属性字典里找(注意,只是找父类的类属性字典,是不会去找父类的对象的属性字典),这里连构造函数都可以去查找
class Dad: money = 1000 def __init__(self,name): self.name = name print('儿子。我是你爸爸') print('你爸爸我叫%s' % self.name) def hit_son(self): print('你让爸爸我很失望,所以爸爸要打你个死儿子') class Son(Dad): //继承的格式,直接在子类后面加括号,写上父类,可以有多个父类 pass print(Son.__dict__) print(Son.money) //先在自己的类属性字典里找,找不到,然后到父类的类属性字典里找,找的到,返回 # s = Son() //因为子类没有构造函数,所以去父类找,找得到,但是父类的构造函数除了self还有一个参数,所以这样写会报错 s = Son('djh') //这样写就对了 print(s.__dict__)//这里必须提一句,对象的属性字典和所属类的属性字典没有必然关系,即使在该类的构造函数里没有的属性在该对象中都有可能存在(可以通过父类的构造函数创建属性),这里就是这个情况 s.hit_son() //在先在自己的对象属性里找,找不到,再到该对象所属类的类属性字典里找,还是找不到,然后到父类的类属性字典里找,找得到,调用
结果
{'__module__': '__main__', '__doc__': None} //看咯,这个字类Son是啥都没有
1000
儿子。我是你爸爸
你爸爸我叫djh
{'name': 'djh'} //这里就是我在上面讲的,即使在该类的构造函数里没有的属性在该对象中都有可能存在(可以通过父类的构造函数创建属性),在自己的子类里的构造函数有这个属性吗?明显没有嘛,所以说对象
//的属性字典和所属的类之间没有必然的关系,是彼此独立的但类又会对对象影响
你让爸爸我很失望,所以爸爸要打你个死儿子
当然啦,我都说了继承其实根本不是c++的继承,而是给你多一个查找的属性字典而已,所以如果在字类中有和父类同名的属性或者函数,自然是用子类自己的(先在自己的类属性字典里找,找不到才去父类的找,但问题是在自己的子类属性字典里找得到呀,自然就不需要去父类找了),还有噢,我最怕有些小朋友以外如果同名就是覆盖掉父类的方法,这是大大大傻逼的想法,不会覆盖掉父类自己方法,父类字典调用该方法时直接用父类自己的就ok啦
class Dad: money = 1000 def __init__(self,name): self.name = name print('儿子。我是你爸爸') print('你爸爸我叫%s' % self.name) def hit_son(self): print('你让爸爸我很失望,所以爸爸要打你个死儿子') class Son(Dad): money = 10000000000 pass s = Son('djh') print(s.money) print(Son.money) print(Dad.money)
结果
儿子。我是你爸爸
你爸爸我叫djh
10000000000
10000000000
1000 //你自己看,有覆盖掉吗?明显没有好不好
继承的顺序
在python3中,继承也就是查找父类的顺序是由底层的 c3 算法(知道就ok,这个算法是怎么实现的你就不用操心了)生成一个MRO列表,然后根据列表的顺序一个一个的查找知道找到为止,找不到自然就报错啦
但是有个东西是必须了解的,就是这个c3算法是有两个原则,记住这些个原则就够了
1:一定是子类先于父类被查找
2:如果有多个父类则按照写在继承参数的顺序从左到右查找
class A: pass class B(A): pass class C(A): pass class D(B,C): pass class E: pass class F(D,E): pass print(F.__mro__) 结果
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.E'>, <class 'object'>)
F --> D --> B --> C --> A --> E --> object
侧重讲一下为什么是B-->C-->A,因为在查找完B的时候,理应要查找B的父类也就是A,但由于两个原则中的一个,子类必须先于父类被查找,A不但是B的父类,同时也是C的父类,所以这个时候如果找A的话就不满足原则
之一,所以先去找C然后再回去找A ,其实我觉得查找的规则就是 深度优先加上那两个规则
在python3中全部类都是新式类,而新式类的继承方式就是c3算法的方式,在python2中的继承中,有新式类和旧式类两种区分,在python2中,只有让类继承 object 这个类(或者是变量?)才叫新式类,比如 class A(object): 这种就叫新式类,新式类才用c3算法继承(也就是和python3中的类的继承顺序相同),其实这些就只是了解一下就ok
如果在python2中,旧式类的继承,继承的查找顺序就真的是深度优先,没有那两个原则
继承的两种格式
1:把共有的属性提取出来形成一个新的类,比如猫,狗都会吃喝拉撒,那就把吃喝拉撒提取出来建立一个动物类,然后猫和狗都是继承这个动物类就ok
class Animal: def eat(self): print('%s正在吃' % self.name) def la(self): print('%s正在拉' % self.name) class Dog(Animal): def __init__(self,name): self.name = name class Cat(Animal): def __init__(self,name): self.name = name d1 = Dog('小狗') c1 = Cat('小猫') d1.eat() d1.la() c1.eat() c1.la()
结果
小狗正在吃
小狗正在拉
小猫正在吃
小猫正在拉
这种格式其实就是代码重用,不用将相同的代码写那么多次,但是有一定的危险?(不太清除,因为在c++里老师们都是鼓励这种做法的,在python我就不知道了)
2:第二种格式,有个特别牛逼的名字,说出去都可以装逼,接口继承和归一化设计,牛逼吧,其实也就是c++里的抽象类继承
import abc class All_file(metaclass=abc.ABCMeta): //python里面是没有抽象类这个概念的,所以必须引入一个第三方模块 abc 然后写些什么,格式是怎么样的,这里都有,自己看,这个All_file类就是一个抽 @abc.abstractmethod //象类,里面的加上装饰器的函数都是纯虚函数,不需要具体实现,只是拿来限定它的派生类的 def read(self): pass @abc.abstractmethod def write(self): pass class Mem(All_file): //这个类继承了抽象类,所以必须把两个纯虚函数都实现,不然就报错 def read(self): print('read from Mem') def write(self): print('write from Mem') m = Mem() m.read() m.write()
结果
read from Mem
write from Mem
任何在子类中调用父类的方法呢?下面会将,不用担心
最直接也是最明显的在子类中调用父类的方法:
class Jtgj: def __init__(self,name,price,speed): self.name = name self.speed = speed self.price = price def run(self): print('老司机开车了,注意扶稳别晕车了!') class Subway(Jtgj): def __init__(self,name,price,speed,line): Jtgj.__init__(self,name,price,speed) //在子类的构造函数中调用父类的构造函数,因为子类也要有name speed 之类的属性,如果在子类中又写一遍的话会显得很多余,所以在这里直接调用父类的
//构造函数,因为是直接通过父类来调用构造函数的嘛,所以是不会自动传对象给self的(因为根本就没有对象,怎么传?)所以就要自己手动传,这里直接将
//形式参数的self传过来就ok,之前讲过传对象传的都是内存地址,所以这个传过来的self其实就是主函数调用Subway类的那个对象,对!传来传去一直都是
//同一个,聪明的孩子已经发现,这样写的话好像和继承没啥关系了,Subway继承不继承Jtgj都无所谓了,事实上确实如此,所以这种写法没那么好,并且如
//果一个不小心碰到需要将父类名字给改掉的情况,噢,那就gg了,类内的逻辑代码都得改掉,所以才有下面我将要说的最好的写法 self.line = line def run(self): Jtgj.run(self) //这里和上面那一长串写的一样 print('现在开的是%s%s' % (self.name,self.line)) s = Subway('广州地铁',3,'10km/min','3号线') s.run()
结果
老司机开车了,注意扶稳别晕车了!
现在开的是广州地铁3号线
最好最好的方法在子类中调用父类的方法是:如下
class Jtgj: def __init__(self,name,price,speed): self.name = name self.speed = speed self.price = price def run(self): print('老司机开车了,注意扶稳别晕车了!') class Subway(Jtgj): /因为下面用了super()函数,所以这里的继承是必须的 def __init__(self,name,price,speed,line): # super().__init__(name,price,speed) //这里super()函数是用来替代继承的父类的,里面什么都不填其实是有默认已经写在里面的 super(__class__,self),如果什么都不写默认就是这样,
//所以这种写法和下面一行的写法是一模一样的(__class__在这里就是返回Subway类) super(Subway,self).__init__(name,price,speed)//用super()函数有什么好处呢,如果用super()方法,则必须让 Subway类 继承父类Jtgj,如果不继承就会报错,因为super()函数本质上就是
//代替了父类,因为有这种特性,所以就和继承扯上关系了,而不像上面那种简单粗暴的写法,连继承都不用都可以实现,还有就是如果父类换名字
//了,我的逻辑代码不用做任何改变,直接将父类名字改好就ok self.line = line def run(self): # super().run() super(Subway,self).run() print('现在开的是%s%s' % (self.name,self.line)) s = Subway('广州地铁',3,'10km/min','3号线') s.run()
结果
老司机开车了,注意扶稳别晕车了!
现在开的是广州地铁3号线