类和实例
在现实生活中是先有的对象后有的类,比如,先有人这个对象,后有的人类
而在计算机中,是先有的类后有的对象,比如,先有int这个类,后有的1这个int对象
所以我们要先定义类,再由类产生一个个实例的对象
在Python3中类型就是类,比如int,str,list等工厂函数都可以叫做类
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
在Python中,定义类是通过class
关键字:
class Student(object): pass
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
定义好了Student
类,就可以根据Student
类创建出Student
的实例,创建实例是通过类名+()实现的:
>>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'>
可以看到,变量bart
指向的就是一个Student
的实例,后面的0x10a67a590
是内存地址,每个object的地址都不一样,而Student
本身则是一个类。
可以自由地给一个实例变量绑定属性,比如,给实例bart
绑定一个name
属性:
>>> bart.name = 'Bart Simpson' >>> bart.name 'Bart Simpson'
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
注意到__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
有了__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去:
>>> bart = Student('Bart Simpson', 59) >>> bart.name 'Bart Simpson' >>> bart.score 59
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,实例调用时,不用传递该参数。除此之外,类的函数和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
#只要是由对象出发的就有自动传值的功能,实例化和调用绑定方法都是有对象引发的,所以都有自动传值的功能 #在类内部,方法跟类没有关系,跟类扯关系的是函数 # class Garen: # camp = 'Demacia' #类的特征 # def attack(self): #共同的技能 # print('attack') # print(Garen)#<class '__main__.Garen'>Garen是一个类 # print(int)#<class 'int'>int是一个类 #如何使用类 #一:实例化 # x=int(10) # print(x) # print(type(x) # y=int() # print(int())weiweiwieiweshenmee # obj=Garen() #实例化 # print(obj)#<__main__.Garen object at 0x00000000011E1438> #二:引用类的特征(类的变量)和技能(类的函数) # print(Garen.camp) # Garen.attack(11)#类调用类的函数,就按函数走,有参就要传 # print(Garen.attack)#<function Garen.attack at 0x000000000115E1E0>说明现在这是个函数,不是方法 #如何使用实例 # class Garen: # camp = 'Demacia' #类的特征 # def __init__(self,nickname): #像__init__这种__开头,__结尾的函数,都是Python内置的函数 # self.nick=nickname #记住形式, # def attack(self,enemy): #共同的技能 # print('attack %s'%enemy) # g1=Garen("草丛伦")#类加括号就是实例化,实例化过程会自动触发类内部的__init__()函数 # g2=Garen("猥琐伦")#别名属于同一类对象的不同特性,语法Garen.__init__(g2,"猥琐伦") # print(g1.nick)#草丛伦 由self.nick=nickname得到输出什么 # print(g2.nick)#猥琐伦 # print(g1)#<__main__.Garen object at 0x0000000000A914E0> # print(g2)#<__main__.Garen object at 0x0000000000A91518>与上面的不相等 # print(g1.attack)#绑定方法<bound method Garen.attack of <__main__.Garen object at 0x0000000000AC14E0>> # print(Garen.attack)#函数<function Garen.attack at 0x0000000000ABE268> # # Garen.attack("g1")#调用的是函数,不传参会报错,有几个参数传几个 # g1.attack("alex")#应为除了self外还有一个参数enemy我们这里传的"alex"就是传给enemy的 #函数只要定义参数调用时就必须传值,没有传必然是Python帮我传了,在这里我们调用的是绑定方法把绑定对象作为参数self传入方法 #总结 #类:1、实例化,2、引用名字(类名.变量名,类名.函数名) #实例:1、实例是类的实例化对象 2、引用名字(实例名.类的变量,实例名.绑定方法,实例名.实例自己的变量名) # class Garen: # camp = 'Demacia' #类的特征 # def __init__(self,nickname): #像__init__这种__开头,__结尾的函数,都是Python内置的函数 # self.nick=nickname #记住形式, # def attack(self,enemy): #共同的技能 # print('attack %s'%enemy) #类的特征(变量)增删该查 # print(Garen.camp)#查,查看变量值 # Garen.camp='aaaa'#改,修改变量值 # print(Garen.camp)# # del Garen.camp#删除,删除类的特征,这里可以说变量 # print(Garen.camp) # Garen.x=1#增加,增加类的特征, 变量 # print(Garen.x) #实例的特征(变量)增删改查,当然必须得先产生实例 # g1=Garen('alex') # print(g1.nick)#查看属性,__init__函数定义的属性这里的属性是别名 # g1.nick='asb'#修改属性 # print(g1.nick) # del g1.nick #删除属性 # g1.sex='female'#增加一个特性,变量名,但是加到哪里了呢??? # print(g1.sex)
类的名称空间和实例的名称空间
创建一个类就创建了一个类的名称空间,用来存储类中定义的所有名字,这些名字成为类的属性
其中类有两种属性:数据属性和函数属性
其中类的属性是共享给所有对象的
而类的函数属性是绑定到所有对象的
# 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('3185525654654','cobila','female','shanxi') # print(Student.__dict__)#查看类的名称空间,字典形式 # print(s1.__dict__)#查看实例的,字典形式 # print(s1.id) # print(s1.x)#实例找属性的时候现在他自己的dict里面找,没有的话再去类的dict里面找,如果类里面也没有的话就报错 # s1.x=123# 实例的属性在哪里定义都一样,他属与实例域 # print(id(s1.country))#18528216 # print(id(Student.country))#18528216,说明类的变量是类和实例共用的 # print(s1.study,id(s1.study))#4814280,既然是绑定方法,就是把s1和study方法绑到一起看,因为s1是对无二的所以s1.study也是独一无二的 #所以绑定方法是唯一绑定到一个确定的对象上的 # print(Student.study,id(Student.study))#11985648
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后类中找不到就抛出异常
面向对象编程流程
#严格遵守下面流程 # ''' # 面向对象程序设计OOD: # 找对象------>找类(归纳对象共同的特征与技能,还有每个对象独有的特征) # 面向对象编程OOP: # 先定义类------>实例化出对象 # ''' #注意#1.应用场景,比如老男孩教育共同的特征就是商标都一样,太大了就写找不到共同特征了 #2.找不到共同特征就不用写了 #OOD:定义学生类 #共同特征:country = 'China' #技能:查看成绩 #独有特征:ID,NAME,SEX,PROBINCE #OOP:编写程序 # 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')
对象交互
# class Riven: # camp = 'Noxus' # def __init__(self,nickname,aggressivity=54,life_value=414): # self.nickname = nickname # self.aggressivity = aggressivity#英雄的攻击力 # self.life_value = life_value#英雄的生命值 # def attack(self,enemy):#英雄的普通攻击技能 # enemy.life_value-=self.aggressivity#敌人的生命值等总值减去英雄的攻击力 # # class Garen: # camp = 'Demaxiya' # def __init__(self,nickname,aggressivity=54,life_value=414): # self.nickname = nickname # self.aggressivity = aggressivity#英雄的攻击力 # self.life_value = life_value#英雄的生命值 # def attack(self,enemy):#英雄的普通攻击技能 # enemy.life_value-=self.aggressivity#敌人的生命值等总值减去英雄的攻击力 # r1=Riven('瑞问问') # g1=Garen('草丛伦') # g1.attack(r1)#对象之间的交互,就是一个对象把另一个对象作为参数运行方法 # print(r1.life_value)
数据封装
面向对象编程的一个重要特点就是数据封装。在上面的Student
类中,每个实例就拥有各自的name
和score
这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
>>> def print_score(std): ... print('%s: %s' % (std.name, std.score)) ... >>> print_score(bart) Bart Simpson: 59
但是,既然Student
实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student
类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student
类本身是关联起来的,我们称之为类的方法:
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score))
要定义一个方法,除了第一个参数是self
外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self
不用传递,其他参数正常传入:
>>> bart.print_score() Bart Simpson: 59
这样一来,我们从外部看Student
类,就只需要知道,创建实例需要给出name
和score
,而如何打印,都是在Student
类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
封装的另一个好处是可以给Student
类增加新的方法,比如get_grade
:
class Student(object): ... def get_grade(self): if self.score >= 90: return 'A' elif self.score >= 60: return 'B' else: return 'C'
同样的,get_grade
方法可以直接在实例变量上调用,不需要知道内部实现细节:
>>> bart.get_grade() 'C'
继承
1、什么是继承?
继承是一种创建新的类的方式,在Python中,新建的类可以继承自一个或者多个父类,原始类统称为基类或超类(Base class、Super class),新建的类称为派生类或子类(Subclass)
Python中的类分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
>>> SubClass1.__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'>,)
继承与抽象(先抽象在继承)
抽象即抽取类似或者说比较像的部分
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
继承与重用性
在开发程序的过程中,如果我们定义了一个类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) ''' 运行结果 300 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 比基尼 '''
组合与重用性
软件重用的重要方式除了继承之外还有另一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合和继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同
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
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('egon','male', BirthDate('1995','1','27'), Couse('python','28000','4 months')) print(p1.birth.year,p1.birth.month,p1.birth.day) print(p1.course.name,p1.course.price,p1.course.period) ''' 运行结果: 1995 1 27 python 28000 4 months '''
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
接口与归一化设计
1.什么是接口
=================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java /* * Java的Interface很好的体现了我们前面分析的接口的特征: * 1)是一组功能的集合,而不是一个功能 * 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作 * 3)接口只定义函数,但不涉及函数实现 * 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */ package com.oo.demo; public interface IAnimal { public void eat(); public void run(); public void sleep(); public void speak(); } =================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口 package com.oo.demo; public class Pig implements IAnimal{ //如下每个函数都需要详细实现 public void eat(){ System.out.println("Pig like to eat grass"); } public void run(){ System.out.println("Pig run: front legs, back legs"); } public void sleep(){ System.out.println("Pig sleep 16 hours every day"); } public void speak(){ System.out.println("Pig can not speak"); } } =================第三部分:Person2.java /* *实现了IAnimal的“人”,有几点说明一下: * 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样 * 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */ package com.oo.demo; public class Person2 implements IAnimal { public void eat(){ System.out.println("Person like to eat meat"); } public void run(){ System.out.println("Person run: left leg, right leg"); } public void sleep(){ System.out.println("Person sleep 8 hours every dat"); } public void speak(){ System.out.println("Hellow world, I am a person"); } } =================第四部分:Tester03.java package com.oo.demo; public class Tester03 { public static void main(String[] args) { System.out.println("===This is a person==="); IAnimal person = new Person2(); person.eat(); person.run(); person.sleep(); person.speak(); System.out.println(" ===This is a pig==="); IAnimal pig = new Pig(); pig.eat(); pig.run(); pig.sleep(); pig.speak(); } }
继承有两种用途:
1.继承基类的方法,并且做出自己的改变或者扩展(代码重用)
2.声明某个子类兼容于某基类,定义一个接口类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(All_file): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法')
实践中,继承的第一种含义意义不是很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的dier中含义非常重要,他又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象”,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象“—这在程序设计上叫做归一化”
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好像linux的一切皆文件理念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
2.为何要用接口
接口提取了一群共同的函数,可以把接口当做一个函数的集合
然后让子类去实现接口中的函数
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
抽象类
1.什么是抽象类
与Java一样,Python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
2.为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
3.在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)
4.抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性
继承实现的原理
继承顺序
1、在Python中可以继承多个类,Java和c#中则只能继承一个类
2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是深度优先和广度优先:当类是经典类是,多继承情况下,会按照深度优先方式查找;当类是新式类时,多继承情况下,会按照广度优先方式查找
在Python3中,全部是新式类,在Python2中,继承于经典类的类都是经典类,继承于新式类的类都是新式类
class A(object): def test(self): print("from A") class B(A): def test(self): print("from B") class C(A): def test(self): print("from C") class D(B): def test(self): print("from D") class E(C): def test(self): print("from E") class F(D,E): def test(self): print("from F") f1 = F() f1.test() print(F.__mro__)#__mro__可以查看所有的线性元组,经典类没有这个属性,只有新式类有 #新式类继承顺序:F-->D-->B-->E-->A-->object #经典类继承顺序:F-->D-->B-->A-->E-->C #Python3中统一都是新式类
继承原理(Python如何实现的继承)
Python中对于定义的每一个类,Python会计算出一个方法解析顺序(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'>)
为了实现继承,Python会在mro列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个mro列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,他实际上就是合并所有父类的mro列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据他们在列表中的顺序被检查
3.取最近的
supr的用法
super是用于派生当中的,就是我们在子类中需要调用父类的函数,并在其基础上进行修改
使用范围仅限于新式类中,在Python2中,super()中需要两个参数,Python3中不需要
# #coding:utf-8 # #super在Python2中的用法: # #1.super(自己的类,self).父类的函数名字 # #2.super只能用于新式类 # class People(object): # 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(Chinese,self).__init__(name,sex,age) # self.language = language # c = Chinese("egon","male",18) # print (c.name,c.age,c.language) # #在Python3中 # class People: # def __init__(self,name,sex,age): # self.name = name # self.sex = sex # self.age = age # def walk(self): # print("%s is walking"%self.name) # class Chinese(People): # country = "China" # def __init__(self,name,sex,age,language="Chinese"): # super().__init__(name,sex,age) # self.language = language # def walk(self,x): # super().walk()#walk后面不需要加参数 # print("子类的x",x) # c = Chinese("egon","male",18) # # print(c.name,c.age,c.sex) # c.walk(1)
当我们使用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__)
多态与多态性
多态指的是一类事物有多种形态,(一个抽象有多个子类,因而多态的概念依赖于继承)
1.序列类型有多种形态:字符串,列表,元组
2.动物有多种形态:人,狗,猪
import abc class Animal(metaclass=abc.ABCMeta):#同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal):#动物形态之一:人 def talk(self): print("say hello") class Dog(Animal): #动物形态之二:狗 def talk(self): print("say wangwang") class Pig(Animal):#动物形态之三:猪 def talk(self): print("say aoao")
3.文件有多种形态:文本文件,可执行文件
import abc class File(metaclass=abc.ABCMeta): #同一类事物:文件 @abc.abstractmethod def click(self): pass class Text(File): #文件的形态之一:文本文件 def click(self): print("open file") class ExeFile(File): #文件的形态之二:可执行文件 def click(self): print("execute file")
多态性是指具有不同的功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数
在面向对象方法中一般这样描述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数
import abc class Animal(metaclass=abc.ABCMeta):#同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal):#动物形态之一:人 def talk(self): print("say hello") class Dog(Animal): #动物形态之二:狗 def talk(self): print("say wangwang") class Pig(Animal):#动物形态之三:猪 def talk(self): print("say aoao") def func(animal): #参数animal就是多态性的体现 animal.talk() people = People()#产生一个人的对象 pig=Pig()#产生一个住的对象 dog=Dog()#产生一个狗的对象 func(people) func(pig) func(dog)
为什么要用多态性:
增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
增加了程序的可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
>>> class Cat(Animal): #属于动物的另外一种形态:猫 ... def talk(self): ... print('say miao') ... >>> def func(animal): #对于使用者来说,自己的代码根本无需改动 ... animal.talk() ... >>> cat1=Cat() #实例出一只猫 >>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能 say miao ''' 这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1) '''
封装
封装数据的主要原因是:保护隐私
封装方法的主要原因是:隔离复杂度
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在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的形式访问到 a = A() # print(a.__dict__)#{'_A__X': 10} # print(A.__dict__)#可以查看属性名有没有被改为_*__*的形式 print(a._A__N) print(a._A__X) a._A__foo() a.bar()
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果
2.在外部无法通过a.__x访问到这个名字,需要通过a._A__x访问
class A: def __fa(self): #_A__fa print("from A") def test(self): self.__fa() #_A__fa class B(A): def __fa(self):#_B__fa print("from B") b = B() b.test()#from A ''' 在子类定义的__fa并不会覆盖在父类定义的__fa,因为子类中变形成了_B__fa,而父类中变形成了_A__fa即双下划线开头的属性在继承给子类时,子类是无法覆盖的 注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了 '''
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
这种变形需要注意的问题是:
1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3.在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况 class A: def fa(self): print("from A") def test(self): self.fa()#b.fa class B(A): def fa(self): print("from B") b = B() 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()#from A
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如__getattr__
特性(property)
property是一种特殊的属性,访问它是会执行一段函数功能(函数)然后返回值
对象想要修改绑定方法是可以借助property中的setter,想要删除自己的函数属性的时候也要用到property中的deleter功能
#被property装饰的属性会优先于对象的属性被使用(包括数据属性和函数属性) #而被property装饰的属性,如下面的sex,分成三种: #1.property 查看 #2.sex.setter 修改 #3.sex.deleter 删除 #@property的第一个用法,把函数属性伪装成数据属性 # import math # class Circle: # def __init__(self,radius): #圆的半径 # self.radius = radius # @property #在这里的area面积应该是个名词,我们要让使用者感觉到它是一个数据属性,而不是函数属性,才符合常理 # def area(self): # return math.pi * self.radius**2 #计算面积 # @property # def perimeter(self): # return 2 * math.pi * self.radius #计算周长 # c = Circle(7) # c.radius = 10 # print(c.area)#在没有property的时候我们打印的时候是内存地址,但是加上property装饰器后,就省去了加()去运行 # print(c.perimeter) #@property的第二个用法,装饰的是对象的数据属性,和setter,deleter连用 # class People: # def __init__(self,name,SEX): # self.name = name # self.sex = SEX # @property#查找,增加了一种查找方式 # def sex(self): # # print("====>") # return self.__sex #真正的sex存在地方 # @sex.setter#修改,增加了一种修改方式 # def sex(self,value): # if not isinstance(value,str): #还可以限定修改的类型 # raise TypeError("性别必须是字符串类型") # self.__sex = value#真正存放SEX的地方是在__sex中, # @sex.deleter#删除,增加一种删除方式 # def sex(self): # del self.__sex # p1 = People("egon","male")#在实例化生成对象的时候property,setter,deleter,都会起到作用,因为实例化的过程也会做数据封装,别忘了property的作用 # print(p1.name,p1.sex) # del p1.sex # print(p1.sex) #p1.sex 找sex的时候先去找有没有property修饰的sex如果有的话先找他 #p1.sex = "" 第一步也是先找,然后返回self.__sex他的值就是value,而value会自己传进去 #del p1.sex 删的是self.__sex
为什么要用property?
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行的一个函数计算出来的,这种特性的使用方式遵循了统一访问的原则
初次之外再看下
ps:Java中面向对象的封装有三种方式: 【public】 这种其实就是不封装,是对外公开的 【protected】 这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开 【private】 这种封装对谁都不公开
Python中并没有在语法上把它们三个內建到自己的class机制中,在C++里一般会将所有的数据都设置为私有,然后提供set和get方法(接口)去设置和获取,在Python中通过property方法可以实现,但是也没有真正的实现
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码,而外部使用者只知道一个接口(函数),只要接口(函数)名,参数不变,使用者的代码永远无需改变。这就提供了一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积 return self.__width * self.__length
#使用者 >>> r1=Room('卧室','egon',20,20,20) >>> r1.tell_area() #使用者调用接口tell_area 400
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码 class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了 return self.__width * self.__length * self.__high
对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area() 8000
静态方法(staticmethod)和类方法(classmethod)
通常情况下,在类中定义的所有函数(注意了,这里说的是所有,跟self啥的没有关系,self也只是一个再普通不过的参数而已)都是对象的绑定方法,对象在调用绑定方法时会自动将自己作为参数传递给方法的第一个参数。除此之外,还有两种常见的方法:静态方法和类方法,二者是为类量身定制的,但是实例非要使用,也不会报错。
1、静态方法(staticmethod)
是一种普通函数,位于类定义的命名空间,不会对任何实例类型进行操作,Python为我们内置了函数staticmethod来把类中的函数定义成静态方法
class Foo: def spam(x,y,z): #类中的一个函数,千万不要懵逼,self和x啥的没有不同都是参数名 print(x,y,z) spam=staticmethod(spam) #把spam函数做成静态方法
基于之前所学装饰器的知识,@staticmethod等同于spam=staticmethod(spam),于是
class Foo: @staticmethod #装饰器 def spam(x,y,z): print(x,y,z)
使用演示
print(type(Foo.spam)) #类型本质就是函数 Foo.spam(1,2,3) #调用函数应该有几个参数就传几个参数 f1=Foo() f1.spam(3,3,3) #实例也可以使用,但通常静态方法都是给类用的,实例在使用时丧失了自动传值的机制 ''' <class 'function'> 1 2 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) print(b.year,b.month,b.day) print(c.year,c.month,c.day)
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 '''
应用场景:
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__,但是结果为 ''' 输出结果: <__main__.Date object at 0x1013f9d68> '''
因为e就是用Date类产生的,所以根本不会触发EuroDate.__str__,解决方法就是用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 '''