Python 基础 面向对象之二 三大特性
Python 基础 面向对象之二 三大特性
上一篇主要介绍了Python中,面向对象的类和对象的定义及实例的简单应用,本篇继续接着上篇来谈,在这一篇中我们重点要谈及的内容有:Python 类的成员、成员修饰符 面向对象的三大特性:继承、多态和封装,貌似今天内容挺多的,没有关系,慢慢来!
一、类中的基本知识:
一、类的成员、成员修饰符
一、字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
- 普通字段属于对象
- 静态字段属于类
1 class Room: 2 tag=1 3 def __init__(self,name,owner,width,length,heigh): 4 self.name=name 5 self.owner=owner 6 self.width=width 7 self.length=length 8 self.heigh=heigh 9 10 11 12 r1=Room('厕所','sb',100,100,100000) 13 print(r1.name) # 访问的是普通字段 14 print(Room.tag) # #直接访问静态字段
so,通过上述例子可以看出:
- 静态字段在内存中只保存一份
- 普通字段在每个对象中都要保存一份(相当于绑定在每个实例化对象的身上,然后本身有一个类“指针”的东西指向类)
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段
二、方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
- 静态方法:由类调用;无默认参数;
class Foo: def __init__(self, name): self.name = name def ord_func(self): """ 定义普通方法,至少有一个self参数 """ # print self.name print ('普通方法') @classmethod def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print ('类方法') @staticmethod def static_func(): """ 定义静态方法 ,无默认参数""" print ('静态方法') # 调用普通方法 f = Foo('sb') f.ord_func() # # 调用类方法 Foo.class_func() f.class_func() # # # 调用静态方法 Foo.static_func() f.static_func()
类方法调用和静态方法调用的时候既可以使用使用类名调用,也可以使用对象来调用,区别在哪里呢?注意:最大的区别是用类名调用的时候是不需要实例化的,而使用对象来访问时是先要进行实例化,之后再进行调用。
三、属性
如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。
对于属性,有以下三个知识点:
- 属性的基本使用
- 属性的两种定义方式
1 class Goods: 2 @property 3 def pricr(self): 4 #print('nijiushidahbi') 5 return "haishininiubi" 6 7 obj = Goods() 8 result = obj.pricr 9 print(result) 10 11 12 显示结果:haishininiubi
由属性的定义和调用要注意一下几点:
- 定义时,在普通方法的基础上添加 @property 装饰器;
- 定义时,属性仅有一个self参数
- 调用时,无需括号
方法:foo_obj.func()
属性:foo_obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回
二、面向对象编程的三大特性介绍
1.继承与派生
继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类。
python中类的继承分为:单继承和多继承
1 class Grandfather: 2 pass 3 4 class Father(Grandfather): #单继承 Father是派生类,grandfather 是基类 5 pass 6 7 class son(Father,Grandfather): #多继承 8 pass
继承使用产生的场景:如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,这就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。
除了继承与派生的概念还有一个概念是需要提出的那就是:组合。组合就是指在一个类中以另外一个类的对象作为数据属性,成为类的组合。
组合与继承两者到底什么的关系?1.继承的方式:通过继承建立了派生类与基类的关系,两者之间是“是”的关系。当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师 2.组合的方式:用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程(当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好)
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('北大','北京') c1=Course('linux',10,'1h',s1) print(c1.name) #linux print(c1.school) # <__main__.School object at 0x02182330> print(c1.school.name) # 北大 print(c1.school.addr) # 北京
在这个例子中呢,创建了两个类,一个是学校类一个是课程类。需要将课程类与学校类进行相关联的时候,继承在这里是不合适的,那我们采用组合的方式来对这两个类进行关联,这样就在实例化一门课程的时候把学校的信息进行关联。
接口的概念:
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(解决代码重用,但是子类与基类出现强耦合)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能。(推举使用)
归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
接口继承:就是定义一个父类,其主要目的就是所有的子类,必须去实现父类的方法(与是否省代码,么有半毛钱关系),父类可以不去具体实现,具体的实现由子类自己去实现。
举个例子:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样。
抽象类: 从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法(这种解释有点像接口了)
#一切皆文件 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)
继承实现的原理:(多继承,很多语言只支持单继承)
Python继承中有两种方式:一种是深度优先;另一种是广度优先;
当类是经典类时,多继承会按照深度优先方式查找;当类是新式类时,多继承会按照广度优先方式查找。注:当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
现在举个例子进行说明:
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') pass f1=F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
在查找的过长中一旦找到查找过程就会立即中断,不会继续查找。
多继承的原理:(Python实现多继承的原理)
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如:
F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'objec
t'>]
子类调用父类方法的实现过程:子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法。
方法一:父类名.父类方法()
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() # 地铁13号线欢迎您 开动啦...
方法二: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): #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self) super().__init__(name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) super(Subway,self).run() class Mobike(Vehicle):#摩拜单车 pass line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run()
注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表。
2.多态(性)
多态是指一个事物有多中形态(一个抽象类有多个子类,so 多态的概念依赖于继承),而多态性是指:指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
多态是反映在执行时的状态,在面向对象中,多态是怎么来得呢?首先,先定义一个基类,再定义一堆子类。其次,才能展现不同的类实例化出的实例去调用同一个方法。
1 class H2O: 2 def __init__(self,name,temperature): 3 self.name=name 4 self.temperature=temperature 5 def turn_ice(self): 6 if self.temperature < 0: 7 print('[%s]温度太低结冰了' %self.name) 8 elif self.temperature > 0 and self.temperature < 100: 9 print('[%s]液化成水' %self.name) 10 elif self.temperature > 100: 11 print('[%s]温度太高变成了水蒸气' %self.name) 12 def aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(self): 13 pass 14 15 class Water(H2O): 16 pass 17 class Ice(H2O): 18 pass 19 class Steam(H2O): 20 pass 21 22 w1=Water('水',25) 23 i1=Ice('冰',-20) 24 s1=Steam('蒸汽',3000) 25 26 w1.turn_ice() 27 i1.turn_ice() 28 s1.turn_ice()
3.封装
装就是把东西包起来,而封的意思就是把口子封起来。为何要在编程的过程中使用这一特性呢?封装数据的作用就是为了保护隐私,而封装方法的作用就是为了隔离复杂程度。
封装其实是有两个层面的意思:
其一:第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者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的形式访问到. f1 = A() # f1.__foo() # 'A' object has no attribute '__foo' f1.bar() # 通过在对象A中创建一个接口,在内部调用私有方法
好了,今天就搞到这里,明天继续!!!