python面向对象三大特性详解封
面向对象编程有三大特性:封装、继承、多态,本文带大家来认识和了解这三个特性~
补充-新式类经典类
1
在python2.x中,新式类是继承了object类的子类,以及该子类的子类子子类...;经典类就是没有继承没有继承object类的子类,以及该子类的子类子子类...。
在python3.x中,如果没有继承任何类,默认就会继承object类,没有经典类和新式类之分。
object类提供了一些常用内置方法的实现,比如用来在打印对象时返回字符串的内置方法__str__,后续文章会做介绍的哦~
封装
1
封装是面向对象编程最重要的特性,封装就是将数据和功能整合到一起,就是我们之前说的将数据与功能装在一起。
针对封装到对象或者类中属性,我们可以严格控制在类外部对它们的访问,即隐藏属性和开放接口。
?
隐藏属性
如何隐藏属性
在类体代码中,如果在一个属性名前加__前缀,就会实现一个对外部隐藏该属性的效果,虽然在类外部无法直接通过对象.属性的方式直接访问双下划线开头的属性,但是知道类名和属性名就可以得到外部访问类属性的方式即对象._类名__属性名,所以说这种操作并没有严格意义上的限制外部访问类内部定义的属性。
这种隐藏对外不对内,这种变形操作只在检查类体语法的时候发生一次,在类外部定义的__开头的属性都不会发生变形。
classTest():__name=test#函数外部访问时变为_Test__namedef__init__(self):self.__x=xdeftest(self):self.__x=test#在类体代码内部可以正常使用属性print(test)print(Test.__name)#AttributeError:typeobjectTesthasnoattribute__nameprint(Test._Test__name)#testprint(Test.__dict__)mappingproxy(...,_Test__name:test,_Test__test:functionTest.__testat0xE75F6E3A0,...})test_obj=Test()test_obj.test()#正常执行print(test_obj.__x)#AttributeError:Testobjecthasnoattribute__xtest_obj.__a=a#类外部定义的__的属性不会发生变形print(test_obj.__a)#a
隐藏属性的目的
定义属性就是为了使用,所以隐藏并不是最终的目的,将数据属性隐藏起来就限制了类外部对数据的直接操作,但是类内部应该提供相应的接口来允许类外部间接的操作数据,可以在接口上增加额外的逻辑来对外部操作类内定义的数据进行严格的控制。比如:
classPeople:def__init__(self,name):self.__name=namedeftell_info(self):pwd=input(请输入暗号).strip()ifpwd==0:print(self.__name)else:print(暗号错误)defset_name(self,new_name):iftype(new_name)isstr:self.__name=new_nameprint(self.__name)else:print(请输入字符串)p=People(python)p.tell_info()p.set_name()
隐藏函数属性的目的是只将用户用到的接口暴露出去,在该接口内可能会用到类内定义的其他方法,但是这些方法用户无需直接调用,就可以将这些方法进行隐藏,隔离程序的复杂度。
隐藏属性和暴露接口就是为了明确的区分类的内外部分,类的内部可以修改类内定义的数据但是不影响外部调用,类外部只需要有一个接口,只要接口的名字、参数不变,那么无论类内部的代码如何改变,外部调用方式都不会发生改变。
继承与派生
2
继承是创建新类的一种方式,通过继承得到的新类可以称为子类或者派生类,被继承的类被称为父类或者基类。
通过继承得到的子类会继承父类的所有属性,python支持多继承,子类可以继承一个或者多个父类。
通过使用类的继承,可以解决类与类之间的代码冗余问题。
类的继承语法如下:
classParent1():x=classParent2():passclassFoo(Parent1):#单继承,父类是Parent1passclassFoo1(Parent1,Parent2):#多进程,父类是Parent1Parent2pass可以通过__bases__查看子类继承的所有父类。print(Foo.__bases__)#(class__main__.Parent1,)print(Foo1.__bases__)#(class__main__.Parent1,class__main__.Parent2)
如果想要找出类和类之间的关系,需要先总结一下多个类的相似之处,就可以得到父类,类与类之间的继承指的就是什么是什么的关系,比如金毛是狗,狗是动物比如:
子类可以继承父类的所有属性,比如我们还是以上篇文章中的外卖系统作为案例,在这个程序中至少存在三个类:商家类、外卖小哥类、顾客类,我们可以先定义出这三个类:
#商家类classMerchants():platform=python外卖平台def__init__(self,name,addr,tel):self.name=name#商家名字self.addr=addr#商家地址self.tel=tel#商家联系电话defreveive_order(self):print(f{self.name}已经接单)#外卖小哥类classRider():platform=python外卖平台def__init__(self,name,addr,tel):self.name=name#外卖小哥姓名self.addr=addr#外卖小哥配送地址self.tel=tel#外卖小哥电话defdistribution(self):print(f{self.name}正在配送)#顾客类classCustomer():platform=python外卖平台def__init__(self,name,addr,tel):self.name=name#顾客姓名self.addr=addr#收货地址self.tel=tel#顾客电话defaccept_delivery(self):print(f{self.name}已经收到外卖)
以上三个类之间存在重复的代码,都属于python外卖平台,都需要独有的name、addr、tel,因此可以得出以下继承关系,实现代码重用:
classPersonnel_information():platform=python外卖平台def__init__(self,name,addr,tel):self.name=nameself.addr=addrself.tel=tel#商家类classMerchants(Personnel_information):defreveive_order(self):print(f{self.name}已经接单)#外卖小哥类classRider(Personnel_information):defdistribution(self):print(f{self.name}正在配送)#顾客类classCustomer(Personnel_information):defaccept_delivery(self):print(f{self.name}已经收到外卖)
商家类、外卖小哥类和顾客类中并没有定义__init__方法,但是会从它们的父类中找到__init__方法,因为子类会继承父类所有的属性和方法,因此仍然可以正常实例化:
mer=Merchants(川菜,山清水秀区,11)rider=Rider(小庄,依山傍水区,)customer=Customer(xu,养生区,)
01
属性查找
单继承属性查找
对象的属性可以通过对象.__dict__查看,有了继承关系后,对象在查找属性时先从自己的__dict__中查找,如果没有则去实例化自己的类中找,找不到再去父类中找,都找不到会抛出异常。注意,属性的查找一定是以定义阶段为准。
classTest():deft1(self):print(fromTestt1)classFoo(Test):deff1(self):print(Foof1)f=Foo()f.t1#运行结果fromTestt1
父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的。
classTest():def__t1(self):print(fromTestt1)deft2(self):self.__t1()print(fromTestt2)classFoo(Test):def__t1(self):print(Foot1)f=Foo()f.t2()#运行结果fromTestt1fromTestt2
多继承属性查找
python面向对象支持多继承,好处是子类可以同时继承多个父类的属性,最大限度的重用代码,但是多继承也有很多缺点,多继承违背了继承表达的什么是什么的关系,而且如果一个类同时继承多个父类,代码的可读性就会变得很差,可能会出现菱形继承的问题。
菱形继承是不同子类拥有同一个非object的父类,而不同的子类又是另一个子类的父类,如果A类中有一个方法,B类或者C类对这个方法进行了重写,D类继承的是哪个版本的方法?
想要弄清楚上面的问题就需要知道类查找属性是按照什么规则查找的,在python中,每定义一个类都会计算出一个方法解析顺序列表(MRO),MRO列表就是一个简单的所有基类的先后顺序列表。
D.mro()#新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法[class__main__.D,class__main__.B,class__main__.C,class__main__.A,classobject]
python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
有菱形继承问题,就会有非菱形继承,如下所示:
不管是菱形继承还是非菱形继承,只需要记住:
如果是类实例化得到的对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去;
如果是由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去。
Mixins机制
多继承实际上提高了程序的耦合程度,但是面向对象的编程思想就是解耦合,解耦合的目的就是提高代码的可读性和可扩展性,那么到底要不要用多继承呢?
python提供了多继承就是为了使用,但是在用多继承的过程中,需要规避两点问题:
1.继承结构尽量不要过于复杂;
2.推荐使用mixins机制,在多继承的背景下满足什么是什么的关系。
mixins机制就是在多继承的背景下尽可能提升多继承代码的可读性,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属关系,所以Mixins机制本质仍是多继承,但同样遵守什么是什么关系,如下代码:
#定义父类---交通工具类classVechicle:print(我是交通工具)#定义飞的功能---Mixin表示该类只是作为一个功能classFlyableMixin():def__init__(self,fly):self.fly=flyclassCivilAircraft(FlyableMixin,Vechicle):#民航飞机passclassHelicopter(FlyableMixin,Vechicle):#直升飞机passclassBus(Vechicle):pass#民航飞机对象c=CivilAircraft(我会飞)print(c.__dict__)#直升飞机对象h=Helicopter(fly)print(h.__dict__)#汽车对象b=Bus()print(b.__dict__)
车、船、直升机、民航飞机都是交通工具,但是只有飞机具有飞的功能,如果将飞的功能添加到交通工具这个父类中是不合理的,所以可以将“飞”这个功能另外定义,采用统一的命名规范(例如Mixin后缀),这个后缀会明确的告诉读代码的人,这个类是作为一个功能添加到子类中,而不是作为父类,当bus这个类混合飞的功能,bus就是会飞的一个交通工具。
但是在使用mixins机制的时候需要注意以下几点:
1.必须表示某一种功能,而不是某类物品
2.python对于mixin类的命名方式一般以Mixin,able,ible为后缀
3.如果有多个功能,那就写多个Mixin类
4.定义的Minx类越多,子类的代码可读性就会越差
02
派生
子类继承父类后会继承父类所有的属性,子类可以直接继承父类的属性,也可以派生出自己的属性,也可以重写父类的属性,子类重用父类属性有以下四种派生方式,我们还是以外卖系统为例:
classPersonnel_information():platform=python外卖平台def__init__(self,name,addr,tel):self.name=nameself.addr=addrself.tel=tel#商家类classMerchants(Personnel_information):defreveive_order(self):print(f{self.name}已经接单)#外卖小哥类classRider(Personnel_information):defdistribution(self):print(f{self.name}正在配送)#顾客类classCustomer(Personnel_information):defaccept_delivery(self):print(f{self.name}已经收到外卖)print(f{self.name}已经收到外卖)
直接继承父类属性
如上述代码,子类直接继承了父类的所有方法和属性。
覆盖父类属性
商家类除了基本的name、addr、tel属性外,还有菜品信息这个属性,但是父类中__init__方法中并没有定义这个属性,因此可以在子类中重写__init__方法,覆盖父类的__init__方法。
classPersonnel_information():platform=python外卖平台def__init__(self,name,addr,tel):self.name=nameself.addr=addrself.tel=tel#商家类classMerchants(Personnel_information):def__init__(self,name,addr,tel,food_info):self.name=nameself.addr=addrself.tel=telself.food_info=food_infodefreveive_order(self):print(f{self.name}已经接单)
向父类索要方法
覆盖父类属性这种写法虽然可以满足要求,但是子类中的__init__方法中存在重复代码,因此子类可以向父类索要该方法,并在该方法内增加自己独有的属性。这种方式不依赖继承关系,可以向任意类索要方法。
classPersonnel_information():platform=python外卖平台def__init__(self,name,addr,tel):self.name=nameself.addr=addrself.tel=tel#商家类classMerchants(Personnel_information):def__init__(self,name,addr,tel,food_info):Personnel_information.__init__(self,name,addr,tel)#向类索要方法self.food_info=food_info#定制自己独有的属性defreveive_order(self):print(f{self.name}已经接单)
super调用父类中的属性
这种方式严格依赖继承关系,调用super会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro,去当前类的父类中找属性。
classPersonnel_information():platform=python外卖平台def__init__(self,name,addr,tel):self.name=nameself.addr=addrself.tel=tel#商家类classMerchants(Personnel_information):def__init__(self,name,addr,tel,food_info):super().__init__(name,addr,tel)#调用的是绑定方法,自动传入self#super(Merchants,self).__init__(name,addr,tel)#与上一行代码等价,但是在python2中必须这么写,python3可以简写self.food_info=food_infodefreveive_order(self):print(f{self.name}已经接单)
多态
3
多态指的是一类事物有多种形态,比如说动物有多种形态,猫、狗、羊。
#????都属于动物一类classAnimal:passclasscat(Animal):passclassDog(Animal):pass
多态性指的是在不考虑对象具体类型的情况下直接使用对象,具体来说就是把对象使用的方法统一起来,比如动物都有发出声音的功能,比如下述代码:
classAnimal:deftalk(self):print(动物会发出的声音...)classCat(Animal):deftalk(self):super().talk()print(喵喵)classDog(Animal):deftalk(self):super().talk()print(汪汪)#产生对象cat=Cat()dog=Dog()#调用方法cat.talk()dog.talk()#运行结果动物会发出的声音...喵喵动物会发出的声音...汪汪
多态的本质就是在于不同的类中定义有相同的方法名,可以不考虑类而统一用一种方式去使用对象,python提供了抽象类的概念来硬性限制子类必须有某些方法名。
01
抽象类
使用抽象类需要注意以下几点:
1.在父类中指定指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化;
2.但凡继承抽象类的子类都必须遵循抽象类规定的标准,若子类中没有遵循抽象类的标准则会抛出异常TypeError,无法实例化。
importabc#使用抽象类必须导入abc包#指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化classAnimal(metaclass=abc.ABCMeta):
abc.abstractmethod#该装饰器限制子类必须定义有一个名为talk的方法deftalk(self):#抽象方法中无需实现具体的功能passclassCat(Animal):#但凡继承Animal的子类都必须遵循Animal规定的标准deftalk(self):passcat=Cat()#若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化02
鸭子类型
不过python更加推崇的是鸭子类型-如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子。
比起使用抽象类和继承来硬性规定某种事物一定属于什么类,鸭子类型在某种程度上实现了程序的解耦合,我们只需要在定义类的时候创建出外观和行为相同的对象就可以了,同样可以实现不考虑对象类型而使用对象。
#像什么就是什么,就具有什么功能classCpu:defread(self):print(cpuread)defwrite(self):print(cpuwrite)classMemory:defread(self):print(memread)defwrite(self):print(memwrite)obj1=Cpu()obj2=Mem()obj1.read()obj1.write()obj2.read()obj2.write()
小庄卖瓜
4
如果想要了解更多python知识,欢迎
转载请注明:http://www.sonphie.com/jbby/14536.html