thtl

导航

类的组合与继承

类的组合

 类的组合是什么呢,就是在一个类中调用另一个类,主要的实现方法是将一个类对象传给另一个类的构造函数,让另一个类的对象属性之中存在前一个类的对象

------------注意哈,如果传参数时传的是对象,那传入的是对象的内存地址,一下举个例子

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号线

 

posted on 2018-09-01 17:14  thtl  阅读(91)  评论(0编辑  收藏  举报