三大编程范式
1、面向过程编程
2、函数式编程
3、面向对象编程
编程进化论
1、编程最初是无组织无结构的,由简单控制流中按步写指令
2、从上述的指令中提取重复的代码块或逻辑,组织到一起(比如定义了一个函数),便实现了代码重用,且代码由无结构走向结构化,创建程序的过程变得更加具有逻辑性。
3、我们定义函数都是独立于函数外定义变量,然后作为参数传递给函数,这意味着:数据与动作是分离的
4、如果我们把数据和动作内嵌到一个结构(函数或类)里面,那么我们就有了一个“对象系统”(对象就是数据与函数整合到一起的产物)
1 #学校类 2 #特征:name,addr,type 3 #动作:考试,招生,招聘老师,开除学生 4 5 def school(name,addr,type): 6 def init(name,addr,type): #定义初始化类:属性和动作 7 sch= { 8 "name":name, 9 "address":addr, 10 "type":type, 11 "exam":exam, 12 "hire":hire 13 } 14 return sch 15 16 def exam(school): 17 print("%s学校在考试" % school["name"]) 18 19 def hire(school): 20 print("%s%s学校在招聘老师" % (school["name"], school["type"])) 21 22 return init(name,addr,type) #在这一步调用init函数 23 24 s1 = school("东华","北京东路","私立") 25 print(s1) 26 #{'name': '东华', 'address': '北京东路', 'type': '私立', 'exam': <function school.<locals>.exam at 0x0000024FDF0C9488>, 'hire': <function school.<locals>.hire at 0x0000024FDF0C9510>} 27 print(s1["name"]) 28 # 东华 29 s1["exam"](s1) 30 # 东华学校在考试 31 s1["hire"](s1) 32 # 东华私立学校在招聘老师 33 s2 = school("清华", "海淀区", "公立") 34 s2["hire"](s2) 35 # 清华公立学校在招聘老师
面向对象设计与面向对象编程
面向对象设计(Object-oriented design):将一类具体事物的数据和动作整合到一起,即面向对象设计
面向对象设计(OOD)不会特别要求面向对象编程语言。事实上,OOD可以由纯结构化语言来实现(如C),但如果想要构造具备对象性质和特点的数据类型,就必须在程序上作更多努力。
面向对象编程(Object-oriented programming):用定义类+实例/对象的方式去实现面向对象的设计
类和对象
类:把一类事物的相同的特征和动作整合到一起就是类
对象:是基于类而创建的一个具体的事物(具体存在的),也是特征和动作整合到一起
类与对象的关系:对象都是由类产生的。
什么是实例化:由类生产对象的过程叫实例化,类实例化的结果就是一个对象,或者说是一个实例。
类
初识类
在python中声明类和声明函数极其相似
1 """ 2 class 类名: #类的首字母大写 3 "类的文档字符串" 4 类体 5 """ 6 #创建一个类 7 class Data: 8 pass 9 10 #用类Data实例化一个对象d1 11 d1 = Data()
注意:在Python2中需要区分经典类和新式类,在Python3中无论如何定义,都是新式类
1 #经典类 2 class Data: 3 pass 4 #新式类 5 class New_data(object): #object是父类 6 pass
属性
类是用来描述一类事物,类的对象指的是这一类事物中的一个个体
凡是事物就有属性,属性分为:
1、数据属性:即变量
2、函数属性:即函数,在面向对象里通常称为方法
注意:类和对象均用点来访问自己的属性
类的属性
数据属性即变量,类的定义与函数极其类似,其实可以用函数的作用域来理解类的属性调用
类的数据属性
1 class Student: 2 #学生类 3 job = "study" 4 def homework(): 5 print("学生在写作业") 6 def exam(self): 7 print("学生在考试") 8 9 print(Student.job) 10 # study 11 print(dir(Student))#查看属性 12 # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'exam', 'homework', 'job'] 13 print(Student.__dict__)#查看类的属性字典 14 # {'__module__': '__main__', 'job': 'study', 'homework': <function Student.homework at 0x000002172F3E9488>, 'exam': <function Student.exam at 0x000002172F3E9510>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None} 15 print(Student.__dict__["homework"]) 16 # <function Student.homework at 0x000001BE1A489488> 17 Student.__dict__["homework"]() 18 # 学生在写作业 19 Student.homework() 20 # 学生在写作业 21 Student.exam("qq") 22 # 学生在考试 23 24 #特殊的类的属性 25 print(Student.__name__) #类的名字(字符串) 26 # Student 27 print(Student.__doc__) #类的文档字符串 28 # None 29 print(Student.__base__) #类的第一个父类(继承时再了解) 30 # <class 'object'> 31 print(Student.__bases__) #类的所有父类构成的元组 32 # (<class 'object'>,) 33 print(Student.__dict__) #类的属性 34 print(Student.__module__) #类定义所在的模块 35 # __main__ 36 print(Student.__class__) #示例对应的类(仅新式类中) 37 # <class 'type'>
对象
对象由类实例化而来
实例化
1 class Student: 2 #学生类 3 def __init__(self,name,gender,age): 4 print("初始化函数开始运行") 5 self.mingzi = name 6 self.xingbie = gender 7 self.nianji = age 8 print("初始化函数运行结束") 9 def homework(self): 10 print("[%s学生]在写作业"%self.mingzi) 11 def exam(self): 12 print("{%s岁}的[%s学生]在考试"%(self.nianji,self.mingzi)) 13 14 s1=Student("Alex","male",20) #实例化 15 # 初始化函数开始运行 16 # 初始化函数运行结束 17 print(s1.__dict__) 18 # {'mingzi': 'Alex', 'xingbie': 'male', 'nianji': 20} 19 print(s1.__dict__["mingzi"]) 20 # Alex 21 print(s1.nianji) 22 # 20 23 Student.exam(s1) 24 # {20岁}的[Alex学生]在考试
函数和类的属性
类属性又称为静态变量,或者是静态数据。这些数据与它们所属的类对象是绑定的,不依赖于任何类实例。在C++ 或Java中,这种类型的数据相当于在一个变量声明前加上static关键字。
1 class Student: 2 #学生类 3 nationality = "中国" 4 def __init__(self,name,gender,age): 5 self.mingzi = name 6 self.xingbie = gender 7 self.nianji = age 8 9 def homework(self,classroom): 10 print("[%s学生]在(%s)写作业"%(self.mingzi,classroom)) 11 def exam(self): 12 print("{%s岁}的[%s学生]在考试"%(self.nianji,self.mingzi)) 13 14 #查询 15 print(Student.nationality) 16 # 中国 17 18 #修改 19 s1 = Student("Alex","male",40) 20 print(s1.nationality) 21 # 中国 22 23 #增加 24 Student.pen = "pencil" 25 print(s1.pen) 26 # pencil 27 28 #删除 29 del Student.pen 30 print(s1.pen) 31 # Wrong 32 33 #增加函数属性 34 def course(self,course): 35 print("%s在上%s课"%(self.mingzi,course)) 36 Student.course = course 37 s1.course("哲学") 38 # Alex在上哲学课
1 class Student: 2 #学生类 3 nationality = "中国" 4 def __init__(self,name,gender,age): 5 self.mingzi = name 6 self.xingbie = gender 7 self.nianji = age 8 9 def homework(self,classroom): 10 print("[%s学生]在(%s)写作业"%(self.mingzi,classroom)) 11 def exam(self): 12 print("{%s岁}的[%s学生]在考试"%(self.nianji,self.mingzi)) 13 14 s1 =Student("ALLY","FEMALE",18) 15 16 #查询 17 print(s1.mingzi) 18 # ALLY 19 s1.homework("B515") 20 # [ALLY学生]在(B515)写作业 21 22 #增加 23 s1.pen = "pencil" 24 print(s1.pen) 25 # pencil 26 27 #修改 28 s1.nianji = 30 29 print(s1.__dict__) 30 # {'mingzi': 'ALLY', 'xingbie': 'FEMALE', 'nianji': 30, 'pen': 'pencil'} 31 32 #删除 33 del s1.mingzi 34 print(s1.__dict__) 35 # {'xingbie': 'FEMALE', 'nianji': 18, 'pen': 'pencil'}
对象和实例属性的对比
1 country = "日本" 2 class Chinese: 3 country = "中国" 4 def __init__(self,name): 5 self.name = name 6 print(">>>>",country) 7 8 print(Chinese.__dict__) 9 print(Chinese.country) #使用.的方式调用的,会去类里 10 # 中国 11 p1 = Chinese('JENNY') #不是使用.的方式调用的,跟类无关,不会去类里 12 # >>>> 日本
1 class Chinese: 2 country = "中国" 3 l = ["a","b"] 4 def __init__(self,name): 5 self.name = name 6 p1=Chinese("Jenny") 7 p1.l = [1,2,3] #修改了实例的属性 8 print(Chinese.l) 9 # ['a', 'b'] 10 p1.l.append("c") #修改了类的属性 11 print(Chinese.l) 12 # ['a', 'b', 'c']
静态属性、类方法和静态方法
1 class Room: 2 def __init__(self, name, owner, width, length, height): 3 self.name = name 4 self.owner = owner 5 self.width = width 6 self.length = length 7 self.height = height 8 @property#把函数属性变成了数据属性 9 def call_area(self): 10 return self.length * self.width 11 12 r1 = Room('503', 'Mr.Xu', 6, 3, 2.5) 13 r2 = Room('101', 'Jenny', 40, 20, 3) 14 print(r1.call_area) 15 # 18 16 print(r1.name) 17 # 503
1 class Room: 2 tag = 1 3 def __init__(self, name, owner, width, length, height): 4 self.name = name 5 self.owner = owner 6 self.width = width 7 self.length = length 8 self.height = height 9 10 @classmethod 11 def tell_tag(cls): 12 return cls.tag 13 14 print(Room.tell_tag()) #无需实例化就可以实现类级别的方法 15 # 1
1 class Room: 2 def __init__(self, name, owner, width, length, height): 3 self.name = name 4 self.owner = owner 5 self.width = width 6 self.length = length 7 self.height = height 8 @staticmethod #类的数据包,不跟类绑定,不跟实例绑定 9 def sightseeing(a,b,c): 10 print("%s、%s和%s在看风景"%(a,b,c)) 11 Room.sightseeing("a","b","c") 12 # a、b和c在看风景
注意:staticmethod方法只是名义上的归属类管理,不能使用类变量和实例变量,是类的工具包
组合
定义一个类,包括数据属性,这几个属性又可以是通过一个类实例化的对象,这就是组合
用途:
1、做关联
2、小的组成大的
1 class School: 2 def __init__(self,schoolname,schooladdr): 3 self.schoolname = schoolname 4 self.schooladdr = schooladdr 5 class Course: 6 def __init__(self,coursename,price,period,school): 7 self.coursename = coursename 8 self.price = price 9 self.period = period 10 self.school = school 11 12 s1 = School("Tsinghua","Haidian") 13 s2 = School("DUT","Ganjingzi") 14 msg = """ 15 1 清华 16 2 大工 17 """ 18 19 20 while True: 21 print(msg) 22 menu = { 23 "1": s1, 24 "2": s2 25 } 26 choice = input("选择学校:") 27 school_obj = menu[choice] 28 coursename = input("课程名字:") 29 price = input("费用:") 30 period = input("课程周期:") 31 new_course = Course(coursename,price,period,school_obj) 32 print("课程【%s】授课地点在{%s}学校"%(new_course.coursename,new_course.school.schoolname)) 33 # 1 34 # 清华 35 # 2 36 # 大工 37 # 38 # 选择学校:2 39 # 课程名字:python 40 # 费用:4000 41 # 课程周期:15 42 # week 43 # 课程【python】授课地点在 44 # {DUT} 45 # 学校 46 # c1 = Course("Linux",5000,"14week",s1) 47 # print(c1.__dict__) 48 # # {'coursename': 'Linux', 'price': 5000, 'period': '14week', 'school': <__main__.School object at 0x00000247FA576390>} 49 # print(c1.school) 50 # # <__main__.School object at 0x000002442E3E72E8> 51 # print(c1.school.schoolname) 52 # # Tsinghua
面向对象编程的三大特性
继承
类的继承跟现实生活中的父、子、孙的继承关系一样,父类称为基类。
Python中的类的继承分为:单继承和多继承
1 class ParentClass1: 2 pass 3 class ParentClass2: 4 pass 5 class SubClass(ParentClass1): #单继承 6 pass 7 class SubClass(ParentClass1,ParentClass2): #多继承 8 pass
子继承到底继承了父类的什么属性?
子类继承了父类的所有属性
1 class Parent: 2 money = 10000 3 def __init__(self,name): 4 print("Dady") 5 self.name = name 6 def hit_son(self): 7 8 print("%s is hitting son"%self.name) 9 class Son(Parent): #单继承 10 money = 2000 11 pass 12 s1 = Son("Alex") 13 print(s1.name) 14 print(s1.hit_son()) 15 # Dady 16 # Alex 17 # Alex is hitting son 18 print(s1.money) 19 print(Parent.money) 20 # 2000 21 # 10000
什么时候用继承?
1、当类之间有显著差异,且较小的类是较大的类所需要的组件时,用组合比较好
2、当类之间有很多相同功能,提取这些共同功能做成基类,用继承比较好
继承同时具有两种含义
1、继承基类的方法,并且做出自己的改变或者扩展(代码重用)——少用
2、声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法
继承的第二种含义非常重要,它叫“接口继承”。
接口继承实际上要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁地处理实现了特定接口的所有对象”——这在程序设计上叫做“归一化”。
归一化使得高层的外部使用者可以不加区分地处理所有接口兼容的对象集合——就像linux的范文件概念一样,所有东西都可以当做文件处理,不必关心它是内存、硬盘网络还是屏幕
继承顺序:
1、Python的类可以继承多个类,而Java和C#只能继承一个
2、Python的类如果继承多个类,那么找寻的方式是深度优先和广度优先。
1 class A: 2 def test(self): 3 print("A") 4 class B(A): 5 def test(self): 6 print("B") 7 class C(A): 8 def test(self): 9 print("C") 10 class D(B): 11 def test(self): 12 print("D") 13 class E(C): 14 def test(self): 15 print("E") 16 class F(D,E): 17 def test(self): 18 print("F") 19 print(F.__mro__) 20 # (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 21 # order:F->D->B->E->C->A
子类中调用父类方法
1 class Animal: 2 def __init__(self,name,bark): 3 self.name = name 4 self.bark = bark 5 class Cat(Animal): 6 def __init__(self,name,bark,kind): 7 self.kind = kind 8 c1 = Cat("小小","喵喵","英短") 9 print(c1.kind) 10 # 英短
super().方法:可以修改父类名称,不影响子类
1 class Animal: 2 def __init__(self,name,bark): 3 self.name = name 4 self.bark = bark 5 class Cat(Animal): 6 def __init__(self,name,bark,kind): 7 super().__init__(name,bark) 8 self.kind = kind 9 c1 = Cat("小小","喵喵","英短") 10 print(c1.name) 11 # 小小
多态
多态指出了对象如何通过它们共同的属性和动作来操作及访问,而不需考虑它们具体的类。
多态表明了动态(又名,运行时)绑定的存在,允许重载及运行时类型确定和验证。
1 class H2O: 2 def __init__(self,name,temp): 3 self.name = name 4 self.temp = temp 5 def turn_ice(self): 6 if self.temp<0: 7 print("%s在%s℃下变为冰"%(self.name,self.temp)) 8 elif self.temp>100: 9 print("%s在%s℃下变为水蒸气"%(self.name,self.temp)) 10 else: 11 print("%s在%s℃下变为液态水"%(self.name,self.temp)) 12 class Ice(H2O): 13 pass 14 class Steam(H2O): 15 pass 16 class Water(H2O): 17 pass 18 19 i1 = Ice("Ice",90) 20 s1 = Steam("Steam",-5) 21 w1 = Water("Water",120) 22 i1.turn_ice() 23 s1.turn_ice() 24 w1.turn_ice() 25 # Ice在90℃下变为液态水 26 # Steam在-5℃下变为冰 27 # Water在120℃下变为水蒸气
多态实际上是依附于继承的两种含义的:“改变”和“扩展”本身就意味着必须有机制去自动选用你改变/扩展过的版本,故无多态,则两种含义就不可能实现
所以,多态实质上是继承的实现细节,那么让多态与封装、继承者两个概念并列,显然不符合逻辑。
封装
第一层面:类就是麻袋,本身就是一种封装
第二层面:类中定义私有的,旨在类的额内部使用,外部不允许调用
第三层面:明确分区内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装)
约定一:任何以单下划线开头的名字都应该是内部的,私有的
约定二:双下划线开头的名字
python不依赖语言特性去封装数据,而是通过遵循一定的数据属性和函数属性的命名约定来达到封的效果
1 class Room: 2 def __init__(self,name,owner,length,width,height): 3 self.name = name 4 self.owner = owner 5 self.__length = length 6 self.__width = width 7 self.__height = height 8 def tell_area(self): #利用接口来访问封装的内容 9 return self.__length * self.__width 10 r1 = Room("511","Aaron",3,5,2.7) 11 print(r1.tell_area()) 12 # 15
总结:
上面提到有两种不同的编码约定(单下划线和双下划线)来命名私有属性,那么问题来了:到底哪种方式好呢?大多数而言,应该让你的非公共名称以单下划线开头。但是如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏起来,那么菜考虑使用双下划线的方案。无论哪种方案,其实python都没有从根本上限制访问。
面向对象的优点
从编程进化论我们得知,面向对象是一种更高等级的结构化编程方式。他的好处主要是:
1、通过封装明确了内外,调用者只需调用需要使用的对象,而无需知道内部的逻辑。
2、通过继承+多态在语言层面支持了归一化设计
注意:不使用面向对象语言(即不使用class),一样可以做归一化(如泛文件概念、游戏行业的一切皆精灵),一样可以封装(通过定义模块和接口),只是用面向对象语言可以直接用语言元素显式声明这些而已;而使用了面向对象语言,满篇都是class,并不等于就有了归一化的设计。甚至,因为这些花哨的迷惑,反而更加不懂什么才是设计
Python中关于OOP的常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__