面向对象编程
面向对象的程序设计
三大编程范式:
编程进化论:
1、编程最开始就是无组织无结构,从简单控制流中按步写指令(非结构化)
2、从上述指令中提取重复的代码块或逻辑,组织到一起(例如:定义一个函数),这样实现了代码重用,且代码由无结构变为结构化,创建程序的过程变得更具逻辑性
3、定义函数都是独立于函数之外定义变量,然后作为参数传递给函数,这意味着:数据与动作是分离的
4、如果我们把数据和动作整合内嵌到一个结构(函数或类),那么我们就有了一个‘对象系统’(对象就是数据与函数整合到一起的产物)
#面向对象设计,例1:
def dog(name,gender,type): # 狗的动作 def jiao(dog): print('一条狗[%s],汪汪汪' % dog['name']) def chi_shi(dog): print('一条[%s] 正在吃屎' % dog['type']) dog1 = { 'name':name, 'gender': gender, 'type': type, 'jiao':jiao, 'chi_shi':chi_shi, } return dog1 d1=dog('元昊','母','中华田园犬') d2=dog('alex','母','藏敖') print(d1) print(d2) d1['jiao'](d1) d2['chi_shi'](d2) jiao(dog1) chi_shi(dog1) chi_shi(dog2) jiao(person1) #人本来不应该能调用狗方法的 #面向对象设计:类,对象 #将所有整合封装起来,利用函数的局部作用域,使得人不能调用了 def dog(name,gender,type): # 狗的动作 def jiao(dog): print('一条狗[%s],汪汪汪' % dog['name']) def chi_shi(dog): print('一条[%s] 正在吃屎' % dog['type']) def init(name,gender,type): #初始化函数:再用一个函数将狗属性封装起来。使得整个程序清晰明了 dog1 = { 'name':name, 'gender': gender, 'type': type, 'jiao':jiao, 'chi_shi':chi_shi, } return dog1 #dog1当中不能写死,狗的信息应该要允许从外部能够传入 return init(name,gender,type) d1=dog('元昊','母','中华田园犬') d2=dog('alex','母','藏敖') print(d1) #打印出调用dog函数之后的dog1字典中的值(包含d1的特征和d1的动作) #['type':'中华田园犬','name':'元昊','jiao':<function dog.<locals>.jiao at 0x0000000000B101E0>,'gender':'母','chi_shi':<function dog.<locals>.chi_shi at 0x0000000000B10378>] print(d2) # 在整个大的dog函数中利用一个dog字典,将内嵌的函数整合在一起 d1['jiao'](d1) #d1专门直接调用其动作 d2['chi_shi'](d2)
一 面向对象的程序设计的由来
面向对象设计的由来见概述:http://www.cnblogs.com/linhaifeng/articles/6428835.html
二 什么是面向对象的程序设计及为什么要有它
面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。
优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计:将一类具体事物的数据和动作整合到一起,即面向对象设计
核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。
#面向对象设计方法一: #利用一个大函数将对象及其方法特征封装起来实现 ##例2:学校类(与例1是一个原理) def school(name,addr,type): def init(name, addr, type): #只要不执行init,当中的kao_shi,zhao_sheng有没有在此函数之前定义都没有影响 #或者说,只要在调用init执行的时候,init内所有函数在执行语句之前都有定义过(已经加载完)就没问题 sch = { 'name': name, 'addr': addr, 'type': type, 'kao_shi': kao_shi, 'zhao_sheng': zhao_sheng, } return sch def kao_shi(school): print('%s 学校正在考试' %school['name']) def zhao_sheng(school): print('%s %s 正在招生' %(school['type'],school['name'])) return init(name,addr,type) #只需利用返回值来调用init函数,才能触发初始化函数的调用,否则school啥都没做 #对象1: s1=school('oldboy','沙河','私立学校') print(s1) print(s1['name']) #oldboy s1['zhao_sheng'](s1) #一定要传参数(字典s1) #对象2: s2=school('清华','北京','公立学校') print(s2) print(s2['name'],s2['addr'],s2['type']) s2['zhao_sheng'](s2)
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:
1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
面向对象编程:用定义类+实例/对象的方式去实现面向对象的设计
#面向对象设计的第二种方法: #用面向对象编程独有的语法class去实现面向对象设计 #利用class只不过更加方便地实现了面向对象设计,但其实单纯用一个大函数也实现了面向对象设计 class Dog: #类:不必在后面再return一个函数了 def __init__(self,name,gender,type): #最终返回一个字典结构 self.name=name self.gender=gender self.type=type def bark(self): print('一条名字为[%s]的[%s],狂吠不止' %(self.name,self.type)) def yao_ren(self): print('[%s]正在咬人' %(self.name)) def chi_shi(self): print('[%s]正在吃屎' %(self.type)) dog1=Dog('alex','female','京巴') #执行函数 print(dog1.__dict__) #内置特征(看内置字典) # {'name':'alex','type':'京巴','gender':'female'} # dog2=Dog('wupeiqi','female','腊肠') # dog3=Dog('yuanhao','female','藏獒') # # dog1.bark() # dog2.yao_ren() # dog3.chi_shi()
总结:
1、一门面向对象的语言不一定会强制你写面向对象(OO)方面的程序。例如C++可以被认为“更好的C”,而JAva要求万物皆类。
2、在python中,类和面向对象编程OOP都不是日常编程所必须的。
3、用面向对象语言写程序(用class来写),和一个程序是面向对象的(可以不用class来写,用例1和例2中的函数形式也可以写出来),这是两码事。
纯C写的linux kernel事实上比C++/java之类语言写的大多数项目更加面向对象,只不过大部分人都自以为class写的才是正统的面向对象,误以为linx的泛化文件抽象是过程式思维。
linux中一切皆文件:面向对象的设计使得所有linux中生成的对象都具有一样的皆文件的性质。
三 类与对象
1、类即类别、种类,是面向对象设计最重要的概念。类是一种数据结构,好比一个模型,该模型用来表述一类事物(即数据和动作的结合体),用它来产生真实的物体(实例)
2、对象:一切事物都是一个个对象,可以理解为一个具体的事物
3、对象由类产生,类是产生对象的一个模板。对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体
4、实例化:由类产生对象的过程叫做实例化,类实例化的结果就是一个对象,或叫一个实例(对象=实例)
3.1 类相关知识
在python中声明类和声明函数很相似:
声明函数:
def functionName(args): '函数文档字符串' 函数体
声明类
''' class 类名: #规范:类名首字母大写 '类的文档字符串' 类体 ''' #我们创建一个类 class Chinese: '这是一个中国人的类' pass print(Chinese) #<class'__main__.Chinese'> #运行该文件,文件名默认为__main__,类名Chinese #这与函数一样 #实例化出一个对象 p1=Chinese() #类的运行进行实例化,函数的运行执行一段逻辑 print(p1)
经典类和新式类:
1、只有在python2中才区分经典类和新式类,python3中统一为新式类
2、区别在于,新式类必须继承至少一个父类
3、所有类不管是否显式声明父类,都有一个默认继承object父类
#在Python2中: #经典类: class 类名: pass #新式类: class 类名(父类): pass #在python3中这两种都是新式类
类是用来描述一类事物,类的对象指的是这一类事物中的一个个个体。凡是事物就有属性,属性分为:
1、数据属性:就是变量
2、函数属性:就是函数,在面向对象中通常称为方法
注意:类和对象均用点来访问自己的属性。
由于类的定义和函数极其相似,所以可以用函数的作用域来理解类属性调用。
class Chinese: '这是一个中国人的类' dang='党' def sui_di_tu_tan(): #可以不传参数 print('朝着墙上就是一口痰') def cha_dui(self): print('插到了前面') print(Chinese.dang) #党 #本质上就是去自己的属性字典中寻找函数名 Chinese.sui_di_tu_tan() #类名Chinese直接调用函数 Chinese.cha_dui('元昊')
查看类属性
1、dir(类名):查出的是一个名字列表
2、类名.__dict__:查出的是一个字典,key为属性名,value为属性值
# print(dir(Chinese)) #系统内置的(——形式——)加上自定义的函数名 # print(Chinese.__dict__) #查看属性字典(内置的)函数和相应的属性及内存地址 print(Chinese.__dict__['dang']) #党 #等价于Chinese.dang print(Chinese.__dict__['sui_di_tu_tan']) #输出该函数内存地址,因此用这个函数地址直接加括号就能调用了 Chinese.__dict__['sui_di_tu_tan']() #调用函数 Chinese.__dict__['cha_dui'](1) #朝着墙上就是一口痰
#python为类内置的特殊属性
类名.__name__ # 类的名字(字符串)
类名.__doc__ # 类的文档字符串
类名.__base__ # 类的第一个父类(在讲继承时会讲),python中所有类都有一个相同的祖先<class 'object'>
类名.__bases__ # 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__ # 类的字典属性
类名.__module__ # 类定义所在的模块
类名.__class__ # 实例对应的类(仅新式类中)
3.2 对象的相关知识
对象由类实例化而来,类实例化的结果称为一个实例或者称为一个对象
#实例化:实质触发初始化函数的运行 #对比函数式的,只要一触发函数,就能触发初始化函数(return init()) #在类中,只要有定义init函数,就会直接将参数全部传入类的init中 #init函数中不需要加任何的return #对象只包含数据属性结构,没有函数属性 #实例属性能访问到类属性(如果在自己对象属性中没找到,就会去类属性中去找) ##类: class Chinese: '这是一个中国人的类' dang='党' #函数中: # def __init__(name,age,gender): # dic={ # 'name':name, # 'age':age, # 'gender':gender # } # return dic #在class类中 def __init__(self,name,age,gender): #self相当于实例本身,一个字典。 print('我是初始化函数,我开始运行了') self.mingzi=name #p1.mingzi=name self.nianji=age #p1.nianji=age self.xingbie=gender print('我结束啦') #不用写return def sui_di_tu_tan(self): print('%s 朝着墙上就是一口痰' %self.mingzi) def cha_dui(self): print(self) print('%s 插到了前面' %self.mingzi) def eat_food(self,food): print('%s 正在吃%s' %(self.mingzi,food)) #实例的生成 p1=Chinese('元昊',18,'female') #--->__init__(self,name,age,gender) #如果不用class用函数实现时,最外层的chinese函数还需要写需要传入的形参表 #而class中,会把你传入的实参值直接交给内置的_init_函数 # p1=Chinese.__init__(p1,name,age,gender) print(p1.__dict__) #实例也有一个内置的_dict_函数,也能打印出一个函数 # {'xingbie':'female','nianji':18,'mingzi':'元昊'}p1.sui_di_tu_tan() p1.eat_food('苹果') p2=Chinese('武sir',10000,'姑娘') p2.eat_food('韭菜馅饼') print(dir(p2)) #输出属性名 # print(p1.__dict__['xingbie']) print(p1.mingzi) #在dict中去找 # print(p1.mingzi111111111111111) print(p1.dang) #Chinese当中虽然没有,但是init中产生的函数 # 类自己调用 print(Chinese.__dict__) Chinese.sui_di_tu_tan() Chinese.cha_dui(p1) # p1.sui_di_tu_tan() #能够成功找到这个函数,但是还报错。因为python会帮你直接把p1传给函数的第一个参数self print('[------------------->') p1.cha_dui() #p1本身直接传给cha_dui()函数的第一个self参量
查实例属性也可以用:dir和内置的_dict_两种方法
3.3 类属性的增删改查
#类属性又称为静态变量,或者静态属性, #这些数据是与他们所属类对象绑定的,不依赖于任何实例。 #在C++和Java当中,静态数据的变量声明前加static关键字 class Chinese: #首字母大写两个单词分别首字母大写,如:ChinesePeople country='China' def __init__(self,name): self.name=name def play_ball(self,ball): #动词_名词 print('%s 正在打 %s' %(self.name)) #查看 print(Chinese.country) #China #修改 Chinese.country='Japan' print(Chinese.country) #Japan p1=Chinese('alex') print(p1.__dict__) #可以发现p1自己的字典中没有country print(p1.country) #Japan #自己没有,去类中找修改过的country值 #增加 Chinese.dang='党' #本质就是操作字典 # 类和实例都能找到 print(Chinese.dang) #党 print(p1.dang) #党 #删除 del Chinese.dang del Chinese.country print(Chinese.__dict__) #字典中没有dang和country了 # print(Chinese.country) #会报错,因为没有country了 #新增一个函数 # 定义一个新的函数,在跟类一个级别上,在最外面 def eat_food(self,food): print('%s 正在吃%s' %(self.name,food)) # 将此顶级函数传给类当中 Chinese.eat=eat_food print(Chinese.__dict__) #查看到新增的成功了 p1.eat('苹果') #可以成功调用 # 修改类中的函数 def test(self): print('test') Chinese.play_ball=test p1.play_ball() #括号内可以不写,实例调用,默认直接把实例传入 # Chinese.play_ball(p1)类调用不会自动传参数,需要在括号内手动将对象p1传入
3.4 实例属性的增删改查
class Chinese: country='China' def __init__(self,name): self.name=name def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) p1=Chinese('alex') print(p1.__dict__) #{'name':'alex'} #查看 print(p1.name) #访问实例的数据属性 print(p1.play_ball) #访问类的函数属性 # 不是自己的,而是一个bound method绑定在类Chinese上的方法 # 此时不要给play_ball后面加括号传参数,否则就运行了 #增加 p1.age=18 print(p1.__dict__) #增加数据属性 # {'age':'18','name':'alex'} print(p1.age) '''''''''''' # 一般都不会采用如下方法,一般往类当中添加函数方法 #仅在实例中加入函数的话,违背了建立类的初衷,减少代码重用 #实例没有函数属性,但是可以访问类的函数属性 #所以,实例可以增加数据属性,但是不能直接增加类的函数属性 #其实可以给实例用和(类增加函数属性一样方法)增加函数属性 #但运行时,由于是调用实例自己的函数,此时不会自动传入 #只有实例调用类的函数时,才会自动将实例传入函数当中 def test(self): print('我是来自实例的函数属性',self) p1.test=test print(p1.__dict__) #{'age':'18','name':'alex','test':<function test at 0x00000000003D7F28>} p1.test(p1) #我是来自实例的函数属性 p1.test('hcjjd') #可以随意传参数(就是一个普通函数) '''''''''''''''''''''''' #不要用如下方式修改底层的属性字典 # p1.__dict__['sex']='male' # print(p1.__dict__) #能够成功加入,也能成功调用 # print(p1.sex) #修改 p1.age=19 print(p1.__dict__) print(p1.age) #删除 del p1.age print(p1.__dict__)
总结:
3.5 对象与实例调用属性
(1)实例化就是:类名(),然后返回结果为一个对象。加上括号类似与函数运行,函数运行完后有返回值。
(2)函数和类都有作用域的概念。可以把class当做最外层的函数,是一个作用域
# 定义一个类,只把它当做一个作用域去使用 # 此时类似于C语言中的结构体 class MyData: pass x=10 y=20 MyData.x=1 #专门给类加数据属性,相当于局部作用域中加一个,与全局作用域自然地隔开了 MyData.y=2 print(x,y) print(MyData.x,MyData.y) print(MyData.x+MyData.y)
(3)实例化会自动触发init函数的运行,最后返回一个值——实例。因此,我们要找的实例属性就在init函数的局部作用域当中
(4)类有类的属性字典,就是类的作用域,实例有实例的属性字典,即实例的作用域
(5)综上,一个点代表一层作用域,obj.x先从自己的作用域找,没找到再去外层的类的字典当中找,都找不到,就会报错
(6)在类中如果不使用点的调用,代表你在带调用全局变量
#关于作用域的几个例子:
# 例1: class Chinese: country='China' def __init__(self,name): self.name=name def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) p1=Chinese('alex') print(p1.country) #此时p1还没有country,调用的是Chinese类当中的China p1.country='日本' print('类的--->',Chinese.country) #China #p1改的是自己的country,动不了类的 print('实例的',p1.country) #日本 #p1自己的country被改(创建)为日本 # 例2: country='中国' class Chinese: def __init__(self,name): self.name=name def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) p1=Chinese('alex') # print(p1.country) #报错 #点访问只在自己实例中和类中找,不会找到全局的country ''''''''''''''''''' #例3:如下方法很初级,特定函数一定要写特定的功能 #init函数本来的就是用于初始化字典结构的,在里面又加入input,这样缺点: #一是函数代码可读性差,二是输入输出和逻辑耦合到一起了 country='中国' class Chinese: def __init__(self): print('---------->?') name=input('请输入用户名>>:') #在init中可以写input,init就不用传参数了 self.name=name def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) p1=Chinese() print(p1.name) '''''''''''''''''''' # 将例3修改为如下:将输入输出和初始化函数分离开 country='中国' class Chinese: def __init__(self,name): self.name=name def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) def shi_li_hua(): name=input('>>: ') p1=Chinese(name) # print(p1.country) print(p1.name) shi_li_hua() #函数当中利用input来实现动态传参数 #例4: country='中国1' class Chinese: country='中国2' def __init__(self,name): self.name=name print('--->',country) #中国1 #现在不是用点访问,找到的是全局的country def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) print(Chinese.__dict__) #查到的country对应的是’中国2‘ print(Chinese.country) #中国2只能通过点来访问,既可以实例调用,也可以类调用 p1=Chinese('alex') #自动触发init函数,输出的是中国1 print('实例--------》',p1.country) #实例来调用中国2 # Chinese. # p.
class Chinese: country='China' l=['a','b'] def __init__(self,name): self.name=name def play_ball(self,ball): print('%s 正在打 %s' %(self.name,ball)) p1=Chinese('alex') print(p1.l) #p1没有l,则调用类中的 #改法1: p1.l.append('c') # p1依旧没有l,这种方法会给类中的l增加一个 print(p1.__dict__) #['name':'alex'] print(Chinese.l) # ['a','b','c'] # 改法2:要给实例增加数据属性,只能通过赋值表达式实现 # p1.l=[1,2,3] #改p1自己的,p1中有一个新的l # print(Chinese.l) # ['a','b'] # print(p1.__dict__) #['l':[1,2,3],'name':'alex']
四、静态属性、类方法、静态方法
#静态属性(一种数据属性) class Room: tag=1 def __init__(self,name,owner,width,length,heigh): self.name=name self.owner=owner self.width=width self.length=length self.heigh=heigh #@函数装饰器,class提供的属性装饰器(调用在这个里面的函数,不用加小括号了) @property def cal_area(self): # print('%s 住的 %s 总面积是%s' % (self.owner,self.name, self.width * self.length)) return self.width * self.length def test(self): #self和实例是绑定到一块的 print('from test',self.name) # 如果我只想要打印一个类属性,如下方法则必须要求我先传入一个实例,才能看到想要的结果 # def tell_info(self): #self和实例是绑定到一块的 # print('---->',self.tag) #类方法不能调用实例属性,很明显,参数列表中没有self @classmethod #类方法:使类不跟任何实例捆绑到一起,专门供类使用的方法 def tell_info(cls): #cls是python帮你自动补全的参数,cls接收的为一个类名 print(cls) print('--》',cls.tag) #等价于 print('--》',Room.tag) print(r1.cal_area) #定义property之后,用点直接访问,函数后面不写括号了 print(r2.cal_area) # 这样的好处:背后的逻辑可以被隐藏起来 ,看起来就和调用name是一样的 # 即实现了一种封装操作 print(r1.name) print(r2.name) # 不用类方法类方法 print(Room.tag) #类调用自己的数据 # Room.test(1) #相当于1.name,明显不能用一个参数取调用name #报错,test的参量self要求的是传入一个实例 # 因此为了看到修改前的(注释中的tell_info(self)的内容,必须要传入一个实例r1 r1=Room('厕所','alex',100,100,100000) Room.tell_info(r1) #加入类方法之后 Room.tell_info() #直接类名就能调用,自动将类名传入到函数的cls中 @classmethod def tell_info(cls,x): print(cls) print('--》',cls.tag,x) #在类方法中多加入一个形参x,此时调用时,就需要手动加入一个参数了 Room.tell_info(10) #静态方法,类的工具包,不跟类也不跟实例绑定,参数表不含self也不含cls,即不和他俩绑定。 @staticmethod #名义上归属类管理,不能使用类变量和实例变量 def wash_body(a,b,c): print('%s %s %s正在洗澡' %(a,b,c))
#普通的类方法 def test(x,y): print(x,y) Room.wash_body('alex','yuanhao','wupeiqi') #类来调用静态方法 print(Room.__dict__) # 包含wash_body r1=Room('厕所','alex',100,100,100000) #实例也能调用 print(r1.__dict__) #不包含wash_body(实例属性中不含函数属性) # r1.wash_body('alex','yuanhao','wupeiqi') #可以调用 Room.test(1,2) #可以调用,类的函数 # r1.test(1,2) #报错 #不可以调用(因为实例会把自己传进去) # r1=Room('厕所','alex',100,100,100000) # r2=Room('公共厕所','yuanhao',1,1,1) # # print('%s 住的 %s 总面积是%s' %(r1.owner,r1.name,r1.width*r1.length)) # # print('%s 住的 %s 总面积是%s' %(r2.owner,r2.name,r2.width*r2.length)) # # r1.cal_area() # # r2.cal_area()
五、组合
定义一个人的类,人包含头、躯干、手、脚等数据属性。这几个属性又可以是一个类实例化的对象,这就是组合。
用途:1.做类与类之间的关联。没有共同点,但是有关联。
#很low的解决关联的方法:利用append class School: def __init__(self,name,addr): self.name=name self.addr=addr self.course_list=[] def zhao_sheng(self): print('%s 正在招生' %self.name) class Course: def __init__(self,name,price,period): self.name=name self.price=price self.period=period s1=School('oldboy','北京') s2=School('oldboy','南京') s3=School('oldboy','东京') c1=Course('linux',10,'1h') c2=Course('python',10,'1h') s1.course_list.append(c1) s1.course_list.append(c2) print(s1.__dict__) #成功加入了两门课程到列表course_list中 for course_obj in s1.course_list: print(course_obj.name,course_obj.price)
2. 小的组成大的
# 例1:定义一个人的类,人包含头、躯干、手、脚等数据属性。 class Hand: pass class Foot: pass class Trunk: pass class Head: pass class Person: def __init__(self,id_num,name): self.id_num=id_num self.name=name self.hand=Hand() #就是创建对象,等价于类名加括号 self.foot=Foot() self.trunk=Trunk() self.head=Head() p1=Person('111111','alex') # print(p1.__dict__) # 例2:将一个类的实例传入另外一个实例 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('oldboy','北京') s2=School('oldboy','南京') s3=School('oldboy','东京') # c1=Course('linux',10,'1h','oldboy 北京') #这么写与s1没关系 c1=Course('linux',10,'1h',s1) #把school创建的实例传入,就是传对象进来 print(c1.__dict__) #c1的school参量指向s1内存地址 #{'period':'1h','price':10,'school':<_main_.School object at ox0000000000B2C208>,'name':'linux'} print(c1.school.name) #两个点来访问 print(s1) #等价于print(c1.school) # 例3:选课系统 # 为了让用户有选择,手动输入参数值 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('oldboy','北京') s2=School('oldboy','南京') s3=School('oldboy','东京') # c1=Course('linux',10,'1h','oldboy 北京') # c1=Course('linux',10,'1h',s1) msg=''' 1 老男孩 北京校区 2 老男孩 南京校区 3 老男孩 东京校区 ''' while True: print(msg) menu={ '1':s1, '2':s2, '3':s3 } choice=input('选择学校>>: ') school_obj=menu[choice] name=input('课程名>>: ') price=input('课程费用>>: ') period=input('课程周期>>: ') new_course=Course(name,price,period,school_obj) print('课程【%s】属于【%s】学校' %(new_course.name,new_course.school.name))
那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看
在现实世界中:先有对象,再有类
世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念
也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在
在程序中:务必保证先定义类,后产生对象
这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类
不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象
按照上述步骤,我们来定义一个类(我们站在老男孩学校的角度去看,在座的各位都是学生)
#在现实世界中,站在老男孩学校的角度:先有对象,再有类 对象1:李坦克 特征: 学校=oldboy 姓名=李坦克 性别=男 年龄=18 技能: 学习 吃饭 睡觉 对象2:王大炮 特征: 学校=oldboy 姓名=王大炮 性别=女 年龄=38 技能: 学习 吃饭 睡觉 对象3:牛榴弹 特征: 学校=oldboy 姓名=牛榴弹 性别=男 年龄=78 技能: 学习 吃饭 睡觉 现实中的老男孩学生类 相似的特征: 学校=oldboy 相似的技能: 学习 吃饭 睡觉 在现实世界中:先有对象,再有类
#在程序中,务必保证:先定义(类),后使用(产生对象) PS: 1. 在程序中特征用变量标识,技能用函数标识 2. 因而类中最常见的无非是:变量和函数的定义 #程序中的类 class OldboyStudent: school='oldboy' def learn(self): print('is learning') def eat(self): print('is eating') def sleep(self): print('is sleeping') #注意: 1.类中可以有任意python代码,这些代码在类定义阶段便会执行 2.因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过OldboyStudent.__dict__查看 3.对于经典类来说我们可以通过该字典操作类名称空间的名字(新式类有限制),但python为我们提供专门的.语法 4.点是访问属性的语法,类中定义的名字,都是类的属性 #程序中类的用法 .:专门用来访问属性,本质操作的就是__dict__ OldboyStudent.school #等于经典类的操作OldboyStudent.__dict__['school'] OldboyStudent.school='Oldboy' #等于经典类的操作OldboyStudent.__dict__['school']='Oldboy' OldboyStudent.x=1 #等于经典类的操作OldboyStudent.__dict__['x']=1 del OldboyStudent.x #等于经典类的操作OldboyStudent.__dict__.pop('x') #程序中的对象 #调用类,或称为实例化,得到对象 s1=OldboyStudent() s2=OldboyStudent() s3=OldboyStudent() #如此,s1、s2、s3都一样了,而这三者除了相似的属性之外还各种不同的属性,这就用到了__init__ #注意:该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值 class OldboyStudent: ...... def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex ...... s1=OldboyStudent('李坦克','男',18) #先调用类产生空对象s1,然后调用OldboyStudent.__init__(s1,'李坦克','男',18) s2=OldboyStudent('王大炮','女',38) s3=OldboyStudent('牛榴弹','男',78) #程序中对象的用法 #执行__init__,s1.name='牛榴弹',很明显也会产生对象的名称空间 s2.__dict__ {'name': '王大炮', 'age': '女', 'sex': 38} s2.name #s2.__dict__['name'] s2.name='王三炮' #s2.__dict__['name']='王三炮' s2.course='python' #s2.__dict__['course']='python' del s2.course #s2.__dict__.pop('course') 在程序中:先定义类,后产生对象
#方式一、为对象初始化自己独有的特征 class People: country='China' x=1 def run(self): print('----->', self) # 实例化出三个空对象 obj1=People() obj2=People() obj3=People() # 为对象定制自己独有的特征 obj1.name='egon' obj1.age=18 obj1.sex='male' obj2.name='lxx' obj2.age=38 obj2.sex='female' obj3.name='alex' obj3.age=38 obj3.sex='female' # print(obj1.__dict__) # print(obj2.__dict__) # print(obj3.__dict__) # print(People.__dict__) #方式二、为对象初始化自己独有的特征 class People: country='China' x=1 def run(self): print('----->', self) # 实例化出三个空对象 obj1=People() obj2=People() obj3=People() # 为对象定制自己独有的特征 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z chu_shi_hua(obj1,'egon',18,'male') chu_shi_hua(obj2,'lxx',38,'female') chu_shi_hua(obj3,'alex',38,'female') #方式三、为对象初始化自己独有的特征 class People: country='China' x=1 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z def run(self): print('----->', self) obj1=People() # print(People.chu_shi_hua) People.chu_shi_hua(obj1,'egon',18,'male') obj2=People() People.chu_shi_hua(obj2,'lxx',38,'female') obj3=People() People.chu_shi_hua(obj3,'alex',38,'female') # 方式四、为对象初始化自己独有的特征 class People: country='China' x=1 def __init__(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' obj.name = x obj.age = y obj.sex = z def run(self): print('----->', self) obj1=People('egon',18,'male') #People.__init__(obj1,'egon',18,'male') obj2=People('lxx',38,'female') #People.__init__(obj2,'lxx',38,'female') obj3=People('alex',38,'female') #People.__init__(obj3,'alex',38,'female') # __init__方法 # 强调: # 1、该方法内可以有任意的python代码 # 2、一定不能有返回值 class People: country='China' x=1 def __init__(obj, name, age, sex): #obj=obj1,x='egon',y=18,z='male' # if type(name) is not str: # raise TypeError('名字必须是字符串类型') obj.name = name obj.age = age obj.sex = sex def run(self): print('----->', self) # obj1=People('egon',18,'male') obj1=People(3537,18,'male') # print(obj1.run) # obj1.run() #People.run(obj1) # print(People.run) !!!__init__方法之为对象定制自己独有的特征
PS:
1. 站的角度不同,定义出的类是截然不同的,详见面向对象实战之需求分析
2. 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类......
3. 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类
!!!补充说明:从代码级别看面向对象 !!!
四 属性查找
类有两种属性:数据属性和函数属性
1. 类的数据属性是所有对象共享的
2. 类的函数属性是绑定给对象用的
类有数据属性和函数属性,实例对象由类产生,但实例只有数据属性,没有函数属性。
实例化的过程实际就是执行_init_的过程,这个函数内部只是为实例本身,即self设定了一堆数据(变量),所以实例只有数据属性。
实例是类产生的,调用的方法都去类当中去调用,可以省内存。宏观来看,实例其实就绑定了类的数据和函数属性。
但实例不一定能访问到类属性。(作用域的原因)
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
练习:编写一个学生类,产生一堆学生对象,要求有一个计数器(属性),统计总共实例了多少个对象
五 绑定到对象的方法的特殊之处
#改写 class OldboyStudent: school='oldboy' def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def learn(self): print('%s is learning' %self.name) #新增self.name def eat(self): print('%s is eating' %self.name) def sleep(self): print('%s is sleeping' %self.name) s1=OldboyStudent('李坦克','男',18) s2=OldboyStudent('王大炮','女',38) s3=OldboyStudent('牛榴弹','男',78)
类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
OldboyStudent.learn(s1) #李坦克 is learning OldboyStudent.learn(s2) #王大炮 is learning OldboyStudent.learn(s3) #牛榴弹 is learning
类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法
强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
s1.learn() #等同于OldboyStudent.learn(s1) s2.learn() #等同于OldboyStudent.learn(s2) s3.learn() #等同于OldboyStudent.learn(s3)
注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
类即类型
提示:python的class术语与c++有一定区别,与 Modula-3更像。
python中一切皆为对象,且python3中类与类型是一个概念,类型就是类
#类型dict就是类dict >>> list <class 'list'> #实例化的到3个对象l1,l2,l3 >>> l1=list() >>> l2=list() >>> l3=list() #三个对象都有绑定方法append,是相同的功能,但内存地址不同 >>> l1.append <built-in method append of list object at 0x10b482b48> >>> l2.append <built-in method append of list object at 0x10b482b88> >>> l3.append <built-in method append of list object at 0x10b482bc8> #操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3 >>> l1.append(3) >>> l1 [3] >>> l2 [] >>> l3 [] #调用类list.append(l3,111)等同于l3.append(111) >>> list.append(l3,111) #l3.append(111) >>> l3 [111]
六 对象之间的交互
class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄; camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia; def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...; self.nickname=nickname #为自己的盖伦起个别名; self.aggressivity=aggressivity #英雄都有自己的攻击力; self.life_value=life_value #英雄都有自己的生命值; def attack(self,enemy): #普通攻击技能,enemy是敌人; enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
我们可以仿照garen类再创建一个Riven类
class Riven: camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus; def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54; self.nickname=nickname #为自己的锐雯起个别名; self.aggressivity=aggressivity #英雄都有自己的攻击力; self.life_value=life_value #英雄都有自己的生命值; def attack(self,enemy): #普通攻击技能,enemy是敌人; enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
实例出俩英雄
>>> g1=Garen('草丛伦') >>> r1=Riven('锐雯雯')
交互:锐雯雯攻击草丛伦,反之一样
>>> g1.life_value 455 >>> r1.attack(g1) >>> g1.life_value 401
补充:
garen_hero.Q()称为向garen_hero这个对象发送了一条消息,让他去执行Q这个功能,类似的有:
garen_hero.W()
garen_hero.E()
garen_hero.R()
七 练习
链接:http://www.cnblogs.com/linhaifeng/articles/7340497.html
八 继承与派生
链接:http://www.cnblogs.com/linhaifeng/articles/7340153.html
九 多态与多态性
链接:http://www.cnblogs.com/linhaifeng/articles/7340687.html
十 封装
链接:http://www.cnblogs.com/linhaifeng/articles/7340801.html
十二 小白容易犯的错误
1.面向对象的程序设计看起来高大上,所以我在编程时就应该保证通篇class,这样写出的程序一定是好的程序(面向对象只适合那些可扩展性要求比较高的场景)
2.很多人喜欢说面向对象三大特性(这是从哪传出来的,封装,多态,继承?漏洞太多太多,好吧暂且称为三大特性),那么我在基于面向对象编程时,我一定要让我定义的类中完整的包含这三种特性,这样写肯定是好的程序
好家伙,我说降龙十八掌有十八掌,那么你每次跟人干仗都要从第一掌打到第18掌这才显得你会了是么:面对敌人,你打到第三掌对方就已经倒下了,你说,不行,你给老子起来,老子还没有show完...
3.类有类属性,实例有实例属性,所以我们在定义class时一定要定义出那么几个类属性,想不到怎么办,那就使劲的想,定义的越多越牛逼
这就犯了一个严重的错误,程序越早面向对象,死的越早,为啥面向对象,因为我们要将数据与功能结合到一起,程序整体的结构都没有出来,或者说需要考虑的问题你都没有搞清楚个八九不离十,你就开始面向对象了,这就导致了,你在那里干想,自以为想通了,定义了一堆属性,结果后来又都用不到,或者想不通到底应该定义啥,那就一直想吧,想着想着就疯了。
你见过哪家公司要开发一个软件,上来就开始写,肯定是频繁的开会讨论计划,请看第八节
4.既然这么麻烦,那么我彻底解脱了,我们不要用面向对象编程了,你啊,你有大才,你能成事啊,傻叉。
十三 python中关于OOP的常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。
对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。
注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”
真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明
(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)
合成
合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。
泛化/特化
基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。
多态与多态性
多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气
多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。
冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样
自省/反射
自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像__dict__,__name__及__doc__
十四 面向对象的软件开发
很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。
所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程
面向对象的软件工程包括下面几个部:
1.面向对象分析(object oriented analysis ,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。
十五 面向对象实战
链接:http://www.cnblogs.com/linhaifeng/articles/7341318.html
本周作业
角色:学校、学员、课程、讲师
要求:
1. 创建北京、上海 2 所学校
2. 创建linux , python , go 3个课程 , linux\py 在北京开, go 在上海开
3. 课程包含,周期,价格,通过学校创建课程
4. 通过学校创建班级, 班级关联课程、讲师
5. 创建学员时,选择学校,关联班级
5. 创建讲师角色时要关联学校,
6. 提供两个角色接口
6.1 学员视图, 可以注册, 交学费, 选择班级,
6.2 讲师视图, 讲师可管理自己的班级, 上课时选择班级, 查看班级学员列表 , 修改所管理的学员的成绩
6.3 管理视图,创建讲师, 创建班级,创建课程
7. 上面的操作产生的数据都通过pickle序列化保存到文件里
posted on 2018-04-13 11:03 Josie_chen 阅读(379) 评论(0) 编辑 收藏 举报