面向对象进阶
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象进阶相关的知识,希望对你有一定的参考价值。
面向对象在python里面有三大特性:继承,多态,封装一、继承
1、概念
继承是一种创建新类的方式,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类,子类会接受父类的属性,从而解决代码重用问题
2、经典类与新式类
(1)只有在python2中才分新式类和经典类,python3中统一都是新式类
(2)在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
(3)在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
(4)在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
二、继承与抽象
1、原理
抽象即抽取类似或者说比较像的部分
继承是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
2、继承示例
(1)示例一
class Animal: def eat(self): print('%s 吃东西' %self.name) def play(self): print('%s 玩游戏' %self.name) class Cat(Animal): def __init__(self,name): self.name=name self.breed = '猫' def cry(self): print('喵喵叫') class Dog(Animal): def __init__(self,name): self.name=name self.breed = '狗' def cry(self): print('汪汪叫') a1=Cat('小花猫') a2=Dog('牧羊犬') print(a1.__dict__) #{'name': '小花猫', 'breed': '猫'} a1.eat() #对象a1查找顺序,先在自己的__dict__里面找,没有就找Cat类的,所以结果是 小花猫 吃东西 a2.cry() #对象a2查找顺序,先在自己的__dict__里面找,没有就找Dog类的,还没有就找父类Animal的,所以结果是 汪汪叫
(2)示例二
class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') self.f1() class Bar(Foo): def f1(self): print('Bar.f1') b=Bar() b.f2() 分析:b=Bar(),生成Bar类的实例,所以b.f2()执行的时候,查找顺序是县在b自己的__dict__里面找,没有再找Bar类,最后找Foo类,得到打印的Foo.f2,下面还有代码self.f1(),由于b生成Bar的实例,所以还要先在Bar找,所以最后结果是先打印Foo.f2,后打印Bar.f1
3、继承顺序
经典类的查找方式是:深度优先
新式类的查找方式是:广度优先
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 F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
4、继承特点
(1)子类会先于父类被检查
(2)多个父类会根据它们在列表中的顺序被检查
(3)如果对下一个类存在两个合法的选择,选择第一个父类
三、派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类)
需要注意的是:一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def do(self): print ('他在做什么?') class Student(People): def __init__(self,name,age,sex,school_name,classes): People.__init__(self, name, age, sex) #调用父类的方法,还可以用另一种方法: super().__init__(name,age,sex) self.school_name=school_name self.classes=classes def lean(self): People.do(self) #子类调用父类的方法 print ('%s 在听课'%self.name) class Teacher(People): def __init__(self,name,age,sex,school_name,course_name): People.__init__(self,name,age,sex) self.school_name = school_name self.course_name=course_name def teach(self): People.do(self) #子类调用父类的方法 print ('%s 在讲课'%self.name) student1=Student('wang',18,'male','东北大学','一班') teacher1=Teacher('tom',33,'male','北京大学','物理') student1.lean() #结果是“他在做什么?wang 在听课” teacher1.teach() #结果是“他在做什么?tom 在讲课”
四、组合
1、组合与继承的特点对比
(1)继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
(2)组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如人有姓名,年龄...
2、组合与继承的应用
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<%s %s %s>' %(self.name,self.period,self.price)) class Teacher(People): def __init__(self,name,age,sex,job_title): People.__init__(self,name,age,sex) self.job_title=job_title self.course=[] self.students=[] class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age,sex) self.course=[] teacher1=Teacher('wang',28,'male','讲师') student1=Student('li',18,'female') python=Course('python','3mons',3000.0) linux=Course('python','3mons',4000.0) #为老师和学生添加课程 teacher1.course.append(python) teacher1.course.append(linux) student1.course.append(python) #为老师添加学生 teacher1.students.append(student1) #使用 for obj in teacher1.course: obj.tell_info() # 结果是<python 3mons 3000.0>,<python 3mons 4000.0>
五、抽象类
1、抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性
2、在python中实现抽象类
#_*_coding:utf-8_*_ 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): #子类继承抽象类,但是必须定义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)
归一化的好处在于:
1. 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2. 归一化使得使用者可以不加区分的处理所有接口兼容的对象集合
总结:抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
六、子类中调用父类的方法
方法一:指名道姓
方法二:super()
1、注意:即使没有直接继承关系,super仍然会按照mro继续往后查找
A没有继承B,但是A内super会基于C.mro()继续往后找
class A: def test(self): super().test() class B: def test(self): print('from B') class C(A,B): pass c=C() c.test() #打印结果:from B print(C.mro()) #结果是[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
2、指名道姓与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() #A.__init__被重复调用 ''' D的构造方法 B的构造方法 A的构造方法 C的构造方法 A的构造方法 ''' #使用super() 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() #super()会基于mro列表,往后找 ''' D的构造方法 B的构造方法 C的构造方法 A的构造方法 '''
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次
(注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)
七、多态与多态性
1、多态指的是一类事物有多种形态
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')
2、多态性
多态性是指在不考虑实例类型的情况下使用实例
3、多态性的好处
(1)增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
(2)增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
八、封装
1、示例
#这仅仅是一种变形操作且仅仅只在类定义阶段发生变形 #类中所有双下划线开头的名称如__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__N是可以访问到的,这种,在外部是无法通过__x这个名字访问到。
2、注意的问题
(1)这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,即这种操作仅仅只是一种语法意义上的变形,主要用来限制外部的直接访问。
(2)变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形
(3)在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
3、封装的意义
封装数据,可以完成对数据属性的逻辑控制
封装方法,目的是隔离复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做隔离了复杂度,同时也提升了安全性
class ATM: def __card(self): print('插卡') def __auth(self): print('用户认证') def __input(self): print('输入取款金额') def __print_bill(self): print('打印账单') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()
九、特性(property)
1、例一:BMI指数,成人的BMI数值:(过轻:低于18.5,正常:18.5-23.9,过重:24-27,肥胖:28-32,非常肥胖, 高于32)
体质指数(BMI)=体重(kg)÷身高^2(m)
class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property #不加@property,打印时需要print(p1.bmi()) def bmi(self): return self.weight / (self.height**2) p1=People('song',63,1.70) print(p1.bmi) #21.79930795847751 使用者无法察觉自己的bmi是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
十、绑定方法与非绑定方法
1、介绍说明
(1)绑定方法(绑定给谁,谁来调用就自动将它本身当作第一个参数传入):
绑定到类的方法:用classmethod装饰器装饰的方法。 为类量身定制(其实对象也可调用,但仍将类当作第一个参数传入)
绑定到对象的方法:没有被任何装饰器装饰的方法。为对象量身定制(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
(2)非绑定方法:用staticmethod装饰器装饰的方法
不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
2、(1)示例一:
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('1989',12,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)示例二:
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 '''
十一、面向对象的几个内置方法
1、 isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls) 检查是否obj是否是类 cls 的对象
issubclass(sub, super) 检查sub类是否是 super 类的派生类
2、反射
使用演示
class BlackMedium: feature='Ugly' def __init__(self,name,addr): self.name=name self.addr=addr def sell_house(self): print('%s 中介卖房子啦' %self.name) def rent_house(self): print('%s 中介租房子啦' %self.name) b1=BlackMedium('鑫鑫地产','阳光花园') #检测是否含有某属性 print(hasattr(b1,'name')) print(hasattr(b1,'sell_house')) #获取属性 n=getattr(b1,'name') print(n) func=getattr(b1,'rent_house') func() # getattr(b1,'aaaaaaaa') #报错 print(getattr(b1,'aaaaaaaa','不存在啊')) #设置属性 setattr(b1,'sb',True) setattr(b1,'show_name',lambda self:self.name+'sb') print(b1.__dict__) print(b1.show_name(b1)) #删除属性 delattr(b1,'addr') delattr(b1,'show_name') delattr(b1,'show_name111')#不存在,则报错 print(b1.__dict__)
3、__str__
#_*_coding:utf-8_*_ format_dict={ 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名 } class School: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __str__(self): return '(%s,%s)' %(self.name,self.addr) #定义返回的内容,不加这个的话,执行结果是一个内存地址 s1=School('oldboy','北京','私立') print('from str: ',str(s1)) print(s1) ''' str函数或者print函数--->obj.__str__() 注意:这俩方法的返回值必须是字符串,否则抛出异常 '''
4、__del__
析构方法,当对象在内存中被释放时,自动触发执行。
如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__
class Foo: def __del__(self): print('hello') f1=Foo() del f1 print('------->') #输出结果 # hello # -------> class Foo: def __del__(self): print('hello') f1=Foo() print('------->') #输出结果 -------> hello
以上是关于面向对象进阶的主要内容,如果未能解决你的问题,请参考以下文章