一、静态属性
实质上就是数据属性
@property跟实例的函数属性绑定后,会把函数属性封装成数据属性。对象仍能访问对象的属性,也可以访问类的属性
class Room: tag=1 def __init__(self,name,owner,width,length,heigh): self.name=name self.owner=owner self.width=width self.length=length self.heigh=heigh @property #class提供的,该关键词看起来像是把函数属性变成了数据属性 def cal_area(self): # print(‘%s 住的 %s 总面积是%s‘ % (self.owner,self.name, self.width * self.length)) return self.width * self.length print(r1.cal_area) #return了一个面积,用了@property后就不能用r1.cal_area()去调用,要用r1.cal_area。
效果跟调数据属性一样,但是看不出这是个函数,这就能隐藏它背后的逻辑
二、类方法
@classmethod跟类绑定,能访问类的函数、数据,不能访问实例的属性
class Room: tag=1 def __init__(self,name,owner,width,length,heigh): self.name=name self.owner=owner self.width=width self.length=length self.heigh=heigh @classmethod #把它变成一个供类使用的方法,不需要self参数。应用场景:跟实例没有关系,类级别的操作 def tell_info(cls,x): #cls参数是python自动加入的,接受的是一个类名 print(cls) print(‘--》‘,cls.tag,x)#print(‘--》‘,Room.tag) #类调用方法的折中办法,需要先实例化一个对象 #def tell_info(self,x): # print(‘---->‘,self.tag,x) #使用@classmethod关键字 Room.tell_info(10) #直接调用,返回类的内存地址、1以及10 #老方法 r1=Room(‘厕所‘,‘alex‘,100,100,100000) Room.tell_info(r1,10) Room.test(1) #1.name执行不了,self原则上接受任何参数,但是实际上它是有意义的,它已经跟实例绑在一起了 #注意 #r1.tell_info(10)#能够调用,但是不建议。因为@classmethod的场景是:跟实例没有关系,类级别的操作
三、静态方法
@staticmethod跟类、实例都不绑定,它是类的工具包。名义上归属类管理,不能访问类变量和实例变量
class Room: tag=1 def __init__(self,name,owner,width,length,heigh): self.name=name self.owner=owner self.width=width self.length=length self.heigh=heigh @staticmethod #跟类和实例都剥离开,做一些跟类和实例没关的事 def wash_body(a,b,c): print(‘%s %s %s正在洗澡‘ %(a,b,c)) def test(x,y): #跟@staticmethod的区别 print(x,y) Room.wash_body(‘alex‘,‘yuanhao‘,‘wupeiqi‘) #直接类执行,不用通过实例 print(Room.__dict__) #有wash_body的内存地址,而test只是个普通函数 r1=Room(‘厕所‘,‘alex‘,100,100,100000) print(r1.__dict__) #没有wash_body、test的内存地址 r1.wash_body(‘alex‘,‘yuanhao‘,‘wupeiqi‘) #也能调用 Room.test(1,2) #能调用 r1.test(1,2) 不能调用,因为r1调用的时候,类会把实例r1传进去,然而这里的test是不需要r1
四、组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合。
组合指的是,在一个类中以另外一个类的对象作为数据属性
作用:做类与类之间的关联,类与类之间没有共同点,但是有关联就可以用组合
应用场景:当类与类之间有显著差别,并且较小类是较大类的组件时,用组合。如一个机器人,有胳膊类、腿类、身体类,一个大类由一个个小类组合而成
用组合写一个人的类
class Hand: pass class Foot: pass class Trunk: pass class Head: pass class Person: def __init__(self,id_num,name): self.id_num=id_num self.name=name self.hand=Hand()#类名加一个小括号就是一个对象 self.foot=Foot() self.trunk=Trunk() self.head=Head() p1=Person(‘111111‘,‘alex‘) print(p1.__dict__)# Foot,Hand,Head的函数地址都在属性字典里
用组合写学校的多个校区的课程划分
class School: def __init__(self,name,addr): self.name=name self.addr=addr def zhao_sheng(self): print(‘%s 正在招生‘ %self.name) class Course: def __init__(self,name,price,period,school): self.name=name self.price=price self.period=period self.school=school s1=School(‘北京大学‘,‘北京‘) s2=School(‘南京大学‘,‘南京‘) s3=School(‘中山大学‘,‘广州‘) # c1=Course(‘linux‘,10,‘1h‘,‘北京大学 北京‘) #你现在只是名字跟”北京大学 北京“这个字符串有关系,但是school下面的招生就跟你没什么关系 c1=Course(‘linux‘,10,‘1h‘,s1) #课程都归属于某个学校,不过这个s1写死了,因为用户应该有自己的选择 print(c1.__dict__)# 有school的对象地址 print(c1.school.name) #相当于s1.name #print(s1)==print(c1.school) 优化,增加用户选择 class School: def __init__(self,name,addr): self.name=name self.addr=addr def zhao_sheng(self): print(‘%s 正在招生‘ %self.name) class Course: def __init__(self,name,price,period,school): self.name=name self.price=price self.period=period self.school=school s1=School(‘北京大学‘,‘北京‘) s2=School(‘南京大学‘,‘南京‘) s3=School(‘中山大学‘,‘广州‘) # c1=Course(‘linux‘,10,‘1h‘,‘oldboy 北京‘) # c1=Course(‘linux‘,10,‘1h‘,s1) msg=‘‘‘ 1 北京大学 北京 2 南京大学 南京 3 中山大学 广州 ‘‘‘ while True: print(msg) menu={ ‘1‘:s1, ‘2‘:s2, ‘3‘:s3 } choice=input(‘选择学校>>: ‘) school_obj=menu[choice] name=input(‘课程名>>: ‘) price=input(‘学费>>: ‘) period=input(‘周期>>: ‘) new_course=Course(name,price,period,school_obj) print(‘课程【%s】属于【%s】学校‘ %(new_course.name,new_course.school.name))
有一种写组合很low的方式
class School: def __init__(self,name,addr): self.name=name self.addr=addr self.course_list=[] def zhao_sheng(self): print(‘%s 正在招生‘ %self.name) class Course: def __init__(self,name,price,period): self.name=name self.price=price self.period=period s1=School(‘oldboy‘,‘北京‘) s2=School(‘oldboy‘,‘南京‘) s3=School(‘oldboy‘,‘东京‘) c1=Course(‘linux‘,10,‘1h‘) c2=Course(‘python‘,10,‘1h‘) s1.course_list.append(c1) s1.course_list.append(c2) print(s1.__dict__) for course_obj in s1.course_list: print(course_obj.name,course_obj.price)
五、继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承,以下是单继承
class Dad: ‘这个是爸爸类‘ money=10 def __init__(self,name): print(‘爸爸‘) self.name=name def hit_son(self): print(‘%s 正在打儿子‘ %self.name) class Son(Dad): money = 1000000000009 def __init__(self,name,age): self.name=name self.age=age print("儿子") def hit_son(self): print(‘来自儿子类‘) # print(Son.money) # Son.hit_son()#不能调用到 # print(Dad.__dict__) # print(Son.__dict__) # s1=Son(‘alex‘)#如果自己没有__init__,则执行父类的__init__,相当于把_父类的_init__拿到子类里执行 s1=Son(‘alex‘,18)#执行自己的__init__,当子类与父类的初始化函数一样时,子类先找自己的,然后再找父类的 s1.hit_son()#儿子类自己有就用自己的
六、子类中调用父类的方法
class Vehicle: Country=‘China‘ def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print("开动啦") class Subway(Vehicle):#子类重构__init__ def __init__(self,name,speed,load,power,line):#子类重构__init__ Vehicle.__init__(self,name,speed,load,power) #继承父类的__init__ self.line=line #子类新增的属性 #为什么继承父类的__init__要传self?因为是类调用类的方法,所以类要传self(类.方法A(实例)),只有在实例化和实例调用类的方法时,才不需要手动传入自己(实例.方法A()) def show_info(self): print(self.name,self.speed,self.load,self.power,self.line) def run(self): #该方法与父类的方法重名,但是不会覆盖父类的方法 Vehicle.run(self) #在这里能调用父类的方法 print(‘%s%s号线,开动啦‘ %(self.name,self.line)) line4=Subway("广州地铁","60km/h","10000","电","4") line4.show_info() line4.run() #执行改写后的子类的run方法 line8=Vehicle("广州火车","30km/h","10","汽油") #Subway子类的run方法没有覆盖Vehicle的run方法,这里仍然调用父类的方法 line8.run()
小结:子类继承了父类的属性,如果子类定义的属性跟父类的重名了(只是在子类的属性字典里新增了一个属性),并不会把父类的属性覆盖
七、用super继承父类的属性
class Vehicle: Country=‘China‘ def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print("开动啦") class Subway(Vehicle): def __init__(self,name,speed,load,power,line): #Vehicle.__init__(self,name,speed,load,power) super(Subway,self).__init__(name,speed,load,power) #通过super调用父类的__init__ self.line=line def show_info(self): print(self.name,self.speed,self.load,self.power,self.line) def run(self): super().run() #不用传self,默认给你传进去 print(‘%s%s号线,开动啦‘ %(self.name,self.line)) line4=Subway("广州地铁","60km/h","10000","电","4") line4.show_info() line4.run() #执行改写后的子类的run方法
八、什么时候用继承
当类之间有很多相同的功能,提取共同的功能做成基类,用继承。如猫狗都有吃喝拉撒,这是动物类的共同特征,狗类、猫类都继承这个动物类
派生:派生就是在继承后,子类衍生出来的新属性,猫继承动物类的属性,但又衍生出"喵喵叫"属性
继承同时具有两种含义:
含义一:继承基类的方法,并且做出自己的改变或者扩展(代码重用),动物类、狗、猫的例子。说是代码重用,但实际上会使得子类和父类的代码出现强耦合,这常常是有害的,因为代码之间应该越独立越好。
含义二:声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法,这又叫做"接口继承",接口继承不会节省代码。
接口继承实质上是要求"做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象"---这在程序设计上,叫做归一化处理。比如Linux中一切皆文件,连cdrom也是文件,而文件只有读和写两种方法,我们不需要关心底层如何实现,只是去调用。
接口本质上是个函数,父类规定好子类必须实现什么方法,但是父类不实现。子类一旦继承父类就需要具体去实现父类规定的方法,下面来模仿一下Linux的一切皆文件:
不加abc模块进行限制的话,子类可以不具体实现父类规定的方法
#父类中规定了子类需要实现的方法read和write class All_file(metaclass=abc.ABCMeta): def read(self): pass def write(self): pass #子类具体实现父类中规定的方法 class Disk(All_file): def read(self): print(‘disk read‘) def write(self): print(‘disk write‘) class Cdrom(All_file): def read(self): print(‘cdrom read‘) def write(self): print(‘cdrom write‘) class Mem(All_file): def read(self): print(‘mem read‘) #Mem没有实现父类中规定的write方法 m1=Mem() #能够初始化 m1.read() #调用自己的方法进行读 m1.write() #Mem类自己没有实现write方法,于是就调用父类,之前说过子类的对象可以调用父类的方法。 #Mem类自己没有实现write方法,于是就调用父类,但是调用的结果没有任何效果,因为父类自己也没有实现该方法。 #为了避免这种情况,父类应该严格限制子类必须实现该方法才对,接下来看真正的接口调用
加了abc模块,mem没有实现write,实例化mem就会报错
#真正版 import abc #导入该模块 class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Disk(All_file): def read(self): print(‘disk read‘) def write(self): print(‘disk write‘) class Cdrom(All_file): def read(self): print(‘cdrom read‘) def write(self): print(‘cdrom write‘) class Mem(All_file): def read(self): print(‘mem read‘) # def write(self): # print(‘mem write‘) m1=Mem() #不能够初始化,父类检测到子类并没有实现write方法 m1.read() m1.write()
归一化的好处:方便
九、继承顺序
继承顺序 class A: # def test(self): # print(‘A‘) pass class B(A): # def test(self): # print(‘B‘) pass class C(A): # def test(self): # print(‘C‘) pass class D(B): # def test(self): # print(‘D‘) pass class E(C): # def test(self): # print(‘E‘) pass class F(D,E): # def test(self): # print(‘F‘) pass f1=F() f1.test() 结论: 经典类:F->D->B->A-->E-->C 深度优先 新式类:F-->D->B-->E--->C--->A 广度优先
python对于你定义的每一个类,都会计算出一个方法解析顺序MRO列表,这个MRO列表就是一个简单的所有基类的线性顺序列表
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
MRO列表遵循的三条准则:
1.子类会优先于父类被检查#先从子类自己中找
2.多个父类会根据它们在列表中的顺序被检查 #从MRO中按顺序找
3.如果对下一个类存在两个合法的选择,选择第一个父类#比如class F(D,E),先找D