@(python)[笔记]
目录
目录
前言
一、类与对象
1.1 什么是对象,什么是类
1.2 类有两种作用
1.2.1属性引用
1.2.2 实例化
1.2.3 查看类的属性
1.3对象的作用
1.4 对象之间的交互
1.5类名称空间与对象名称空间
二、继承与派生
2.1 什么是继承
2.2 继承与抽象
2.3 继承与重用性
2.4 组合与重用性
2.4.1 继承的方式
2.4.2 组合的方式
2.5 接口与归一化设计
2.5.1什么是接口
2.5.2 为何要用接口
2.6 抽象类
2.6.1 什么是抽象类
2.6.2 为什么要有抽象类
2.6.3 在python中实现抽象类
2.7 继承实现原理
2.7.1 继承顺序
2.7.2 继承原理
2.8 子类中调用父类的方法
2.8.1 不用super()引发的惨案
三、多态与多态性
3.1 多态
3.2 多态性
3.3 多态性的好处
四、封装
4.1 为什么要封装
4.2 如何实现封装
4.3 特性(property)
4.3.1 什么是特性property
4.3.2 为什么要用property
五、绑定方法与非绑定方法
5.1 @staticmethod
5.2 @classmethod
5.3 比较@staticmethod和@classmethod的区别
六、 面向对象的软件开发
七、python中关于OOP的常用术语
前言
在python中,一切皆对象
面向对象的程序设计的核心就是对象;
面向对象的程序设计的优缺点:
- 优点:扩展性好;对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易;
- 缺点:可控性差,无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一量开始就由对象之间的交互解决问题,即使是上帝也无法预测最张结果。
应用场景:
需求经常变化的软件,一般需求的变化都集中在用户层,如互联网应用、企业内部软件、游戏等都是面向对象的程序设计大显身手的好地方。
对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性:
一、类与对象
1.1 什么是对象,什么是类
python中一切皆对象,且python3中统一了类与类型的概念,即类型就是类。
类是所有对象都具有的特征和技能和结合体;
在python中,用变量表示特征,用函数表示技能,因而类是变量与函数的结合体,对象是变量与方法(指向类的函数)的结合体。
类的语法定义:
class 类名:
'''类的文档描述'''
类体
类是数据与函数的结合,二者称为类的 属性
class Chinese: #定义一个类,类名叫Chinese,可以用它实例化出一个中国人;
country = 'China' #所有中国人的国籍都是China
def talk(self): #所有中国人都具有的技能说汉语
print("speak chinese")
#通过Chinese类实例化出两个对象
alex = Chinese()
lisi = Chinese()
print(type(alex)) #<class '__main__.Chinese'>
print(alex.country) #China
print(lisi.country) #China
alex.talk() #speak chinese
lisi.talk() #speak chinese
通过同一个类实例化出来的对象拥有相同的属性,如上面的例子,通过Chinese类实例化出的两个人alex和lisi都拥有相同的国籍China和相同的技能说汉语
1.2 类有两种作用
- 属性引用
- 实例化
1.2.1属性引用
语法:类名.属性
class Chinese: #定义一个类,类名叫Chinese,可以用它实例化出一个中国人;
country = 'China' #所有中国人的国籍都是China
def talk(self): #所有中国人都具有的技能说汉语
print("speak chinese")
#通过Chinese类实例化出两个对象
alex = Chinese()
lisi = Chinese()
print(type(alex)) #<class '__main__.Chinese'>
print(alex.country) #China,引用类的数据属性,该属性与所有对象(实例)共享
print(lisi.country) #China
alex.talk() #speak chinese ,引用类的函数属性,也是共享
lisi.talk() #speak chinese
Chinese.writing = 'Chinese character' #增加属性
del Chinese.writing #删除属性
1.2.2 实例化
__init__
与self
类名加括号就是实例化,会自动触发内部的__init__
函数的运行,可以用它来为每个实例定制自己独有的特征
比如每个中国人的名字、年龄、性别是不一样的,就可以这样来写:
class Chinese:
country = 'China'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
#如实例化的对象是alex,则这里self.name就相当于alex.name
def talk(self):
print("%s can speak chinese, age: %s, sex: %s"%(self.name,self.age,self.sex))
#通过Chinese类实例化出两个对象
alex = Chinese('alex',20,'male')
#实际上就是在执行Chinese.__init__(alex,'alex',20,'male')
lisi = Chinese('lisi',26,'female')
alex.talk() #alex can speak chinese, age: 20, sex: male
lisi.talk() #lisi can speak chinese, age: 26, sex: female
self的作用是在实例化时自动将对象(也叫实例)本身传给
__init__
函数的第一个参数。
1.2.3 查看类的属性
print(Chinese.__dict__) #查看类的属性,字典形式
print(alex.__dict__) #查看对象的属性,不包括共有的属性
#特殊的类属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
1.3对象的作用
对象是关于类而实际存在的一个例子,即实例
print(type(alex)) #<class '__main__.Chinese'>
print(isinstance(alex,Chinese)) #判断alex是否是Chinese的一个实例
对象只有一种作用:属性引用
print(alex.country) #China
print(alex.name) #alex
print(alex.age) #20
'''
查看实例属性
同样是dir和内置__dict__两种方式
特殊实例属性
__class__
__dict__
....
'''
对象本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不会一样
print(alex.talk) #对象的绑定方法talk本质就是调用类的函数talk的功能,二者是一种绑定关系
print(lisi.talk)
#输出:
<bound method Chinese.talk of <__main__.Chinese object at 0x00000000006F19B0>>
<bound method Chinese.talk of <__main__.Chinese object at 0x00000000006F19E8>>
#从输出结果可以看出,是绑定方法,并且两个内存地址不一样
注:对象的绑定方法的特别之外在于:obj,func()会把obj付给func的第一个参数。
1.4 对象之间的交互
例如:模拟英雄联盟里的角色盖伦(Garen)和 锐雯(Riven)之间相互攻击对方。
#模拟英雄联盟里的角色盖伦(Garen)和 锐雯(Riven)之间相互攻击对方
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 #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
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 #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
#实例化出一个Garen角色,叫egon
egon = Garen("亮亮")
#实例化出一个Riven角色,叫alex
alex = Riven("飞飞")
#分别查看egon和alex的原始生命值
print(egon.life_value) #455
print(alex.life_value) #414
#egon用自己的技能攻击一次alex
egon.attack(alex) #发送了一条消息,称为向egon发送了attack指令
#查看alex的剩余生命值
print(alex.life_value) #356 = 414(alex生命值) - 58(egon攻击力)
#alex用自己的技能攻击egon一次
alex.attack(egon)
#查看egon的剩余生命值
print(egon.life_value) #401 = 455(egon生命值) - 54(alex攻击力)
1.5类名称空间与对象名称空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性。
而类有两属性:数据属性和函数属性
其中类的数据属性是共享给所有对象的
print(id(egon.camp))
#本质就是在引用类的camp属性,二者id一样
print(id(Garen.camp))
'''
#输出:
7542688
7542688
'''
而类的函数属性是绑定到所有对象的:
print(id(egon.attack))
print(id(Garen.attack))
#输出:
4897608
7332456
#id并不一样
'''
egon.attack就是在执行Garen.attack的功能,python的class机制会将Garen的函数属性attack绑定给egon,egon相当于拿到了一个指针,指向Garen类的attack功能;
险些之外egon.attack()还会将egon传给attack的第一个参数self
'''
- 创建一个对象就会创建一个对象的名称空间,存放对象的名字,称为对象的属性;
- 在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类。。。最后都找不到就抛出异常。
练习:编写一个学生类,产生一堆学生对象,要求有一个计数器(属性),统计总共实例化了多少个对象
class stu:
count = 0
def __init__(self,name,age,zy,sex='male'):
stu.count +=1 #类的count
#count += 1 # 全局的count
#self.count +=1 # 对象自己的count
self.name = name
self.age = age
self.zhuanyei = zy
self.sex = sex
def study(self):
print("%s正在学习%s专业课程"%(self.name,self.zhuanyei))
p1 = stu('alex',28,'python')
p2 = stu('egon',25,'java')
print(stu.count) #输出2
print(p1.count) #输出2
二、继承与派生
2.1 什么是继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可以称为基类或超类,新建的类称为派生类或子类。
在开发过程中,如果定义了一个类A,此后又想新建一个类B,但是类B的大部分内容与类A的相同时,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会遗传到A的所有属性(数据属性和函数属性),实现代码重用,减少代码量。
python中类的继承分为:
- 单继承:子类只继承一个父类
- 多继承:子类继承多个父类
class ParentClass1: #定义父类1
pass
class ParentClass2: #定义父类2
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass1
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass
查看子类继承了哪些父类
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个父类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果没有指定基类(或叫父类),python的类会默认继承object
类,object
是所有python类的基类,它提供了一些常见方法(如__str__
)的实现。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
2.2 继承与抽象
先抽象再继承
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
- 1.将奥巴马和梅西这两个对象比较相似的部分抽取成类;
- 2.将人、猪、狗这三个类比较相似的部分抽取成类;
抽象最主要的作用是划分类别,这样可以隔离关注点,降低复杂度。
继承是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程一个动作或者说一个技巧,通过抽象可以得到类。
2.3 继承与重用性
使用继承来重用代码比较好的例子
#==========================第一部分
例如:
猫可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:
#猫和狗有大量相同的内容
class 猫:
def 喵喵叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
class 狗:
def 汪汪叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
#==========================第二部分
上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:
动物:吃、喝、拉、撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
伪代码如下:
class 动物:
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 猫(动物):
def 喵喵叫(self):
print '喵喵叫'
# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类
class 狗(动物):
def 汪汪叫(self):
print '喵喵叫'
#==========================第三部分
#继承的代码实现
class Animal:
def eat(self):
print("%s 吃 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
def shit(self):
print ("%s 拉 " %self.name)
def pee(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('汪汪叫')
# ######### 执行 #########
c1 = Cat('小白家的小黑猫')
c1.eat()
c2 = Cat('小黑的小白猫')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
我们可以将《1.4 对象之间的交互》示例的代码通过继承的方式,修改成如下这样:
class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
def move_forward(self):
print('%s move forward' %self.nickname)
def move_backward(self):
print('%s move backward' %self.nickname)
def move_left(self):
print('%s move forward' %self.nickname)
def move_right(self):
print('%s move forward' %self.nickname)
def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass
class Riven(Hero):
pass
g1=Garen('草丛伦',100,300)
r1=Riven('锐雯雯',57,200)
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)
'''
运行结果
243
'''
注意:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Riven(Hero):
camp='Noxus'
def attack(self,enemy): #在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
print('from riven')
def fly(self): #在自己这里定义新的
print('%s is flying' %self.nickname)
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
class Riven(Hero):
camp = 'Noxus'
def __init__(self,nickname,aggressivity,life_value,skin):
Hero.__init__(self,nickname,aggressivity,life_value) #调用父类的属性
self.skin = skin #添加自己的新属性
def attack(self,enemy): #在自己这里定义新attack,不再使用父类的attack,且不会影响父类
Hero.attack(self,enemy) #调用父类的attack功能
def fly(self): #在自己这里定义新功能
print('%s is flying' %self.nickname)
r1 = Riven('锐雯雯',57,200,'黄皮肤')
r1.fly()
print(r1.skin)
'''
输出:
锐雯雯 is flying
黄皮肤
''
2.4 组合与重用性
软件征用的重要方式除了继承之外还有另外一种方式,即:组合
组合是指在一个类中以另外一个类的对象作为数据属性,称为类的组全
组合示例:
class Equip: #武器装备类
def fire(self):
print('Release Fire skill')
class Guido: #英雄Guido的类,一个英雄需要有装备,因而需要组合Equip类
camp = 'Malaysia'
def __init__(self,nickname):
self.nickname = nickname
self.equip = Equip() #用Equip类产生一个装备,赋值给实例的equip属性
r1 = Guido('马利亚')
r1.equip.fire() #可以使用组合的类产生的对象所持有的方法
'''
输出:
Release Fire skill
'''
组合与继承都是有效地利用已有类的资源的重要方式,但是二者的概念和使用场景不同.
2.4.1 继承的方式
通过继承建立了派生类(子类)与基类(父类)之间的关系,它是一种 “是” 的关系,比如狗是动物,猫是动物;
当类之间有很多相同的功能时,提取这些共同的功能做成基类(父类),用继承比较好。比如:教授是老师
class Teacher:
def __init__(self,name,age):
self.name = name
self.age = age
def teach(self):
print("teaching")
class Professor(Teacher):
pass
p1 = Professor('egon',28)
p1.teach('python')
'''
输出:
teaching
'''
2.4.2 组合的方式
用组合的方式建立了“类”与“组合的类”之间的关系,它是一种"有"的关系,比如教授有生日,教授教python课程
class BirthDate:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
class Course:
def __init__(self,name,price,period):
self.name = name
self.price = price
self.period = period
class Teacher:
def __init__(self,name,age):
self.name = name
self.age = age
def teach(self,course):
print('teaching')
class Professor(Teacher):
def __init__(self,name,age,birth,course):
Teacher.__init__(self,name,age)
self.birth = birth
self.course = course
p1 = Professor('egon',28,
BirthDate('1960','6','9'),
Course("python",'20000','7 months'))
#将BirthDate('1960','6','9')传给birth
#将Course("python",'20000','7 months')传给course
print(p1.birth.year,p1.birth.month,p1.birth.day)
print(p1.course.name,p1.course.price,p1.course.period)
'''
输出:
1960 6 9
python 20000 7 months
'''
总结:当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。
2.5 接口与归一化设计
2.5.1什么是接口
- 接口是组功能的集合
- 接口的功能用于交互,所有的功能都是公共的(即:别的对象可操控)
- 接口只定义函数,但不涉及函数的实现
继承的两种用途:
- 继承基类(父类)的方法,并且做出自己的改变或扩展(代码重用);
- 声明某个子类兼容某个父类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类接口类,并且实现接口中的功能;
class Interface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。
def read(self): #定接口函数read
pass
def write(self): #定义接口函数write
pass
class Txt(Interface): #文本,具体实现read和write
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(Interface): #磁盘,具体实现read和write
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(Interface):
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
实践中,继承的第一种含义意义并不是很大,甚至常常是有害的,因为它会使得子类与父类出现强耦合。
继承的第二种含义非常重要,它又叫接口继承。
接口继承实质上是要求做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象 ------这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,就像Linux中一切皆文件的理念一样,所有东西都可以当成文件来处理, 不必关心它是内存、磁盘、网络、还是屏幕(当然,对底层设计者,也可以区分出字符设备和块设备)。
2.5.2 为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数。
这么做的意义在于归一化。归一化就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化让使用者无需关心对象的类是什么,只需要知道这此对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
2.6 抽象类
2.6.1 什么是抽象类
与JAVA一样,python也有抽象类的概念,但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。
2.6.2 为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
2.6.3 在python中实现抽象类
在python中实现抽象类需要利用abc模块
#一切皆文件
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):
# pass
#
# t1=Txt() #报错,子类没有定义抽象方法
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)
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性;
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
2.7 继承实现原理
2.7.1 继承顺序
- Python的类可以继承多个类,而Java和C#中则只能继承一个类;
- Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先 和 广度优先
- 深度优先:如上图,A类继承B类和C类,B类又继承D类,那么查找顺序是:A -> B -> D -> C
- 广度优先: 如上图,A类继承B类和C类,B类又继承D类,那么查找顺序是:A -> B -> C -> D
- 当类是经典类时,多继承情况下,会按照深度优先方式查找
- 当类是新式类时,多继承情况下,会按照广度优先方式查找
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了更多的功能,也是之后推荐的写法,从写法上区分的话,如果当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。
注意:只有Python2中才有新式类和经典类之分,Python3中统一都是新式类。
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
#python3中统一都是新式类
#pyhon2中才分新式类与经典类
2.7.2 继承原理
python到底是如何实现继承的?
对于你定义的每一个类,python都会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有蕨类的线性顺序列表,例如:
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法实现的,不用去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循以下三条准则:
- 子类会先于父类被检查;
- 多个父类会根据它们在列表中的顺序被检查;
- 如果对下一个类存在两个合法的选择,则选择第一个父类。
2.8 子类中调用父类的方法
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法。
方法一: 父类名.父类方法()
class School: #定义一个学校类
school_name = "oldboy"
def __init__(self,course,price,):
self.course = course
self.price = price
def foo(self):
print("开学啦。。。")
class Students(School):
def __init__(self,course,price,name,age):
School.__init__(self,course,price)
#这里调用父类的__init__方法时,必须把self传进去
self.name = name
self.age = age
def foo(self):
print("{_name}报了{_school}学校的{_course}专业".format(
_name = self.name,
_school = self.school_name,
_course = self.course
))
School.foo(self) #调用父类的方法,必须传入self。否则报错
stu_1 = Students("Python","11000","egon","18")
stu_1.foo()
'''
输出:
egon报了oldboy学校的Python专业
开学啦。。。
'''
方法二:利用super()
函数
class School: #定义一个学校类
school_name = "oldboy"
def __init__(self,course,price):
self.course = course
self.price = price
def foo(self):
print("开学啦。。。")
class Students(School): #定义一个学生类,并继承学校类
def __init__(self,course,price,name,age):
super().__init__(course,price)
#利用super()函数,调用父类的__init__方法,就不需要再将self传入了;
#在python3中super()就等同于python2中的super(Students,self),
#super(Students,self)就相当于实例本身
self.name = name
self.age = age
def foo(self):
print("{_name}报了{_school}学校的{_course}专业".format(
_name = self.name,
_school = self.school_name,
_course = self.course
))
super().foo() #利用super()函数调用父类的foo()方法,也无需再传入self参数了
stu_1 = Students("Python","11000","egon","18")
stu_1.foo()
'''
输出:
egon报了oldboy学校的Python专业
开学啦。。。
'''
2.8.1 不用super()引发的惨案
不用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()
print(D.__mro__) #python2中没有这个属性
'''
输出:
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
'''
可以看出以上查找顺序为: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()
print(D.__mro__) #python2中没有这个属性
'''
输出结果:
D的构造方法
B的构造方法
C的构造方法
A的构造方法
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
''
总结:只要重新定义的方法统一使用
super()
并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次;
注意:使用super()
调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表。
三、多态与多态性
3.1 多态
多态指的是一类事物有多种形态。一个抽象类有多个子类,因而多态的概念依赖于继承。
例如:
1、 序列类型有多种形态:字符串、列表、元组
2、 动物有多种形态:猫、狗、猪
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod #定义抽象类
def talk(self):
pass
class Cat(Animal): #动物的形态之一:人
def talk(self):
print('say 喵喵喵')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say 汪汪汪')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say 哼哼哼')
3、 文件有多种形状:文本文件、可执行文件
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')
3.2 多态性
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同功能的函数。
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息,这里所谓的消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同。
如下示例:
class animal:
def talk(self):
print("正在叫")
class people(animal):
def talk(self):
print("say hello")
class pig(animal):
def talk(self):
print("哼哼哼")
class dog(animal):
def talk(self):
print("汪汪汪")
peo = people()
pig1 = pig()
dog1 = dog()
def func(obj): #参数obj就是多态性的具体表现形式
obj.talk()
func(peo) #say hello
func(pig1) #哼哼哼
func(dog1) #汪汪汪
#每种动物都有talk功能,而每种动物实例执行的talk功能,得到的结果并不相同
综上,也可以说,多态性是“一个接口(即上例中的func()
函数),多种实现(如obj.talk()
)”
3.3 多态性的好处
增加了程序的灵活性
以不变应万变,不论对象怎么变化,使用者都是用同一种形式去调用,如func(peo)
,func(pig1)
,func(dog1)
。增加了程序的可扩展性
通过继承animal
类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
>>> class Cat(Animal): #属于动物的另外一种形态:猫
... def talk(self):
... print('say miao')
...
>>> def func(animal): #对于使用者来说,自己的代码根本无需改动
... animal.talk()
...
>>> cat1=Cat() #实例出一只猫
>>> func(cat1) #甚至连调用方式也无需改变,就能调用猫的talk功能
say miao
'''
这样我们新增了一个形态Cat,由Cat类产生的实例cat1,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)
'''
四、封装
- 数据封装
- 方法封装
4.1 为什么要封装
- 封装数据的主要原因是:保护隐私
- 封装方法的主要原因是:隔离复杂度
4.2 如何实现封装
在类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用,外部无法直接访问这些隐藏的属性,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__name
都会自动变形成:_类名__name
这样的形式。
注意:类名前面是一个下划线,name前面是双下划线。
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的形式访问到.
print(A.__dict__)
#输出:
'_A__foo': <function A.__foo at 0x000000000118E268>, '__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', '__init__': <function A.__init__ at 0x000000000118E1E0>, 'bar': <function A.bar at 0x000000000118E2F0>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '_A__N': 0, '__doc__': None}
如下示例,隐藏name和age,让外部不能直接访问name和age,只能通过tell_info()
和set_info()
这两个接口来访问,set_into()
附加类型检查逻辑
class Teacher:
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print('姓名:%s,年龄:%s' %(self.__name,self.__age))
def set_info(self,name,age):
if not isinstance(name,str):
raise TypeError('姓名必须是字符串类型')
if not isinstance(age,int):
raise TypeError('年龄必须是整型')
self.__name=name
self.__age=age
t=Teacher('egon',18)
t.tell_info()
t.set_info('egon',19)
t.tell_info()
这种自动变形的特点:
- 类中定义的
__x
只能在类的内部使用,如self.__x
,引用的就是变形的结果。 - 这种变形其实只是针对外部的变形,在外部是无法通过
__x
这个名字访问到的。 - 在子类定义的
__x
不会覆盖在父类定义的__x
,因为子类中的变形成了:_子类名__x
,而父类中变形成了:_父类名__x
,所以双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注意:对于这种封装,我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了。也可以用
property
来解决,下面即将介绍。
这种变形需要注意的问题:
1、 这种机制也并没有真正意义上限制我们从外部直接访问隐藏性,知道了类名和属性名就可以拼出名字:_类名__属性
,然后就可以通过拼出的名字直接访问了,如a._A__N
class A:
__N = 10
def foo(self):
pass
a=A()
print(a._A__N)
#输出:10
2、 变形的过程只在类的定义时发生一次,在定义后的赋值操作不会再变形
a.__M = 1
print(a.__M)
#输出:1
3、 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的(隐藏的)。
#正常情况
>>> class A:
... def fa(self):
... print('from A')
... def test(self):
... self.fa()
...
>>> class B(A):
... def fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from B
#把fa定义成私有的,即__fa
>>> class A:
... def __fa(self): #在定义时就变形为_A__fa
... print('from A')
... def test(self):
... self.__fa() #只会与自己所在的类为准,即调用_A__fa
...
>>> class B(A):
... def __fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from A
注:python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的;
其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,作为外部的你,一意孤行也是可以用的,只不过显得稍微傻逼一点点。
python要想与其他编程语言一样,严格控制属性的访问权限,只能借助内置方法如
__getattr__
,详见面向对象进阶。
4.3 特性(property)
4.3.1 什么是特性property
property
是一种特殊的属性,访问它时会执行一段功能(函数),然后返回值
例一:BMI指数,是通过身高和体重计算得来的,但很明显BMI听起来像是一个属性而非方法,如果我们将其做成一个属性,将更便于理解。
例:成人的BMI数值
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class people:
def __init__(self,name,weight,height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
if not isinstance(self.weight,int):
raise TypeError("必须传入整型参数")
if not isinstance(self.height,float):
raise TypeError("必须传入浮点型参数")
return self.weight / (self.height**2)
p1 = people("lisi",65,1.70)
print(p1.bmi)
#在`bmi()`上方加上`@property`这个装饰器后,再调用`bmi()`方法时就不用再加括号了,看起来就像是引用属性
例二:圆的周长和面积听起来也像是属性,但显示它们都是通过圆的半径计算得来
import math
#导入meth模块用以导入圆周率 "派" 的值
class Circle:
def __init__(self,radius): #radius表示圆的半径
self.radius = radius
@property
def area(self): #不能加入多个参数
return math.pi * (self.radius**2) #计算面积
@property
def perimeter(self):
return 2*math.pi*self.radius #计算周长
c = Circle(10)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
注意:此时的特性
area
和perimeter
不能被赋,也不能传入参数。
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
4.3.2 为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name
根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。
除此之外,面向对象的封装有三种方式:
- public 这种其实就是不封装,是对外公开的;
- protected 这种封装方式对外不公开,但对子类公开;
- private 这种封装对谁都不公开。
python并没有在语法上把上面三种方式内建到自己的class机制中,在C++ 里一般会将所有的数据都设置为私有的,然后提供 set 和 get 方法去设置和获取,在python中通过property
方法可以实现设置、获取、删除:
利用@property
特性装饰一个函数属性,然后利用@name.setter
来重新设定值,利用@name.deleter
删除设定的值。注意:这里的name
是被@property
装饰的函数名。
代码示例如下:
class Foo:
def __init__(self,val,permission=False):
self.__NAME=val #将所有的数据属性都隐藏起来
self.permission = permission
@property #相当于定义获取
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter #相当于定义设置,这里的name是引用上面的name
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter #相当于定义删除,可以通过del去删除属性
def name(self):
if not self.permission: #加一个判断,当有权限删除时,才能删除
raise TypeError('Can not delete')
del self.__NAME
f=Foo('egon')
print(f.name) #输出egon
f.name="alex" #设定一个新的name,如果不是字符串,则会抛出异常'TypeError: 10 must be str'
print(f.name) #输出alex
# 直接del f.name 会抛出异常'TypeError: Can not delete'
#先设定权限
f.permission = True
print(f.permission) #输出True
#再次删除,就不会再抛出异常了
del f.name #当permission=True时,可以正常删除
# print(f.name) #删除后,就不能再访问到`f.name`,所以会报异常
property
非装饰器用法:
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
def getname(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
def setname(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
def delname(self):
raise TypeError('Can not delete')
name=property(getname,setname,delname) #不如装饰器的方式清晰
一种property的古老用法
五、绑定方法与非绑定方法
类中定义的函数分成两厌烦:
-
绑定方法(谁来调用就自动将它本身当作第一个参数传入),绑定方法还分为绑定到类的方法和绑定到对象的方法;
1.1 绑定到类的方法:用
@classmethod
装饰器装饰的方法,是为类量身定制的,类名.boud_method()
,会自动将类名当作第一个参数传入。(其实对象也可以调用,但是仍然会将类当作第一个参数传入)1.2 绑定到对象的方法:没有被任何装饰装饰的方法,为对象量身定制,
对象.boud_method()
自动将对象当作第一个参数传入。(绑定到对象的方法实际上就是类内部的函数,类也可以调用,但是必须按照函数的规则来调用,没有自动传值的功能。)
-
非绑定方法:用
@staticmethod
装饰器装饰的方法,不与类或对象绑定,类和对象都可以调用,但是都没有自动传值的功能,就是一个普通工具而已。
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
5.1 @staticmethod
@staticmethod
装饰的方法不与类或对象绑定,谁都可以调用,没有自动传值的功能,python为我们内置了函数@staticmethod
来把类中的函数定义成静态方法:
如下:定义一个MySQL类,为每个MySQL类的实例生成一个id(要求唯一),这个id类和对象都能调用;
import hashlib
import time
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
self.id = self.create_id()
@staticmethod
def create_id(): #就是一个普通工具
m = hashlib.md5(str(time.clock()).encode('utf-8'))
#time.clock()是cpu时间,每时每刻都不一样
#所以利用cpu时间生成的md5值是唯一的。
return m.hexdigest()
'''
print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看结果为普通函数
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看结果为普通函数
'''
5.2 @classmethod
@classmethod
装饰的方法是给类调用的,即绑定到类,类在使用时会将类本身当作参数传给类方法的第一个参数(即使是对象来调用也会将类当作第一个参数传入)。python为我们内置了函数@classmethod
来把类中的函数定义成类方法
示例如下:
除了实例化时自己传入host和port,我们还想通过配置文件来获取host和port,生成另外一个实例
#settings.py
HOST='127.0.0.1'
PORT=3306
import settings
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
def connection(self):
print("Host:%s , port:%s,connecting ..."%(self.host,self.port))
@classmethod
def from_conf(cls):
return cls(settings.HOST,settings.PORT)
#当类来调用时,会自动将类传入
#这里的cls(settings.HOST,settings.PORT)就等同于mysql(settings.HOST,settings.PORT)
#return返回的实际上是一个实例化的对象
conn1 = mysql("192.168.1.87","3306")
conn1.connection()
conn2 = mysql.from_conf()
conn2.connection()
'''
输出:
Host:192.168.1.87 , port:3306,connecting ...
Host:127.0.0.1 , port:3306,connecting ...
'''
conn3 = conn1.from_conf()
# 对象也可以调用,但是默认传的第一个参数仍然是类
conn3.connection()
'''
输出:
Host:127.0.0.1 , port:3306,connecting ...
'''
5.3 比较@staticmethod
和@classmethod
的区别
先来看看__str__
内置方法的用法
l = list([1,2,3])
print(l) #打印的是[1, 2, 3]
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
conn = mysql("127.0.0.1","3306")
print(conn) #打印的是<__main__.mysql object at 0x0000000000701898>
从以上两段代码可以看到,我们自己定义的类,生成的对象直接被打印时,打印的是对象的内存地址,而这并不是我们想要的,我们实际想要的也是像第一段代码那样返回一个有用的信息,这时就要用到__str__
这个内置方法了,它定义在类的内部,只要类被实例化,就会自动触发__str__
的执行,并返回一个值给实例化后的对象。如下示例:
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
def __str__(self):
return "Host:%s,Port:%s"%(self.host,self.port)
conn = mysql("127.0.0.1","3306")
#会将`__str__`方法的返回值赋值给对象conn
print(conn)
'''
输出:
Host:127.0.0.1,Port:3306
'''
现在再来比较比较@staticmethod
和@classmethod
的区别:
先来看看用@staticmethod
效果:
#settings.py
HOST='127.0.0.1'
PORT=3306
import settings
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
@staticmethod
def from_conf():
return mysql(settings.HOST,settings.PORT)
#这里需要指明用mysql类去实例化
def __str__(self):
return "我是mysql实例化的对象"
class mariadb(mysql):
def __str__(self):
return "主机:%s,端口:%s"%(self.host,self.port)
conn = mariadb.from_conf()
print(conn) #我们的意图是想触发mariadb.__str__,但是结果触发了mysql.__str__的执行
#打印结果:我是mysql实例化的对象
将@staticmethod
换成@classmethod
看看有什么变化
import settings
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
@classmethod
def from_conf(cls):
return cls(settings.HOST,settings.PORT)
# @staticmethod
# def from_conf():
# return mysql(settings.HOST,settings.PORT)
#这里需要指明用mysql类去实例化
def __str__(self):
return "我是mysql实例化的对象"
class mariadb(mysql):
def __str__(self):
return "主机:%s,端口:%s"%(self.host,self.port)
conn = mariadb.from_conf()
print(conn) #触发了mariadb.__str__的执行
#打印结果:主机:127.0.0.1,端口:3306
总结:
@classmethod
绑定到类的方法,谁调用,就会将谁当作第一个参数传入,所以在这种继承的环境下,就必须用@classmethod
。
练习:定义MySQL类
1.对象有id、host、port三个属性
2.定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一
3.提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化
4.为对象定制方法,save和get,save能自动将对象序列化到文件中,文件名为id号,文件路径为配置文件中DB_PATH;get方法用来从文件中反序列化出对象
import time,hashlib
import settings
import pickle
import os
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
self.id = self.create_id()
@staticmethod
def create_id():
m = hashlib.md5(str(time.clock()).encode("utf-8"))
# 查看clock源码注释,指的是cpu真实时间,不要用time.time(),否则会出现id重复
return m.hexdigest()
@classmethod
def from_conf(cls):
return cls(settings.HOST,settings.PORT)
def save(self):
pickle.dump(self,open(self.id,'wb'))
def get(self):
return pickle.load(open(self.id,'rb'))
conn = mysql("192.168.1.87","3306")
conn1 = mysql.from_conf()
# print(conn.id)
# print(conn1.id)
# conn.save()
obj = conn.get() #从文件中获取对象
print(obj.id) #获得对象的id
六、 面向对象的软件开发
面向对象的软件工程包括下面几个部分:
- 面向对象分析(object oriented analysis ,OOA)
- 面向对象设计(object oriented design,OOD)
- 面向对象编程(object oriented programming,OOP)
- 面向对象测试(object oriented test,OOT)
- 面向对象维护(object oriendted soft maintenance,OOSM)