python(25)- 面向对象补充Ⅰ
一、如何使用类
1.实例化:创建对象
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征。
例子一
x=int(10) print(x)
python中一切皆为对象,且python3统一了类与类型的概念,类型就是类
>>> dict #类型dict就是类dict <class 'dict'> >>> d=dict(name='egon') #实例化 >>> d.pop('name') #向d发一条消息,执行d的方法pop。 'egon'
例子二
>>> g1=Garen('草丛伦') #类实例化得到g1这个实例,基于该实例我们讲解实例相关知识 >>> type(g1) #查看g1的类型就是类Garen <class '__main__.Garen'> >>> isinstance(g1,Garen) #g1就是Garen的实例 True
例子三
class Garen: camp='Demacia' def attack(self): print('attack') obj=Garen() #实例化,实例就是对象。类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征。 print(obj) #----> <__main__.Garen object at 0x0000000001E7CC18>
2.引用类的特征(类的变量)和技能(类的函数)
基于面向对象设计一个款游戏:英雄联盟,每个玩家选一个英雄,每个英雄都有自己的特征和和技能,
特征即数据属性,技能即方法属性,特征与技能的结合体就一个对象。
从一组对象中提取相似的部分就是类,类所有对象都具有的特征和技能的结合体
在python中,用变量表示特征,用函数表示技能,因而类是变量与函数的结合体,对象是变量与方法(指向类的函数)的结合体
对象/实例本身其实只有数据属性
>>> g1.nickname '草丛伦' >>> g1.aggressivity >>> g1.life_value ''' 查看实例属性 同样是dir和内置__dict__两种方式 特殊实例属性 __class__ __dict__ .... '''
class Garen: camp='Demacia' #变量代表特征 def attack(self): #函数代表技能 print('attack') print(Garen.camp) #引用类的数据属性,该属性与所有对象/实例共享 ---->Demacia print(Garen.attack)#引用类的函数属性,该属性也共享 ---> <function Garen.attack at 0x0000000001E7F158> Garen.attack(123) ---->attack
类属性的补充
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
二、如何使用实例
class Garen: camp='Demacia' #变量,共有特征 def __init__(self,nickname): #self的作用是在实例化时自动将对象/实例本身传给__init__的第一个参数,self可以是任意名字,但是瞎几把写别人就看不懂了 self.nick=nickname #变量,独有特征 def attack(self,enemy): #函数,技能 # print('---->',self.nick) #g1.nick print('%s attack %s' %(self.nick,enemy)) g1=Garen('草丛伦') #就是在执行Garen.__init__(g1,'草丛伦'),然后执行__init__内的代码g1.nickname=‘草丛伦’等 g2=Garen('猥琐轮') print(g1.nick) ---->草丛伦 g1.attack('alex') ---->草丛伦 attack alex print(g1.camp) ---->Demacia print(g1.attack) ---> <bound method Garen.attack of <__main__.Garen object at 0x00000000027BCD30>> print(Garen.attack) ----><function Garen.attack at 0x00000000021DF048>
class Student: country='china' def __init__(self,ID,NAME,SEX,PROVINCE): self.id=ID self.name=NAME self.sex=SEX self.province=PROVINCE def search_score(self): print('tell score') def study(self): print('study',self) s1=Student('123','egon','male','shandong') print(Student.__init__(s1,'123','egon','male','shandong')) #调用__init__函数 ----> None #调用对象的数据属性,对象的属性:对象本身就只有特征(变量) s1.id='123' #调用对象的id属性 s1.name='egon' #调用对象的name属性 s1.sex='male' #调用对象的sex属性 s1.province='shandong' #调用对象的province属性 #查看函数 print(Student.country) #查看类的数据属性 ---->china print(Student.__init__) ----><function Student.__init__ at 0x0000000001E73B70> print(Student.study) ----><function Student.study at 0x0000000001E7F2F0> print(Student.search_score) ----><function Student.search_score at 0x000000000221F2F0> Student.__init__(s1,'123','egon','male','shandong') Student.study(s1) ---->study <__main__.Student object at 0x00000000021EC470>
总结:
类有二种作用:一:实例化,二:引用名字(类名.变量名,类名.函数名)
实例:引用名字(实例名.类的变量,实例名.绑定方法,实例名.实例自己的变量名)
三、属性的增删改查及及运算相加
#变量的增删改查 class Garen: camp='Demacia' def __init__(self,nickname): self.nick=nickname #g1.nick='草丛伦' def attack(self,enemy): print('%s attack %s' %(self.nick,enemy)) print(Garen.camp) #查 ---->Demacia Garen.camp='aaaaaa' #改 print(Garen.camp) ---->aaaaaa del Garen.camp #删除 print(Garen.camp) #报错:----> AttributeError: type object 'Garen' has no attribute 'camp' Garen.x=1 #增 print(Garen.x) ---->1 g1=Garen('alex') print(g1.nick) ---->alex g1.nick='asb' #改 print(g1.nick) ---->asb del g1.nick #删 print(g1.nick) #报错:----> AttributeError: 'Garen' object has no attribute 'nick' g1.sex='female' #增 print(g1.sex) ---->female Garen.x=1 Garen.y=2 Garen.res=Garen.x+Garen.y print(Garen.res=Garen.x+Garen.y) ---->3
python3中所有的类都是新式类
class A:pass print(A.__bases__) #__bases__查看继承 ---->(<class 'object'>,),继承是object的都是新式类
python2中分为新式类和经典类
新式类和经典类声明的最大不同在于,所有新式类必须继承至少一个父类
所有类甭管是否显式声明父类,都有一个默认继承object父类
python2中新式类 class B(object):pass class C(B):pass print(B.__bases__) #__bases__查看继承 ---->(<class 'object'>,) print(C.__bases__) ---->(<class '__main__.B'>,)
#python2中经典类 class D:pass print(D.__bases__) ---->()
在 python3中上面的新式类和经典类都是新式类
四、类的名称空间与对象的名称空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:数据属性和函数属性
其中类的数据属性是共享给所有对象的
>>> id(r1.camp) #本质就是在引用类的camp属性,二者id一样 4315241024 >>> id(Riven.camp) 4315241024
而类的函数属性是绑定到所有对象的:
>>> id(r1.attack) >>> id(Riven.attack) ''' r1.attack就是在执行Riven.attack的功能,python的class机制会将Riven的函数属性attack绑定给r1,r1相当于拿到了一个指针,指向Riven类的attack功能 除此之外r1.attack()会将r1传给attack的第一个参数 '''
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
class Student: country = 'China' def __init__(self, ID, NAME, SEX, PROVINCE): self.id = ID self.name = NAME self.sex = SEX self.province = PROVINCE def search_score(self): print('tell score') def study(self): #self=s1 print('study',self) def walk(self): print('name:%s is walking' %self.name) s1 = Student('123', 'cobila', 'female', 'shanxi') s2 = Student('124', 'cobilamei', 'femaleasfd', 'shasdfanxi') s1.walk() ---->name:cobila is walking s2.walk() ---->name:cobilamei is walking print(Student.__dict__) #__dict__,查看名称空间,查看类的名称空间 ---->{'__module__': '__main__', 'country': 'China', '__init__': <function Student.__init__ at 0x00000000021D3A60>, 'search_score': <function Student.search_score at 0x00000000021DF048>, 'study': <function Student.study at 0x00000000021DF2F0>, 'walk': <function Student.walk at 0x00000000021DF158>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None} print(s1.__dict__) #__dict__,查看名称空间,查看对象的名称空间 {'id': '123', 'name': 'cobila', 'sex': 'female', 'province': 'shanxi'} print(s1.id) ---->123 s1.country="321" print(id(s1.country)) ---->42061192 print(s2.id) ---->124 s2.country="421" print(id(s2.country)) ---->42061248 #绑定方法,绑定方法的核心在于“绑定”,唯一绑定一个确定的对象 print(s1.study,id(s1.study)) ----><bound method Student.study of <__main__.Student object at 0x000000000284CEB8>> 5139208 print(Student.study,id(Student.study)) ----><function Student.study at 0x000000000284F2F0> 42267376
对象/实例本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不会一样
>>> g1.attack #对象的绑定方法 <bound method Garen.attack of <__main__.Garen object at 0x101348dd8>> >>> Garen.attack #对象的绑定方法attack本质就是调用类的函数attack的功能,二者是一种绑定关系 <function Garen.attack at 0x101356620>
对象的绑定方法的特别之处在于:obj.func()会把obj传给func的第一个参数。
五、对象之间的交互
class Riven: camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus; def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54; self.nickname=nickname #为自己的锐雯起个别名; self.aggressivity=aggressivity #英雄都有自己的攻击力; self.life_value=life_value #英雄都有自己的生命值; def attack(self,enemy): #普通攻击技能,enemy是敌人; enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。 class Garen: camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus; def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54; self.nickname=nickname #为自己的锐雯起个别名; self.aggressivity=aggressivity #英雄都有自己的攻击力; self.life_value=life_value #英雄都有自己的生命值; def attack(self,enemy): #普通攻击技能,enemy是敌人; enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。 # 对象之间的交互 r1=Riven('芮雯雯') g1=Garen('草丛轮') print(r1.life_value) ---->414 g1.attack(r1) print(r1.life_value) ---->360 name=input('your nickname: ') r1=Riven(name)
六、面向对象的编程思路
#应用场景 #找不到共同特征和技能不用强求 #对象:学校----->归类 #共有的特征:商标为etiantian #共有的技能:招生 #独有的特征:地址不一样,老师们,课程 class School: tag='etiantian' def __init__(self,addr): self.addr=addr self.teacher_list=[] self.course_list=[] def zhaosheng(self): pass #对象:老师---->归类 #共同的技能:教课 #独有的特征:名字,性别,level,课程 class Teacher: def __init__(self,name,sex,level): self.name=name self.sex=sex self.level=level self.course_list=[] def teach(self): pass #对象:学生---->归类 #共同的特征: #共同的技能:search_score,handin #独有的特征:学号,名字,性别,课程 class Student: def __init__(self,ID,name,sex): self.id=ID self.name=name self.sex=sex self.course_list=[] def search_score(self): pass def handin(self): pass
七、继承与派生
1.继承的概念
继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类。
python中类的继承分为:单继承和多继承。
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
>>> SubClass1.__bases__ #__bases__查看继承 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类,python中新式类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
2.继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
3.使用继承来重复使用代码
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,这
就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。
class Hero: def __init__(self,nickname,aggressivity,life_value): self.nickname=nickname self.aggressivity=aggressivity self.life_value=life_value def move_forward(self): print('%s move forward' %self.nickname) def move_backward(self): print('%s move backward' %self.nickname) def move_left(self): print('%s move forward' %self.nickname) def move_right(self): print('%s move forward' %self.nickname) def attack(self,enemy): enemy.life_value-=self.aggressivity class Garen(Hero): pass class Riven(Hero): pass g1=Garen('草丛伦',100,300) r1=Riven('锐雯雯',57,200) print(g1.life_value) r1.attack(g1) print(g1.life_value) ''' 运行结果 243 '''
提示:用已经有的类通过继承的联系建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重
用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重
大.
注意:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那
么调用新增的属性时,就以自己为准了。
class Riven(Hero): camp='Noxus' def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 print('from riven') def fly(self): #在自己这里定义新的 print('%s is flying' %self.nickname)
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:
类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值。
class Riven(Hero): camp='Noxus' def __init__(self,nickname,aggressivity,life_value,skin): Hero.__init__(self,nickname,aggressivity,life_value) #调用父类功能 self.skin=skin #新属性 def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类 Hero.attack(self,enemy) #调用功能 print('from riven') def fly(self): #在自己这里定义新的 print('%s is flying' %self.nickname) r1=Riven('锐雯雯',57,200,'比基尼') r1.fly() print(r1.skin) ''' 运行结果 锐雯雯 is flying 比基尼 '''
4.组合与重用性
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
>>> class Equip: #武器装备类 ... def fire(self): ... print('release Fire skill') ... >>> class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类 ... camp='Noxus' ... def __init__(self,nickname): ... self.nickname=nickname ... self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性 ... >>> r1=Riven('锐雯雯') >>> r1.equip.fire() #可以使用组合的类产生的对象所持有的方法 release Fire skill
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同。
4.1继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师。
>>> class Teacher: ... def __init__(self,name,gender): ... self.name=name ... self.gender=gender ... def teach(self): ... print('teaching') ... >>> >>> class Professor(Teacher): ... pass ... >>> p1=Professor('egon','male') >>> p1.teach() teaching
4.2组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程
class BirthDate: def __init__(self,year,month,day): self.year=year self.month=month self.day=day class Couse: def __init__(self,name,price,period): self.name=name self.price=price self.period=period class Teacher: def __init__(self,name,gender): self.name=name self.gender=gender def teach(self): print('teaching') class Professor(Teacher): def __init__(self,name,gender,birth,course): Teacher.__init__(self,name,gender) self.birth=birth self.course=course p1=Professor('xuyaping','female', BirthDate('2000','1','1'), Couse('python','19999','6 months')) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) ''' 运行结果: 2000 1 1 python 19999 6 months '''
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
5.接口与归一化设计
继承有两种用法:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:接口继承:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,
子类继承接口类,并且实现接口中的功能
#接口继承 class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): #定接口函数read pass def write(self): #定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(Interface): #磁盘,具体实现read和write def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(Interface): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法')
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种接口继承非常重要。接口继承实质上是要求“做出一个良好的抽象(例子中接口Interface类),这个抽象规定了一个兼容接口,使得
外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它
是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口(java中
子类必须要实现接口的内容,否则会报错),如果非要去模仿接口的概念,可以借助第三方模块:
http://pypi.python.org/pypi/zope.interface
twisted的twisted\internet\interface.py里使用zope.interface
文档https://zopeinterface.readthedocs.io/en/latest/
设计模式:https://github.com/faif/python-patterns
为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生
一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
6.抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个
具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能,即抽象类中只能定义函数却不给予函数具体信息),该类
不能被实例化,只能被继承,且子类必须实现抽象方法(子类中必须完善抽象类中缺失的函数具体信息),这一点与接口有点类似。
在python中实现抽象类
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' #一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
抽象类与接口的区别
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计 。
7.继承的顺序
#新式类的继承,在查找属性时遵循:广度优先 class A(object): def test(self): print('from A') pass class B(A): def test(self): print('from B') pass class C(A): def test(self): print('from C') pass class D(B): def test(self): print('from D') pass class E(C): def test(self): print('from E') pass class F(D,E): def test(self): print('from F') pass f1=F() f1.test() #广度优先:F->D->B->E->C->A->object
#新式类的继承,在查找属性时遵循:广度优先 class X(object): def test(self): print('from X') pass class Y(object): def test(self): print('from Y') pass class B(X): def test(self): print('from B') pass class C(Y): def test(self): print('from C') pass class D(B): def test(self): print('from D') pass class E(C): def test(self): print('from E') pass class F(D,E): def test(self): print('from F') pass f1=F() f1.test() #F--->D---->B--->X--->E---->C---->Y---->object
#新式类的继承,在查找属性时遵循:广度优先 class A(object): def test(self): print('from A') pass class B(A): def test(self): print('from B') pass class C(A): def test(self): print('from C') pass class D(A): def test(self): print('from D') pass class E(B): def test(self): print('from E') pass class F(C): def test(self): print('from F') pass class G(D): def test(self): print('from G') pass class H(E,F,G): def test(self): print('from H') pass h1=H() h1.test() #H--->E--->B--->F--->C--->G--->D--->A
查看继承关系__mro__
只有新式类才能使用__mro__或mro()查看线性列表,经典类没有这个属性
#coding:utf-8 #新式类的继承,在查找属性时遵循:广度优先 class A(object): def test(self): print('from A') pass class B(A): def test(self): print('from B') pass class C(A): def test(self): print('from C') pass class D(B): def test(self): print('from D') pass class E(C): def test(self): print('from E') pass class F(D,E): def test(self): print('from F') pass f1=F() print(F.__mro__) #只有新式类才能使用__mro__或mro()查看线性列表,经典类没有这个属性 print(F.mro()) --->(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] #广度优先:F->D->B->E->C->A->object
python2中经典类的继承,在查找属性时遵循:深度优先
#coding:utf-8 class A: def test(self): print('from A') pass class B(A): def test(self): print('from B') pass class C(A): def test(self): print('from C') pass class D(B): def test(self): print('from D') pass class E(C): def test(self): print('from E') pass class F(D,E): def test(self): print('from F') pass f1=F() f1.test() # F->D->B->A->E->C
8.子类中调用父类方法
方法一:普通的类的调用函数的方法:父类名.父类方法()
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' 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) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) Vehicle.run(self) line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run()
方法二:super()
super()在Python3中和Python2中用法不同
#在python2中,super(Chinese,self).__init__(name,sex,age). #在python3中,super().__init__(name,sex,age),也可以super(Chinese,self).__init__(name,sex,age). class People: def __init__(self,name,sex,age): self.name=name self.age=age self.sex=sex def walk(self): print('%s is walking' %self.name) class Chinese(People): country='China' def __init__(self,name,sex,age,language='Chinese'): # self.name=name # self.sex=sex # self.age=age # People.__init__(self,name,sex,age) super().__init__(name,sex,age) #super()相当于对象,__init__()调用函数.和 People.__init__(self,name,sex,age)同用法,但不使用super可能会引发的惨案 self.language=language def walk(self,x): super().walk() print('子类的x',x) c=Chinese('egon','male',18) # print(c.name,c.age,c.sex,c.language) c.walk(123)
不使用super引发的惨案
#每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') A.__init__(self) class C(A): def __init__(self): print('C的构造方法') A.__init__(self) class D(B,C): def __init__(self): print('D的构造方法') B.__init__(self) C.__init__(self) pass f1=D() print(D.__mro__) #python2中没有这个属性
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列
表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定
要看MRO列表)
#每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') super(B,self).__init__() class C(A): def __init__(self): print('C的构造方法') super(C,self).__init__() class D(B,C): def __init__(self): print('D的构造方法') super(D,self).__init__() f1=D() print(D.__mro__) #python2中没有这个属性
八、多态与多态性
#多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度) class Animal: def run(self): raise AttributeError('子类必须实现这个方法') class People(Animal): def run(self): print('人正在走') class Pig(Animal): def run(self): print('pig is walking') class Dog(Animal): def run(self): print('dog is running') peo1=People() pig1=Pig() d1=Dog() peo1.run() pig1.run() d1.run()
#多态性:一种调用方式,不同的执行效果(多态性) def func(obj): obj.run() func(peo1) func(pig1) func(d1) ##多态性:定义统一的接口, def func(obj): #obj这个参数没有类型限制,可以传入不同类型的值 obj.run() #调用的逻辑都一样,执行的结果却不一样 func(peo1) func(pig1) func(d1)
九、封装
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问.
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。
>>> r1.nickname '草丛伦' >>> Riven.camp 'Noxus'
注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口。
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到.
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
2.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. >>> a=A() >>> a._A__N 0 >>> a._A__X 10 >>> A._A__N 0
2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况 >>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() #先从对象b下找test,明显没有。然后从类B中找,找到test后运行函数 from B
#把fa定义成私有的,即__fa >>> class A: ... def __fa(self): #在定义时就变形为_A__fa ... print('from A') ... def test(self): ... self.__fa() #只会与自己所在的类为准,即调用_A__fa ... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() #从对象b下找test,没有。从B类中找test,没有。然后从父类A中找,找到test,运行函数,得到b._A__fa()。然后找_A__fa(),输出from A from A
封装变形相关例子
class A: __x=1 #_A__x def __test(self): #_A__test print("from A") # print(A.__x) #报错,AttributeError: type object 'A' has no attribute '__x' print(A._A__x) --->1 a=A() print(a._A__x) --->1 print(A.__dict__) --->{'__module__': '__main__', '_A__x': 1, '_A__test': <function A.__test at 0x00000000021CF378>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} A._A__test(123) --->from A
#__名字,这种语法,只在定义的时候才会有变形的效果,如果类或者对象已经产生了,就不会有变形效果 class B: pass B.__x=1 print(B.__dict__) --->{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None} print(B.__x) --->1 b=B() b.__x=1 print(b.__dict__) --->{'__x': 1} print(b.__x) --->1
十、特性
property是一种特殊的属性,访问它时会执行一段功能(函数)并且返回一个值
被property装饰的属性会优先于对象的属性被使用
import math class Circle: def __init__(self,radius): #圆的半径radius self.radius=radius @property def area(self): return math.pi * self.radius**2 #计算面积 @property def perimeter(self): return 2*math.pi*self.radius #计算周长 c=Circle(10) print(c.radius) --->10 print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值 --->314.1592653589793 print(c.perimeter) #可以向访问数据属性一样去访问perimeter,会触发一个函数的执行,动态计算出一个值 --->62.83185307179586
注意:此时的特性arear和perimeter不能被赋值
c.area=3 #为特性area赋值 ''' 抛出异常: AttributeError: can't set attribute '''
class People: def __init__(self,name,age,height,weight): self.name=name self.age=age self.height=height self.weight=weight @property def bodyindex(self): return self.weight/(self.height**2) p1=People('cobila',89,1.65,75) print(p1.bodyindex) --->27.548209366391188 p1.weight=200 #修改体重 print(p1.bodyindex) --->73.46189164370983
setter
class People: def __init__(self,name,SEX): self.name=name self.sex=SEX @property def sex(self): return self.__sex @sex.setter def sex(self,value): print(self,value) if not isinstance(value,str): #主动抛出异常 raise TypeError("性别必须是字符串类型") self.__sex=value p1=People("cobila","male") p1.sex="female" #触发@sex.setter下的sex的运行 ---><__main__.People object at 0x00000000021CDBE0> female print(p1.sex) #触发@property下的sex的运行 --->female p1.sex=123 print(p1.sex) --->报错。TypeError: 性别必须是字符串类型 p1=People("cobila",123) #将__init__函数下的self.__sex=SEX改成self.sex=SEX触发__init__函数,self.sex=SEX,其中的sex触发@sex.setter下的sex的运行,所以报错 --->报错。TypeError: 性别必须是字符串类型
deleter
class People: def __init__(self,name,SEX): self.name=name self.sex=SEX @property def sex(self): return self.__sex @sex.setter def sex(self,value): if not isinstance(value,str): raise TypeError("性别必须是字符串类型") self.__sex=value @sex.deleter def sex(self): # del self.sex #self.sex触发@sex.deleter下的sex运行,所以变成递归删 RecursionError: maximum recursion depth exceeded del self.__sex p1=People("cobila","male") del p1.sex #第一步,触发__init__函数,self.sex=SEX,寻找sex,找到@sex.deleter下的sex运行,删除的是@property下的__sex
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
十一、静态方法和类方法
1 静态方法staticmethod
是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作,python为我们内置了函数staticmethod来把类中的函数定义成静态方法
class Foo: def spam(self,y,z): #类中的一个函数,千万不要懵逼,self和y,z啥的没有不同都是参数名,也需要传参 print(self,y,z) spam=staticmethod(spam) #把spam函数做成静态方法
基于之前所学装饰器的知识,@staticmethod 等同于spam=staticmethod(spam)
class Foo: @staticmethod #装饰器 def spam(self,y,z): print(self,y,z)
print(type(Foo.spam)) #类型本质就是函数 ---><class 'function'> Foo.spam(1,2,3) #调用函数应该有几个参数就传几个参数 --->1 2 3 f1=Foo() f1.spam(3,3,3) #实例也可以使用,但通常静态方法都是给类用的,实例在使用时丧失了自动传值的机制 --->3 3 3
应用场景:编写类时需要采用很多不同的方式来创建实例,而我们只有一个__init__函数,此时静态方法就派上用场了
class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day @staticmethod def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间 t=time.localtime() #获取结构化的时间格式 return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回 @staticmethod def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间 t=time.localtime(time.time()+86400) return Date(t.tm_year,t.tm_mon,t.tm_mday) a=Date('1987',11,27) #自己定义时间 b=Date.now() #采用当前时间 c=Date.tomorrow() #采用明天的时间 print(a.year,a.month,a.day) --->1987 11 27 print(b.year,b.month,b.day) --->2017 4 23 print(c.year,c.month,c.day) --->2017 4 24
2 类方法classmethod
类方法是给类用的,类在使用时会将类本身当做参数传给类方法的第一个参数,python为我们内置了函数classmethod来把类中的函数定义成类方法
class A: x=1 @classmethod def test(cls): print(cls,cls.x) class B(A): x=2 B.test() ---> <class '__main__.B'> 2
应用场景:
#静态方法staticmethod的应用场景例子 import time class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day @staticmethod def now(): t=time.localtime() return Date(t.tm_year,t.tm_mon,t.tm_mday) class EuroDate(Date): def __str__(self): return 'year:%s month:%s day:%s' %(self.year,self.month,self.day) e=EuroDate.now() print(e) #我们的意图是想触发EuroDate.__str__,但是@staticmethod是解除绑定方法,不会自动传值 ---><__main__.Date object at 0x1013f9d68>
解决方法就是用classmethod
import time class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day # @staticmethod # def now(): # t=time.localtime() # return Date(t.tm_year,t.tm_mon,t.tm_mday) @classmethod #改成类方法 def now(cls): t=time.localtime() return cls(t.tm_year,t.tm_mon,t.tm_mday) #哪个类来调用,即用哪个类cls来实例化 class EuroDate(Date): def __str__(self): return 'year:%s month:%s day:%s' %(self.year,self.month,self.day) e=EuroDate.now() print(e) #我们的意图是想触发EuroDate.__str__,此时e就是由EuroDate产生的,所以会如我们所愿 ---> year:2017 month:3 day:3
强调,注意注意注意:静态方法和类方法虽然是给类准备的,但是如果实例去用,也是可以用的,只不过实例去调用的时候容易让人混淆,不知道你要干啥
x=e.now() #通过实例e去调用类方法也一样可以使用,静态方法也一样 print(x) ---> year:2017 month:3 day:3
总结:
在类内部定义的函数无非三种用途:
一:绑定到对象的方法
只要是在类内部定义的,并且没有被任何装饰器修饰过的方法,都是绑定到对象的
class Foo: def test(self): #绑定到对象的方法 pass def test1(): #也是绑定到对象的方法,只是对象.test1(),会把对象本身自动传给test1,因test1没有参数所以会抛出异常 pass
绑定到对象,指的是:就给对象去用。
使用方式:对象.对象的绑定方法(),不用为self传值,self会被自动传值。
特性:调用时会把对象本身当做第一个参数传给对象的绑定方法。
二:绑定到类的方法:classmethod
在类内部定义的,并且被装饰器@classmethod修饰过的方法,都是绑定到类的
class Foo: @classmethod def test(cls): #绑定到类的方法 pass @classmethod def test1(): #也是绑定到类的方法,只是类.test1(),会把类本身传给test1,因test1没有参数所以抛出异常 pass
绑定到对象,指的是:就给类去用。
使用方式:类.类的绑定方法(),不用为cls传值,cls会被自动传值。
特性:调用时会把类本身当做第一个参数传给类的绑定方法。
三:解除绑定的方法:staticmethod
既不与类绑定,也不与对象绑定,不与任何事物绑定。
绑定的特性:自动传值(绑定到类的就是自动传类,绑定到对象的就自动传对象)。
解除绑定的特性:不管是类还是对象来调用,都没有自动传值这么一说了。
所以说staticmethod就是相当于一个普通的工具包。
class Foo: def test1(self): pass def test2(): pass @classmethod def test3(cls): pass @classmethod def test4(): pass @staticmethod def test5(): pass
test1与test2都是绑定到对象方法:调用时就是操作对象本身。
<function Foo.test1 at 0x0000000000D8E488>
<function Foo.test2 at 0x0000000000D8E510>
test3与test4都是绑定到类的方法:调用时就是操作类本身。
<bound method Foo.test3 of <class '__main__.Foo'>>
<bound method Foo.test4 of <class '__main__.Foo'>>
test5是不与任何事物绑定的:就是一个工具包,谁来都可以用,没说专门操作谁这么一说。
<function Foo.test5 at 0x0000000000D8E6A8>