十四、面向对象
十四、面向对象
一、引子
1、面向对象也是一种编程思想
核心是对象
对象:在现实生活中实实在在存在的,具备自己的特征和行为的事物
简而言之:对象就是特征和行为的结合体
如何识别是对象:如果能够准确的找到一个事物,它就是一个对象
面向对象编程:我们关注的点是,使用什么样的对象可以完成我的任务
基于该思想编程就好比在创造一个世界,世界是由一个个具体存在的对象组成的,你就是这个世界的上帝
你从一个操作者变成了指挥者,例如:西游记中的如来,他要完成一个传经的任务,要把经书传到东土大唐去,本来要自己去做的,但是太麻烦,所以叫观音找了唐僧师徒5个对象来帮他完成这个任务
面向对象的优点:
1.对于指挥者(程序员)来说,不需要再关心具体的步骤
2.扩展性高,一个单独的个体(对象)的特征或行为发生变化时,不会影响到其他人
面向对象的缺点:
1.程序的复杂程度高,你得需要设计这些个对象
2.程序的执行结果可控制低
注意点:要避免过度设计问题
2、类与对象
类就是分类,类型的意思
定义:一堆具备相同特征和行为事物的抽象概念,不实际存在
先有类还是先有对象:
生活中:是通过对象的特征和行为抽取而来的
编程中:必须是先有类,才能有对象,因为你必须先告诉程序,你这个对象有什么样的特征和行为
类的作用就是描述该类的对象具备什么样的特征和行为
在python3中统一了类与类型的概念
3、使用面向对象编程
面向对象是更高程度的一种封装
1 #用函数设计一个类和对象 2 def student(name,age,sex,hobby): 3 def study(student): 4 print('%s在教室里学习'%student['name']) 5 6 def play(student): 7 print('%s喜欢%s'%(student['name'],student['hobby'])) 8 9 def init(name, age, sex, hobby): 10 dic = {'name': name, 11 'age': age, 12 'sex': sex, 13 'hobby': hobby, 14 'study':study, 15 'play':play} 16 return dic 17 18 return init(name,age,sex,hobby) 19 20 stu1=student('maple',18,'male','read') 21 print(stu1) 22 stu1['play'](stu1) 23 stu2=student('angel',18,'female','shopping') 24 print(stu2) 25 stu2['play'](stu2)
1 #利用python的关键字class能更简单的帮我们创建类和对象,调用方法也更简单 2 3 class Student: 4 def __init__(self,name,age,sex,hobby): 5 self.name=name 6 self.age=age 7 self.sex=sex 8 self.hobby=hobby 9 def study(self): 10 print('%s在教室里学习'%self.name) 11 12 def play(self): 13 print('%s喜欢%s'%(self.name,self.hobby)) 14 15 stu1=Student('maple',18,'male','read') 16 stu2=Student('angel',18,'female','shopping') 17 stu1.play() 18 stu2.play()
1 country='日本' 2 3 class china: 4 country='中国' 5 def __init__(self,city): 6 self.city=city 7 #不带点的变量,找的不是类里面的,也不是实例化里的 8 print(country) 9 def have(self): 10 print('%s有好吃的'%self.city) 11 12 city1=china('上海') 13 print(city1.country) 14 print(china.country)
二、类的继承和派生
1、什么是继承:
在程序中继承是一种新建子类的方式,新创建的类称之为子类、派生类,被继承的类称之为父类、基类、超类
继承描述的是一种遗传关系,子类可以重用父类的属性
1.2、为何要用继承;
减少类与类之间的代码冗余问题
注意:也不要过度使用类与类的继承,反而容易造成代码的解耦合差
1.3、如何使用继承:
先抽象再继承
在python中继承的特点是:即有单继承也和多继承
1 class Parent1: 2 pass 3 4 class Parent2: 5 pass 6 7 8 class Sub1(Parents1): 9 pass 10 11 class Sub2(Parents2): 12 pass 13 14 print(Sub1.__bases__) 15 print(Sub2.__bases__)
python2与python3在继承上的区别:
新式类:但凡继承object类的子类,以及该子类的子子类。。。都称之为新式类
经典类:没有继承object类的子类,以及该子类的子子类。。。都称之为经典类
只有在python2中才区分经典类与新式类
在单继承背景下,无论是新式类还是经典类属性查找顺序都一样:
先找object--->类--->父类。。。
1 class Foo: 2 def f1(self); 3 print('Foo.f1') 4 5 def f2(self); 6 print('Foo.f2') 7 self.f1() 8 9 10 class Bar(Foo): 11 def f1(self): 12 print('Bar.f1') 13 14 15 obj=Bar() 16 17 obj.f2() 18 19
在多继承的背景下,如果一个子类继承了多个分支,但是多个分支没有汇聚到一个非object类,无论是新式类还是经典类,属性查找顺序都一样,会按照从左到右的顺序一个分支一个分支的查找下去
class E: xxx='E' pass class F: xxx='F' pass class B(E): xxx='B' pass class C(F): xxx='C' pass class D: xxx='D' pass class A(B,C,D): xxx='A' pass obj=A() print(A.mro())
在多继承背景下,如果一个子类继承了多个分支,但是多个分支最终汇聚到一个非object类(菱形继承问题)
新式类:广度优先查找:obj->A->B->E->C->F->D->G->object
经典类:深度优先查找:obj->A->B->E->G->C->F->D
1 class G: 2 xxx='G' 3 4 class E(G): 5 xxx='E' 6 pass 7 8 class F(G): 9 xxx='F' 10 pass 11 12 class B(E): 13 xxx='B' 14 pass 15 16 class C(F): 17 xxx='C' 18 pass 19 20 class D(G): 21 xxx='D' 22 pass 23 24 class A(B,C,D): 25 xxx='A' 26 pass 27 28 29 print(A.mro())
总结:其实所有的继承顺序都是按照A.mro()下的3c算法所制造出来的列表顺序来查找的
2、类的派生:
在子类派生出的新功能中如何重用父类的功能:
方式一:指名道姓的访问某一个类中的函数(注意:与继承无关)
1 class OldboyPeople: 2 school = 'Oldboy' 3 def __init__(self, name, age, gender): 4 self.name = name 5 self.age = age 6 self.gender = gender 7 8 # print(OldboyPeople.__init__) 9 10 class OldboyStudent(OldboyPeople): 11 def choose_course(self): 12 print('%s is choosing course' %self.name) 13 14 class OldboyTeacher(OldboyPeople): 15 # tea, 'egon', 18, 'male', 10, 3000 16 def __init__(self, name, age, gender,level,salary): 17 OldboyPeople.__init__(self, name, age, gender) 18 19 self.level=level 20 self.salary=salary 21 22 def score(self,stu,num): 23 stu.num=num 24 print('老师%s给学生%s打分%s' %(self.name,stu.name,num)) 25 26 stu=OldboyStudent('kevin',38,'male') #__init___(stu1,'kevin',38,'male') 27 print(stu.__dict__) 28 tea=OldboyTeacher('egon',18,'male',10,3000) #__init___(tea,'egon',18,'male',10,3000) 29 print(tea.__dict__) 30 print(stu.school) 31 print(tea.school)
方式二:super()函数功能,在python3中super可以不传参数,调用该函数会得到一个特殊的对象,该对象是专门用来访问父类中的属性
1 class OldboyPeople: 2 school = 'Oldboy' 3 def __init__(self, name, age, gender): 4 self.name = name 5 self.age = age 6 self.gender = gender 7 8 class OldboyTeacher(OldboyPeople): 9 # tea, 'egon', 18, 'male', 10, 3000 10 def __init__(self, name, age, gender,level,salary): 11 super(OldboyTeacher,self).__init__(name, age, gender) 12 13 self.level=level 14 self.salary=salary 15 16 def score(self,stu,num): 17 stu.num=num 18 print('老师%s给学生%s打分%s' %(self.name,stu.name,num)) 19 20 tea=OldboyTeacher('egon',18,'male',10,3000) #__init___(tea,'egon',18,'male',10,3000) 21 print(tea.__dict__)
三、组合
1、什么是组合:
一个对象的属性是来自于另外一个类的对象,称之为组合
2、为何要用组合:
组合也是用来解决类与类代码冗余的问题
3、如何用组合:
把一个类或一个对象的名称空间地址指向另外一个类或对象的名称空间
把两者的名称空间组合在一起
1 class Foo: 2 a=111 3 def __init__(self,x,y): 4 self.x=x 5 self.y=y 6 def func1(self): 7 print('Foo内的功能') 8 class Bar: 9 b=222 10 def __init__(self,m,n): 11 self.m=n 12 self.n=n 13 def func2(self): 14 print('Bar内的功能') 15 16 obj1=Foo(10,20) 17 obj2=Bar(30,40) 18 19 obj1.xxx=obj2 20 21 print(obj1.xxx.m,obj1.xxx.n,obj1.xxx.func2)
四、封装
1、什么是封装:
装指的是把属性装进一个容器
封指的是隐藏的意思,但是这种隐藏是对外不对内的
2、为何要用封装:
封装不是单纯意义的隐藏
封装数据属性的目的:将数据属性封装起来,类外部的使用就无法直接操作该数据属性了
需要类内部开一个接口给使用者,类的设计者可以在接口之上附加任意的逻辑,从而严格控制使用者对属性的操作
封装函数属性的目的:隔离复杂度
3、如何封装:
只需要在属性前加上__开头,该属性就会被隐藏起来,该隐藏具备的特点:
1.只是一种语法意义上的变形,即__开头的属性会在检测语法时发生变形_类名__属性名
2.这种隐藏是对外不对内的,因为在类内部检测语法时所有的代码统一都发生的变形
3.这种变形只在检测语法时发生一次,在类定义之后新增的__开头的属性并不会发生变形
4.如果父类不想让子类覆盖自己的属性,可以在属性前加__开头
1 class Foo: 2 __x=111#_F__x 3 def __init__(self,m,n): 4 self.__m=m#_F__m 5 self.n=n 6 def __func(self):#_F__func 7 print("Foo.func") 8 def func1(self): 9 print(self.__m) 10 print(self.__x) 11 12 obj1=Foo(10,20) 13 print(obj1.func1()) 14 15 # 封装数据属性的真实意图 16 class People: 17 def __init__(self,name,age): 18 self.__name=name 19 self.__age=age 20 def get_info(self): 21 print('<name:%s,age:%s>'%(self.__name,self.__age)) 22 23 def set_info(self,new_name,new_age): 24 if type(new_name) is not str: 25 print('名字必须是str类型') 26 return 27 elif type(new_age)is not int: 28 print('年龄必须是int类型') 29 return 30 self.__name=new_name 31 self.__age=new_age 32 33 obj1=People('maple',18) 34 obj1.get_info() 35 obj1.set_info('angel',20)
五、装饰器property
1 # property装饰器 2 3 class People: 4 def __init__(self,name,weight,height): 5 self.weight=weight 6 self.height=height 7 self.name=name 8 9 @property#该装饰器作用是把函数属性变成像数据属性一样调用 10 def bmi(self): 11 return self.weight/(self.height**2) 12 13 obj=People('maple',72,1.7) 14 print(obj.bmi) 15 16 # 需要了解的property的用法 17 class People: 18 def __init__(self,name): 19 self.__name=name 20 21 @property 22 def name(self): 23 if not self.__dict__: 24 print('该名字不存在了') 25 return 26 return '<name:%s>'%self.__name 27 28 @name.setter 29 def name(self,new_name): 30 if type(new_name)is not str: 31 print('名字必须是str类型') 32 return 33 self.__name=new_name 34 35 @name.deleter 36 def name(self): 37 y=input('你确定要删除这个名字吗?y/n').strip() 38 if y=='y': 39 del self.__name 40 print('删除了') 41 return 42 else: 43 return 44 45 obj1=People('maple') 46 obj1.name 47 obj1.name='angel' 48 del obj1.name 49 obj1.name
六、多态与多态性
1、什么是多态:
同一种事物的多种形态
2、为何要用多态:
多态性:指的是可以在不用考虑对象具体类型的前提下而直接使用对象下的方法
3、如何用多态:
1 #一个模块,作用是强制让子类继承父类中的函数属性功能 2 import abc 3 4 5 class Animal(metaclass=abc.ABCMeta): 6 @abc.abstractmethod#此装饰器的作用就是要求子类们必须使用相同名字的函数功能 7 def speak(self): 8 pass 9 10 class People(Animal): 11 def speak(self): 12 print('say hello') 13 14 class Dog(Animal): 15 def speak(self): 16 print('汪汪汪') 17 18 class Pig(Animal): 19 def speak(self): 20 print('咕力咕力') 21 22 people1=People() 23 dog1=Dog() 24 pig1=Pig() 25 26 people1.speak() 27 dog1.speak() 28 pig1.speak()
七、绑定方法与非绑定方法
1、绑定方法:
特殊之处:绑定给谁就因该由谁来调用,谁来调用就会将谁当做第一个参数自动传入
绑定给对象的方法:在类中定义函数没有被任何装饰器修饰的情况下,默认就是绑定对象的
1.对象与类中的某个函数的绑定关系
2.关键处就是绑定后会自动传值,传的是对象自己
绑定给类的方法:为类中定义函数添加一个装饰器classmethod就是绑定类的
2、非绑定方法:
特殊之处:非绑定方法就是一个普通函数,即不与类绑定也不与对象绑定,意味着类与对象去调用,就像普通函数一样,该传几个参数就传几个参数,不会自动传值
非绑定方法:为类中定义函数添加一个装饰器staticmethod就是非绑定方法
1 class Foo: 2 def func1(self): 3 print('func1',self) 4 5 @classmethod 6 def func2(cls): 7 print('func2',cls) 8 9 @staticmethod 10 def func3(x,y): 11 print('func3',x,y) 12 13 obj=Foo() 14 # 一、绑定给对象的方法: 15 # 绑定给对象的,应该由对象来调 16 obj.func1() 17 18 # 绑定给对象的方法.类也可以调用,但是类调用就是一个普通 函数,没有自动传值的效果 19 print(obj.func1()) 20 print(Foo.func1(10)) 21 Foo.func1(10) 22 23 # 二、绑定给类的方法: 24 # 绑定给类的,应该由类来调用,但对象也能调用,不过对象调用的话,自动传值还是传的类自己 25 print(Foo.func2()) 26 print(obj.func2()) 27 Foo.func2() 28 obj.func2() 29 30 # 三、非绑定方法: 31 # 即不绑定类,也不绑定对象 32 print(obj.func3(1,2)) 33 print(Foo.func3(3,4)) 34 obj.func3(1,2) 35 Foo.func3(3,4)
八、反射
反射:反射就是指通过字符串来操作属性
1 class Foo: 2 def __init__(self,name,age): 3 self.name=name 4 self.age=age 5 6 def tell_info(self): 7 print('<%s:%s>'%(self.name,self.age)) 8 9 obj=Foo('maple',18) 10 11 #hasattr#判断类和对象的属性是否存在 12 print(hasattr(obj,'name')) 13 print(hasattr(obj,'tell_info')) 14 15 #getatr获取类和对象的属性 16 res=getattr(obj,'name') 17 print(res) 18 19 res=getattr(obj,'tell_info') 20 print(res) 21 22 #setattr#修改或添加类和对象的属性 23 setattr(obj,'name','ffm') 24 setattr(obj,'gender','male') 25 print(obj.name) 26 print(obj.gender) 27 28 #delattr删除类和对象的属性 29 delattr(obj,'name') 30 print(obj.name)
九、内置方法
内置方法就是一些__名字__的方法:这些方法会在某一个特定的条件下自动触发
__str__:会在对象被打印时自动触发,然后将返回值返回给print功能进行打印
1 class People: 2 def __init__(self,name,age): 3 self.name=name 4 self.age=age 5 6 def __str__(self): 7 return '<%s:%s>'%(self.name,self.age) 8 9 people1=People('maple',18) 10 print(people1)
__del__:会在对象被删除时自动触发执行,用来在对象被删除前回收系统资源
1 class Foo: 2 def __del__(self): 3 print('>>>>>>') 4 5 obj=Foo() 6 del obj 7 print('其他代码') 8 9 10 class Bar: 11 def __init__(self,x,y,filepath): 12 self.x=x 13 self.y=x 14 self.f=open(filepath,'r',encoding='utf-8') 15 16 def __del__(self): 17 #写回收系统资管相关的代码 18 self.f.close() 19 20 obj=Bar(10,20) 21 del obj
#isinstance,issubclass,两个内置函数
1 #isinstance用来判断对象是不是这个类型 2 print(isinstance([],list)) 3 4 class Foo: 5 pass 6 7 obj=Foo() 8 print(isinstance(obj,Foo)) 9 10 #issubclass用来判断是不是子类关系 11 class Foo: 12 pass 13 14 class Bar(Foo): 15 pass 16 17 print(issubclass(Bar,Foo))
__getattr__是python里的一个内置函数,可以很方便地动态返回一个属性;
当调用不存在的属性时,Python会试图调用__getattr__(self,'key')来获取属性,并且返回key;
class Dict(dict): def __init__(self, **kw): super().__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value
class Student(object): def__getattr__(self, attrname): ifattrname =="age": return40 else: raiseAttributeError, attrname x =Student() print(x.age) #40 print(x.name) #error text omitted.....AttributeError, name
这里定义一个Student类和实例x,并没有属性age,当执行x.age,就调用_getattr_方法动态创建一个属性;
下面展示一个_getattr_经典应用的例子,可以调用dict的键值对
class ObjectDict(dict): def __init__(self, *args, **kwargs): super(ObjectDict, self).__init__(*args, **kwargs) def __getattr__(self, name): value = self[name] if isinstance(value, dict): value = ObjectDict(value) return value if __name__ == '__main__': od = ObjectDict(asf={'a': 1}, d=True) print od.asf, od.asf.a # {'a': 1} 1 print od.d # True
十、元类
1、什么是元类
类本质上也是一个对象,而元类就是用来创建类的一个东西,在python中时一切皆对象
#手动调用type实现实例化 class_name='GIG' bases=(object,) dict={} class_body=''' def __init__(self): print('GIG初始化') ''' #执行一堆字符串代码,将生产的内容放到dict中 exec(class_body,{},dict) #调用type产生一个类 g=type(class_name,bases,dict) print(g)
默认情况下,所有的类都是type这个元类来实例化的
1 #单例元类方法模板 2 3 class MyMataClass(type): 4 instance=None 5 #cls这里是Play类名,call会在Play实例化时触发执行 6 def __call__(cls, *args, **kwargs): 7 if not MyMataClass.instance: 8 #调用object的new产生一个空对象 9 MyMataClass.instance=object.__new__(cls) 10 print('创建一个新对象') 11 #执行init函数初始化对象,*args, **kwargs代表Play实例化传的参数 12 MyMataClass.instance.__init__(*args, **kwargs) 13 #返回对象 14 return MyMataClass.instance 15 16 17 class Play(metaclass=MyMataClass): 18 def play(self,ball): 19 print('开始玩%s'%ball) 20 21 def __init__(self): 22 pass 23 24 #创建一个对象 25 b1=Play() 26 #此时第2个对象不会创建 27 b2=Play() 28 b1.play('足球') 29 b1.play('篮球')