面向对象介绍
范式编程
编程是程序员用 特定的语法+数据结构+算法 组成的代码来告诉计算机如何执行任务的过程。
一个程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多不同的方式,对这些不同的编程方式的特点进行归纳总结得出来的编程方式的类别,即为编程范式。不同的编程范式本质上代表各种类型的任务采取的不同的解决问题的思路,大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。两种最重要的编程范式分别是面向过程编程和面向对象编程
面向过程编程(Procedural Programming)
Procedural programming uses a list of instructions to tell the computer what to do step-by-step.
面向过程编程依赖 - procedures, 一个procedure包含一组要被执行的计算步骤,面向过程又被称为top-down languages, 就是程序从上到下一步步执行, 从头到尾解决问题。基本设计思路就是程序一开始是要着手解决一个大问题,然后把一个大问题分解为很多个小问题或子过程,这些子过程再执行的过程再继续分解知道小问题足够见到到可以在一个小步骤范围内解决。
例如, 写一个数据远程备份程序,分为三步,本地打包,上传到云服务器, 测试备份文件可用性。
def cloud_upload(file):
print("\nconnecting cloud storage center...")
print("cloud storage connected.")
print("upload file...xxx..to cloud...", file)
print('close connection.....')
def data_backup(folder):
print("找到要备份的目录...", folder)
print("将备份文件打包,移至相应目录...")
return '/tmp/backup20181103.zip'
def data_backup_test():
print("\n从另外一台机器将备份文件从远程cloud center下载,看文件是否无损")
def main():
zip_file = data_backup("c:\\users\\alex\欧美100G高清无码")
cloud_upload(zip_file)
data_backup_test()
if __name__ == '__main__':
main()
但是如果需求改变,那你写的子过程也需要修改,又如果又有其它子进程依赖这个子过程,那就会发成一连串的影响,随着程序越来越大,这种编程方式的维护难度会越来越高.如果你要处理的任务是复杂的,且需要不断迭代和维护的,还是使用面向对象方便.
面向对象编程(object oriented programming)
假设你现在视一家游戏公司的开发人员,需要你开发一款叫做"人狗大战"的游戏,怎么描述这种不同的角色和他们的功能呢?
person = {
"name": "Alex",
"attack": 100,
"life_value": 1000
}
dog = {
"name": "Peiqi",
"attack": 200,
"life_value": 800
但是这样是有问题的,因为如果你的字典的值不小心定义错了,吧attack写成了atteck,整个程序就有问题。所以你很快想出改进方案, 把字典放进函数:
def person(name,attack,life_value):
data = {
'name':name,
'attack':attack,
'life_value':life_value,
}
return data
def dog(name, attack, life_value):
data = {
'name': name,
'attack': attack,
'life_value': life_value,
}
return data
alex = person("Alex",100,1000)
rain = person("Black girl",80,700)
d = dog("PeiQi",200,800)
这样的话,角色定义好了,还差每个角色的功能,人打狗,狗咬人的功能要定义出来
def attack(p,d):
"""人打狗功能"""
d['life_value'] -= p['attack'] #被打了,要掉血
print("人[%s] 打了 狗[%s]。。。,[%s]的生命值还有[%s]" % (p['name'], d['name'],d['name'],d['life_value']))
def bite(d,p):
"""狗咬人功能"""
p['life_value'] -= d['attack']
print("狗[%s] 咬了 人[%s]。。。,[%s]的生命值还有[%s]" % (d['name'], p['name'],p['name'],p['life_value']))
alex = person("Alex",100,1000)
black_girl = person("Black girl",80,700)
d = dog("PeiQi",200,800)
attack(alex,d)
bite(d,black_girl)
现在,就可以开心的玩耍啦。。。
但玩着玩着, 你不小心调用错了,
你让我咬black_girl一口我不介意,但以狗的身份,我是反对的,所以这明显是个bug,bite()功能是狗专属的,不应该允许人调用,这可怎么办呢?
哈,想了一会,你又搞定了。
def person(name,attack_val,life_value):
def attack( d):
"""人打狗功能"""
d['life_value'] -= attack_val # 被打了,要掉血
print("人[%s] 打了 狗[%s]。。。,[%s]的生命值还有[%s]" % (name, d['name'], d['name'], d['life_value']))
data = {
'name':name,
'attack_val':attack_val,
'life_value':life_value,
'attack':attack
}
return data
def dog(name, attack_val, life_value):
def bite(p):
"""狗咬人功能"""
p['life_value'] -= attack_val
print("狗[%s] 咬了 人[%s]。。。,[%s]的生命值还有[%s]" % (name, p['name'], p['name'], p['life_value']))
data = {
'name': name,
'attack_val': attack_val,
'life_value': life_value,
'bite':bite
}
return data
alex = person("Alex",100,1000)
black_girl = person("Black girl",80,700)
d = dog("PeiQi",200,800)
alex['attack'](d)
d['bite'](black_girl)
你是如此的机智,这样就实现了限制人只能用人自己的功能啦。
但,我的哥,不要高兴太早,刚才你只是阻止了两个完全 不同的角色 之间的功能混用, 但有没有可能 ,同一个种角色,但有些属性是不同的呢? 比如 ,现在游戏升级了,不仅可以打狗,还可以生孩子,但男人不能生呀,只能女的生,怎么办呢?你想了想说,简单呀, 在person函数里包一个子函数叫get_birth(),
def get_birth(person_data):
if person_data['sex'] == 'female':
print("%s生孩子 啦..."% person_data['name'] )
else:
print("你是男的生个毛线.")
没错, 这虽然解决了只能女人生孩子的问题,但其实随着游戏功能越来越多,你会发出男女之间的区别也越来越多 ,但又同时有很多共性,如果 在每个区别处都 单独做判断,那得累死。
你想了想说, 那就直接写2个角色吧, 反正 这么多区别, 我的哥, 不能写两个角色呀,因为他们还有很多共性 , 写两个不同的角色,就代表 相同的功能 也要重写了,是不是我的哥? 。。。 没话说了吧?哈哈, 就是要逼你到绝路上。
上面的问题通过面向对象编程可轻松解决!
什么是面向对象编程?
OOP(Object Oriented Programming)编程是利用“类”和“对象”来创建模型实现对真实世界的描述。-
为什么要使用面向对象?
- 使程序更加容易扩展和易修改,使开发效率变得更高。
- 基于面向对象的程序可以使他人更加容易理解你的代码逻辑
-
面向对象常用名词
- 类: 一个类就是对一类有相同属性的对象的抽象、蓝图、原型、模板。
- 属性: 人类包括很多特征,把这些特征用程序来描述的话,就叫做属性。在一个类中,可以有多个属性。
- 方法: 人类不止有身高、年龄、性别这些属性,还能做很多事情,比如说话、走路、吃饭等,这些动词用程序来描述就叫做方法。
- 实例(对象): 一个对象就是一个类实例化后的实例。
- 实例化: 把一个类转变成一个对象发的过程就叫做实例化
-
面向对象的三大特征
- Encapsulation 封装 在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含类的数据和方法
- Inherittance 继承 一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承。
- Polymorphism 多态 多态是面向对象的重要特征,简单点说“一个接口,多种实现”,一个基类中派生出了不同的子类,且每个子类继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出来的多种形态。
类与对象
类与对象的概念
类即类别、种类,是面向对象设计最重要的概念,从一小节我们得知对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体。
那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看:
- 在现实世界中:肯定是先有对象,再有类
- 在程序中:务必保证先定义类,后产生对象
定义类
按照上述步骤,我们来定义一个类:
- 在现实世界中,先有对象,再有类
对象1:李坦克
特征:
学校=oldboy
姓名=李坦克
性别=男
年龄=18
技能:
学习
吃饭
睡觉
对象2:王大炮
特征:
学校=oldboy
姓名=王大炮
性别=女
年龄=38
技能:
学习
吃饭
睡觉
对象3:牛榴弹
特征:
学校=oldboy
姓名=牛榴弹
性别=男
年龄=78
技能:
学习
吃饭
睡觉
现实中的学生类
相似的特征:
学校=oldboy
相似的技能:
学习
吃饭
睡觉
- 在程序中,务必保证:先定义(类),后使用类(用来产生对象)
#在Python中程序中的类用class关键字定义,而在程序中特征用变量标识,技能用函数标识,因而类中最常见的无非是:变量和函数的定义
class OldboyStudent:
school='oldboy'
def learn(self):
print('is learning')
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
注意:
- 类中可以有任意python代码, 这类代码在类定义阶段会执行,因而会产生新的名称空间,用来存放类的变量名和函数名,可以通过
类名.__dict__
查看 - 类中定义的名字,都是类的属性,点事访问属性的语法。
- 对于经典类来说我们都可通过该字典操作类名称空间的名字,但新式类有限制
类的使用
- 引用类的属性
OldboyStudent.school # 查
OldboyStudent.school='Oldboy' #改
OldboyStudent.x=1 #增
del OldboyStudent.x #删
- 调用类,或称为实例化,得到程序中的对象
s1=OldboyStudent()
s2=OldboyStudent()
s3=OldboyStudent()
#如此,s1、s2、s3都一样了,而这三者除了相似的属性之外还各种不同的属性,这就用到了__init__
-
__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')
补充说明
- 站的角度不同,定义的类是截然不同的;
- 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类等;
- 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类
属性查找与绑定方法
属性查找
类有两种属性: 数据属性和函数属性
- 类的数据属性是对所有对象共享的
# 类的数据属性是所有对象共享的,id都一样
print(id(OldboyStudent.school))
# 下面print结果都一致
print(id(s1.school))
print(id(s2.school))
print(id(s3.school))
- 类的函数数据是绑定给对象使用的, 称为绑定到对象的方法:
# 类的函数属性书绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样
print(OldboyStudent.learn) #<function OldboyStudent.learn at 0x1021329d8>
print(s1.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>>
print(s2.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>>
print(s3.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>>
#ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准
在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中一切皆对象,且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]
小节练习
练习1:编写一个学生类,产生一堆学生对象, (5分钟)
要求:
- 有一个计数器(属性),统计总共实例了多少个对象
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-26 下午9:59
# @File : counter_test.py
class Counter():
count = 0
def __init__(self):
Counter.count += 1
c1 = Counter()
c2 = Counter()
print(Counter.count)
练习2:模仿王者荣耀定义两个英雄类, (10分钟)
要求:
- 英雄需要有昵称、攻击力、生命值等属性;
- 实例化出两个英雄对象;
- 英雄之间可以互殴,被殴打的一方掉血,血量小于0则判定为死亡。
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-26 下午10:04
# @File : heroes.py
class Heroes():
def __init__(self, name, attack, life):
self.name = name
self.attack = attack
self.life = life
self.status = "alive"
def hit(self, hero):
if not isinstance(hero, Heroes):
print("攻击目标必须为英雄.")
return
hero.life -= self.attack
print("%s攻击%s,造成%s点伤害..." % (self.name, hero.name, self.attack))
if hero.life <= 0:
print("%s已经被%s击杀..." % (hero.name, self.name))
hero.life = 0
hero.status = "die"
def __str__(self):
rt = "英雄名: %s 攻击力: %s 生命值: %s 状态: %s " % (
self.name,
self.attack,
self.life,
self.status
)
return rt
h1 = Heroes("关羽", 97, 1000)
h2 = Heroes("夏侯惇", 92, 1200)
print(h1)
h1.hit(h2)
h2.hit(h1)
print(h1)
继承与派生
继承
什么是继承?
继承指的是类与类之间的关系,是一种什么是什么的关系,继承的功能之一就是来解决代码重用问题。
继承是一种创建新类的方式,在python中,新建类可以继承一个或多个父类, 父类有可以成为基类或超累,新建的类称为派生类或子类。
- python中的继承分为: 单继承和多继承
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass
- 查看继承
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
经典类与新式类(关于新式类与经典类的区别,我们稍后讨论)
1.只有在python2中才分新式类和经典类,python3中统一都是新式类
2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
4.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__
)的实现。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类:
继承与重用
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
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) #结果:300
r1.attack(g1)
print(g1.life_value) #结果:243
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
再看属性查找
提示:像g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。那么如何解释下面的打印结果呢?
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1()
class Bar(Foo):
def f1(self):
print('Foo.f1')
b=Bar()
b.f2()
# 打印结果:
# Foo.f2
# Foo.f1
派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
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) #调用功能
print('from riven')
def fly(self): #在自己这里定义新的
print('%s is flying' %self.nickname)
r1=Riven('锐雯雯',57,200,'比基尼')
r1.fly()
print(r1.skin)
'''
运行结果
锐雯雯 is flying
比基尼
'''
继承的实现原理
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列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
-
如果对下一个类存在两个合法的选择,选择第一个父类
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,那么属性的查找方式有两种,分别是:深度优先和广度优先
示范代码:
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中才分新式类与经典类
在子类中调用父类的方法
在子类派生出的新方法中,往往需要重用父类的方法,我们有两种方式实现:
方式一:指名道姓,即父类名.父类方法()
class Vehicle: #定义交通工具类
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('开动啦...')
class Subway(Vehicle): #地铁
def __init__(self,name,speed,load,power,line):
Vehicle.__init__(self,name,speed,load,power)
self.line=line
def run(self):
print('地铁%s号线欢迎您' %self.line)
Vehicle.run(self)
line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()
方式二:super()
class Vehicle: #定义交通工具类
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('开动啦...')
class Subway(Vehicle): #地铁
def __init__(self,name,speed,load,power,line):
#super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
super().__init__(name,speed,load,power)
self.line=line
def run(self):
print('地铁%s号线欢迎您' %self.line)
super(Subway,self).run()
class Mobike(Vehicle):#摩拜单车
pass
line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()
这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,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'>]
组合
组合与重用性
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
>>> class Equip: #武器装备类
... def fire(self):
... print('release Fire skill')
...
>>> class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类
... camp='Noxus'
... def __init__(self,nickname):
... self.nickname=nickname
... self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性
...
>>> r1=Riven('锐雯雯')
>>> r1.equip.fire() #可以使用组合的类产生的对象所持有的方法
release Fire skill
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,
- 继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人 - 组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
示例:继承与组合
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=[]
egon=Teacher('egon',18,'male','沙河霸道金牌讲师')
s1=Student('牛榴弹',18,'female')
python=Course('python','3mons',3000.0)
linux=Course('python','3mons',3000.0)
#为老师egon和学生s1添加课程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
#为老师egon添加学生s1
egon.students.append(s1)
#使用
for obj in egon.course:
obj.tell_info()
总结:
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
抽象类
接口与归一化设计
1.什么是接口
hi boy,给我开个查询接口。。。此时的接口指的是:自己提供给使用者来调用自己功能的方式\方法\入口,java中的interface使用如下
=================第一部分:Java 语言中的接口很好的展现了接口的含义: IAnimal.java
/*
* Java的Interface接口的特征:
* 1)是一组功能的集合,而不是一个功能
* 2)接口的功能用于交互,所有的功能都是public,即别的对象可操作
* 3)接口只定义函数,但不涉及函数实现
* 4)这些功能是相关的,都是动物相关的功能,但光合作用就不适宜放到IAnimal里面了 */
package com.oo.demo;
public interface IAnimal {
public void eat();
public void run();
public void sleep();
public void speak();
}
=================第二部分:Pig.java:猪”的类设计,实现了IAnnimal接口
package com.oo.demo;
public class Pig implements IAnimal{ //如下每个函数都需要详细实现
public void eat(){
System.out.println("Pig like to eat grass");
}
public void run(){
System.out.println("Pig run: front legs, back legs");
}
public void sleep(){
System.out.println("Pig sleep 16 hours every day");
}
public void speak(){
System.out.println("Pig can not speak"); }
}
=================第三部分:Person2.java
/*
*实现了IAnimal的“人”,有几点说明一下:
* 1)同样都实现了IAnimal的接口,但“人”和“猪”的实现不一样,为了避免太多代码导致影响阅读,这里的代码简化成一行,但输出的内容不一样,实际项目中同一接口的同一功能点,不同的类实现完全不一样
* 2)这里同样是“人”这个类,但和前面介绍类时给的类“Person”完全不一样,这是因为同样的逻辑概念,在不同的应用场景下,具备的属性和功能是完全不一样的 */
package com.oo.demo;
public class Person2 implements IAnimal {
public void eat(){
System.out.println("Person like to eat meat");
}
public void run(){
System.out.println("Person run: left leg, right leg");
}
public void sleep(){
System.out.println("Person sleep 8 hours every dat");
}
public void speak(){
System.out.println("Hellow world, I am a person");
}
}
=================第四部分:Tester03.java
package com.oo.demo;
public class Tester03 {
public static void main(String[] args) {
System.out.println("===This is a person===");
IAnimal person = new Person2();
person.eat();
person.run();
person.sleep();
person.speak();
System.out.println("\n===This is a pig===");
IAnimal pig = new Pig();
pig.eat();
pig.run();
pig.sleep();
pig.speak();
}
}
java中的interface
2. 为何要用接口
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
- 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
- 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
- 就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
- 再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
3. 模仿interface
在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念
可以借助第三方模块:http://pypi.python.org/pypi/zope.interface
也可以使用继承,其实继承有两种用途
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用):实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
二:声明某个子类兼容于某基类,定义一个接口类(模仿java的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('进程数据的读取方法')
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,这就用到了抽象类
抽象类
1.什么是抽象类
与java一样,python也有抽象类的概念,但是同样需要借助模块实现,抽象类是一个特殊的类,他的特殊之处在于只能被继承,不能被实例化。
2.为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
- 比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
- 从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
- 从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
3. 在python中实现抽象类
#一切皆文件
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)
4. 抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
多态与多态性
多态
多态指的是一类事物有多种形态,比如
动物有多种形态:人,狗,猪
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
文件有多种形态:文本文件,可执行文件
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')
多态性
什么是多态动态绑定 (在继承的背景下使用时,有时也成为多态性)
多态性是指在不考虑实例的情况下使用实例,多态性分为静态多态性和动态多态性。
静态多态性:如任何类型都可以使用运算符+进行运算
动态多态性如下:
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()
为什么要用多态性(多态性的好处)
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说python本身就是支持多态性的,这么做的好处是什么呢?
- 增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal) - 增加了程序额可扩展性
通过继承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)
'''
鸭子类型
逗比时刻:
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
例1:利用标准库中定义的各种‘与文件类似’的对象,尽管这些对象的工作方式像文件,但他们没有继承内置文件对象的方法
#二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class TxtFile:
def read(self):
pass
def write(self):
pass
class DiskFile:
def read(self):
pass
def write(self):
pass
例2:序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))
#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()
len(s)
len(l)
len(t)
封装
从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子。照这种逻辑看,封装=‘隐藏’,这种理解是相当片面的。
先看如何隐藏
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__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只能在内部使用,如self.__x,引用的就是变形的结果。
- 这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
- 在子类定义的__x不会覆盖在父类定义的_x,因为子类中变形成了:子类名_x,而父类中变形成了:父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
这种变形需要注意的问题是:
- 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:类名_属性,然后就可以访问了,如a._A__N
- 变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
- 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
#正常情况
>>> 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
封装不是单纯意义的隐藏
将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。
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()
封装方法:目的是隔离复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性
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()
封装方法的其他举例:
- 你的身体没有一处不体现着封装的概念:你的身体把膀胱尿道等等这些尿的功能隐藏了起来,然后为你提供一个尿的接口就可以了(接口就是你的。。。,),你总不能把膀胱挂在身体外面,上厕所的时候就跟别人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
- 电视机本身是一个黑盒子,隐藏了所有细节,但是一定会对外提供了一堆按钮,这些按钮也正是接口的概念,所以说,封装并不是单纯意义的隐藏!!!
- 快门就是傻瓜相机为傻瓜们提供的方法,该方法将内部复杂的照相功能都隐藏起来了
提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。
特性(property)
什么是特性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):
return self.weight / (self.height**2)
p1=People('egon',75,1.85)
print(p1.bmi)
例二:圆的周长和面积
import math
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.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
注意:此时的特性area和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
除此之外,看下:
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现:
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
@property
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f=Foo('egon')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'
封装与扩展性
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
#类的设计者
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
return self.__width * self.__length
#使用者
>>> r1=Room('卧室','egon',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
def __init__(self,name,owner,width,length,high):
self.name=name
self.owner=owner
self.__width=width
self.__length=length
self.__high=high
def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
return self.__width * self.__length * self.__high
#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()
绑定方法与非绑定方法
类中定义的函数分成两大类
绑定方法(绑定给谁,谁来调用就自动将它本身当做第一个参数传入)
- 绑定到类的方法:用
classmethod
装饰器装饰的方法。
为类量身定制
类.boud_method(),自动将类当作第一个参数传入
(其实对象也可调用,但仍将类当作第一个参数传入)
- 绑定到对象的方法:没有被任何装饰器装饰的方法。
为对象量身定制
对象.boud_method(),自动将对象当作第一个参数传入
(属于类的函数,类可以调用,但是必须按照函数的规则来,没有自动传值那么一说)
非绑定方法: 用staticmethod
装饰器装饰的方法
不与类或对象绑定,类和对象都可以调用,但是没有自动传值那么一说。就是一个普通工具而已
注意:与绑定到对象方法区分开,在类中直接定义的函数,没有被任何装饰器装饰的,都是绑定到对象的方法,可不是普通函数,对象调用该方法会自动传值,而staticmethod装饰的方法,不管谁来调用,都没有自动传值一说
绑定方法
绑定给对象的方法(略)
绑定给类的方法(classmethod)
classmehtod是给类用的,即绑定到类,类在使用时会将类本身当做参数传给类方法的第一个参数(即便是对象来调用也会将类当作第一个参数传入),python为我们内置了函数classmethod来把类中的函数定义成类方法
#settings.py
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向对象编程\test1\db'
#test.py
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
@classmethod
def from_conf(cls):
print(cls)
return cls(settings.HOST,settings.PORT)
print(MySQL.from_conf) #<bound method MySQL.from_conf of <class '__main__.MySQL'>>
conn=MySQL.from_conf()
conn.from_conf() #对象也可以调用,但是默认传的第一个参数仍然是类
非绑定方法
在类内部用staticmethod装饰的函数即非绑定方法,就是普通函数
staticmethod不与类或对象绑定,谁都可以调用,没有自动传值效果
import hashlib
import time
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id(): #就是一个普通工具
m=hashlib.md5(str(time.time()).encode('utf-8'))
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> #查看结果为普通函数
classmethod与staticmethod的对比
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)
# @classmethod #哪个类来调用,就将哪个类当做第一个参数传入
# def from_conf(cls):
# return cls(settings.HOST,settings.PORT)
def __str__(self):
return '就不告诉你'
class Mariadb(MySQL):
def __str__(self):
return '<%s:%s>' %(self.host,self.port)
m=Mariadb.from_conf()
print(m) #我们的意图是想触发Mariadb.__str__,但是结果触发了MySQL.__str__的执行,打印就不告诉你:
练习
练习1:定义MySQL类
要求:
- 对象有id、host、port三个属性
- 定义工具create_id,在实例化时为每个对象随机生成id,保证id唯一
- 提供两种实例化方式,方式一:用户传入host和port 方式二:从配置文件中读取host和port进行实例化
- 为对象定制方法,save和get_obj_by_id,save能自动将对象序列化到文件中,文件路径为配置文件中DB_PATH,文件名为id号,保存之前验证对象是否已经存在,若存在则抛出异常,;get_obj_by_id方法用来从文件中反序列化出对象
# my.cnf
[mysqld]
host = 127.0.0.1
port = 3066
DB_PATH = /home/fbo/PycharmProjects/fullstack/untitled1/Chapter5
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-27 下午9:20
# @File : mysql_test.py
import hashlib
import time
import os
import configparser
import pickle
class MySQL:
@staticmethod
def load_settings():
cnf = configparser.ConfigParser()
cnf.read("my.cnf")
return cnf
def __init__(self, *args):
self.id = self.creat_id()
if len(args) == 2:
self.host = args[0]
self.port = args[1]
else:
cnf = self.load_settings()
self.host = cnf['mysqld']['host']
self.port = cnf['mysqld']['port']
@staticmethod
def creat_id():
m = hashlib.md5(str(time.time()).encode(encoding="utf-8"))
return m.hexdigest()
@classmethod
def save(cls, obj):
file_path = os.path.join(cls.load_settings()["mysqld"]["DB_PATH"], obj.id)
# file_path = os.path.join(cls.load_settings()["mysqld"]["DB_PATH"], "123")
if os.path.isfile(file_path):
raise FileExistsError
with open(file_path, "wb") as f:
pickle.dump(obj, f)
@classmethod
def get_obj_by_id(cls, id):
file_path = os.path.join(cls.load_settings()["mysqld"]["DB_PATH"], id)
if not os.path.isfile(file_path):
raise FileNotFoundError
with open(file_path, "rb") as f:
rt = pickle.load(f)
return rt
my = MySQL()
my2 = MySQL("localhost", "3066")
print(my2.__dict__)
MySQL.save(my)
print(MySQL.get_obj_by_id('123').__dict__)
内置方法
isinstance(obj,cls)和issubclass(sub,super)
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
issubclass(sub, super)检查sub类是否是 super 类的派生类
反射
什么是反射
反射的概念是由smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
hasattr(object, name)
判断object中有没有一个name字符串对应的方法或属性
getattr(object, name, default=None)
def getattr(object, name, default=None): # known special case of getattr
"""
getattr(object, name[, default]) -> value
Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
When a default argument is given, it is returned when the attribute doesn't
exist; without it, an exception is raised in that case.
"""
pass
setattr(x, y, v)
def setattr(x, y, v): # real signature unknown; restored from __doc__
"""
Sets the named attribute on the given object to the specified value.
setattr(x, 'y', v) is equivalent to ``x.y = v''
"""
pass
delattr(x,y)
def delattr(x, y): # real signature unknown; restored from __doc__
"""
Deletes the named attribute from the given object.
delattr(x, 'y') is equivalent to ``del x.y''
"""
pass
四个方法的使用演示:
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__)
类也是对象
class Foo(object):
staticField = "old boy"
def __init__(self):
self.name = 'wupeiqi'
def func(self):
return 'func'
@staticmethod
def bar():
return 'bar'
print getattr(Foo, 'staticField')
print getattr(Foo, 'func')
print getattr(Foo, 'bar')
反射当前模块成员:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
def s1():
print 's1'
def s2():
print 's2'
this_module = sys.modules[__name__]
hasattr(this_module, 's1')
getattr(this_module, 's2')
导入其他模块,利用反射查找该模块是否存在某个方法
#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
程序目录:
module_test.py
index.py
当前文件:
index.py
"""
import module_test as obj
#obj.test()
print(hasattr(obj,'test'))
getattr(obj,'test')()
为什么用反射之反射的好处
好处一:实现可插拔机制
有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。
总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能.
egon还没有实现全部功能:
class FtpClient:
'ftp客户端,但是还么有实现具体的功能'
def __init__(self,addr):
print('正在连接服务器[%s]' %addr)
self.addr=addr
不影响lili的代码编写
#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):
func_get=getattr(f1,'get')
func_get()
else:
print('---->不存在此方法')
print('处理其他的逻辑')
__setattr__
,__delattr__
,__getattr__
三者的用法演示:
class Foo:
x=1
def __init__(self,y):
self.y=y
def __getattr__(self, item):
print('----> from getattr:你找的属性不存在')
def __setattr__(self, key, value):
print('----> from setattr')
# self.key=value #这就无限递归了,你好好想想
# self.__dict__[key]=value #应该使用它
def __delattr__(self, item):
print('----> from delattr')
# del self.item #无限递归了
self.__dict__.pop(item)
#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3
print(f1.__dict__)
#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)
#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx
二次加工标准类型(包装)
包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)
二次加工标准类型(基于继承实现)
class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
def append(self, p_object):
' 派生自己的append:加上类型检查'
if not isinstance(p_object,int):
raise TypeError('must be int')
super().append(p_object)
@property
def mid(self):
'新增自己的属性'
index=len(self)//2
return self[index]
l=List([1,2,3,4])
print(l)
l.append(5)
print(l)
# l.append('1111111') #报错,必须为int类型
print(l.mid)
#其余的方法都继承list的
l.insert(0,-123)
print(l)
l.clear()
print(l)
练习(clear加权限限制)
class List(list):
def __init__(self,item,tag=False):
super().__init__(item)
self.tag=tag
def append(self, p_object):
if not isinstance(p_object,str):
raise TypeError
super().append(p_object)
def clear(self):
if not self.tag:
raise PermissionError
super().clear()
l=List([1,2,3],False)
print(l)
print(l.tag)
l.append('saf')
print(l)
# l.clear() #异常
l.tag=True
l.clear()
授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。
实现授权的关键点就是覆盖__getattr__方法
授权示范一
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-28 下午9:03
# @File : 授权示例一.py
import time
class FileHandle:
def __init__(self, filename, mode='r', encoding='utf-8'):
self.file = open(filename, mode, encoding=encoding)
def write(self, line):
t = time.strftime("%Y-%m-%d %T")
self.file.write("%s %s" % (t, line))
def __getattr__(self, item):
return getattr(self.file, item)
f1 = FileHandle('b.txt', "w+")
f1.write("hello")
f1.seek(0)
print(f1.read())
f1.close()
授权示例二
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-28 下午9:11
# @File : 授权示例二.py
import time
class FileHandle:
def __init__(self, filename, mode='r', encoding='utf-8'):
if 'b' in mode:
self.file = open(filename, mode)
else:
self.file = open(filename, mode, encoding=encoding)
self.filename = filename
self.mode = mode
self.encoding = encoding
def write(self, line):
if 'b' in self.mode:
if not isinstance(line, bytes):
raise TypeError("must be bytes")
self.file.write(line)
def __getattr__(self, item):
return getattr(self.file, item)
def __str__(self):
if 'b' in self.mode:
res = "<_io.BufferedReader name='%s'>" % self.filename
else:
res = "<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'" % (
self.filename,
self.mode,
self.encoding
)
return res
f = FileHandle('b1.txt', 'wb')
# f.write("hehe")
f.write("hehe".encode('utf-8'))
print(f)
f.close()
练习题
1. 面向对象三大特性,各有什么用处,说说你的理解。
面向对象的三大特性: 封装,继承,多态。
封装: 将数据隐藏起来,隔离复杂度。
继承: 继承用来解决代码重用性,子类可以继承父类已经定义的方法。
多态:指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,多态增加了程序的灵活性和程序的可扩展性。
2.类的属性和对象的属性有什么区别?
3.面向过程编程与面向对象编程的区别与应用场景?
面向过程编程主要是流程,将复杂的问题流程化;缺点是一个流程对应一个问题,扩展性差;应用场景适用于一旦确定需求就很少去改动的程序。
面向对象编程重点是对象,将具体的问题抽象化,形成一个个的类别,解决了程序的扩展性问题;缺点是编程的复杂度远远高于面向过程编程;应用于需求经常变化的软件中;
4.类和对象在内存中是如何保存的。
5.什么是绑定到对象的方法、绑定到类的方法、解除绑定的函数、如何定义,如何调用,给谁用?有什么特性
6.使用实例进行 获取、设置、删除 数据, 分别会触发类的什么私有方法
class A(object):
pass
a = A()
a["key"] = "val" # __setitem__
a = a["key"] # __getitem__ __setattr__
del a["key"] # __delitem__
7.python中经典类和新式类的区别
经典类:深度优先
新式类:广度优先
super()用法
8.如下示例, 请用面向对象的形式优化以下代码
def exc1(host,port,db,charset):
conn=connect(host,port,db,charset)
conn.execute(sql)
return xxx
def exc2(host,port,db,charset,proc_name)
conn=connect(host,port,db,charset)
conn.call_proc(sql)
return xxx
# 每次调用都需要重复传入一堆参数
exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;')
exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字')
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午4:30
# @File : ex008.py
class DataBases:
def __init__(self, host, port, db, charset):
self.host = host
self.port = port
self.db = db
self.charset = charset
def execute(self, sql):
rt = ("execute Sql: [%s]." % sql)
return rt
def call_proc(self, proc_name):
rt = "Calling process: [%s]." % proc_name
return rt
conn = DataBases('127.0.0.1', 3306, 'db1', 'utf8')
print(conn.execute('select * from tb1;'))
print(conn.call_proc('存储过程的名字'))
9.示例1, 现有如下代码, 会输出什么:
class People(object):
__name = "luffy"
__age = 18
p1 = People()
print(p1.__name, p1.__age)
程序会报错,抛出AttributeError,因为python或将前缀为双下划线的属性在构建时改为_People__name
,实现属性隐藏的目的。
10.示例2, 现有如下代码, 会输出什么:
class People(object):
def __init__(self):
print("__init__")
def __new__(cls, *args, **kwargs):
print("__new__")
return object.__new__(cls, *args, **kwargs)
People()
输出结果为:
__new__ # People类实际上是默认元类type类的的实例对象,由class语法调用type类的__new__方法创建
__init__ # 在对象实例化时调用__init__方法
11.请简单解释Python中 staticmethod(静态方法)和 classmethod(类方法), 并分别补充代码执行下列方法。
class A(object):
def foo(self, x): # 绑定到对象的方法,会将对象当做第一个参数传入
print("executing foo(%s, %s)" % (self,x))
@classmethod #绑定到类的犯法,将类当做对个参数传入
def class_foo(cls, x):
print("executing class_foo(%s, %s)" % (cls,x))
@staticmethod # 非绑定方法, 不会制动传入任何参数
def static_foo(x):
print("executing static_foo(%s)" % (x))
a = A()
a.foo("foo")
A.class_foo("foo")
a.static_foo("aaaa")
A.static_foo("static")
12.请执行以下代码,解释错误原因,并修正错误。
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午5:00
# @File : ex012.py
class Dog(object):
def __init__(self,name):
self.name = name
@property
def eat(self):
print(" %s is eating" %self.name)
d = Dog("ChenRonghua")
# d.eat() # @property把一个方法变成一个静态属性 --> TypeError: 'NoneType' object is not callable
d.eat
13.下面这段代码的输出结果将是什么?请解释。
class Parent(object):
x = 1
class Child1(Parent):
pass
class Child2(Parent):
pass
print(Parent.x, Child1.x, Child2.x)
Child1.x = 2
print(Parent.x, Child1.x, Child2.x)
Parent.x = 3
print(Parent.x, Child1.x, Child2.x)
# 1 1 1 继承自父类的类属性x,所以都一样,指向同一块内存地址
# 1 2 1 更改Child1,Child1的x指向了新的内存地址
# 3 2 3 更改Parent,Parent的x指向了新的内存地址
14.多重继承的执行顺序,请解答以下输出结果是什么?并解释。
class A(object):
def __init__(self):
print('A')
super(A, self).__init__()
class B(object):
def __init__(self):
print('B')
super(B, self).__init__()
class C(A):
def __init__(self):
print('C')
super(C, self).__init__()
class D(A):
def __init__(self):
print('D')
super(D, self).__init__()
class E(B, C):
def __init__(self):
print('E')
super(E, self).__init__()
class F(C, B, D):
def __init__(self):
print('F')
super(F, self).__init__()
class G(D, B):
def __init__(self):
print('G')
super(G, self).__init__()
if __name__ == '__main__':
g = G()
f = F()
# G
# D
# A
# B
#
# F
# C
# B
# D
# A
python3继承时广度优先,查找顺序按照__dict__
15.请编写一段符合多态特性的代码.
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午5:18
# @File : ex015.py
import abc
class Animal(metaclass=abc.ABCMeta):
def talk(self):
pass
class People(Animal):
def talk(self):
print("I'm the king of the world!!!")
class Pig(Animal):
def talk(self):
print("Live is nothing but eat...")
People().talk()
Pig().talk()
16.很多同学都是学会了面向对象的语法,却依然写不出面向对象的程序,原因是什么呢?
原因就是因为你还没掌握一门面向对象设计利器,即领域建模,请解释下什么是领域建模,以及如何通过其设计面向对象的程序?领域建模 此blog最后面有详解
17.请写一个小游戏,人狗大站,2个角色,人和狗,游戏开始后,生成2个人,3条狗,互相混战,人被狗咬了会掉血,狗被人打了也掉血,狗和人的攻击力,具备的功能都不一样。注意,请按题14领域建模的方式来设计类。
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午5:35
# @File : ex016.py
"""
人狗大站,2个角色,人和狗,
游戏开始后,生成2个人,3条狗,互相混战,人被狗咬了会掉血,
狗被人打了也掉血,狗和人的攻击力,具备的功能都不一样。
"""
import abc
import random
class Role(metaclass=abc.ABCMeta):
def __init__(self, name, atc, life):
self.name = name
self.atc = atc
self.life = life
self.die = False
def attack(self, obj):
if obj.die:
print("目标已经死亡,攻击无效!!!")
return
obj.life -= self.atc
if obj.life <= 0:
obj.life = 0
obj.die = True
class Human(Role):
def attck(self, obj):
super(Human, self).attack(obj)
print("[%s]攻击了[%s],造成了<%s>点伤害..." % (self.name, obj.name, self.atc))
if obj.die:
print((obj.name + "已经死亡").center(80, "-"))
class Dog(Role):
def attck(self, obj):
super(Dog, self).attack(obj)
print("[%s]咬了[%s],造成了<%s>点伤害..." % (self.name, obj.name, self.atc))
if obj.die:
print((obj.name + "已经死亡").center(80, "-"))
h1 = Human("张三", 10, 100)
h2 = Human("李四", 8, 120)
d1 = Dog("Alex", 20, 50)
d2 = Dog("Wupeiqi", 22, 44)
d3 = Dog("zhang", 30, 30)
role_list = [h1, h2, d1, d2, d3]
game_end = False
while not game_end:
if len(role_list) == 1:
print("<%s> 赢得了胜利" % role_list[0].name)
game_end = True
else:
r1 = random.choice(role_list)
obj_list = [i for i in role_list if i != r1 ]
obj = random.choice(obj_list)
r1.attck(obj)
if obj.die:
role_list.remove(obj)
18.编写程序, 在元类中控制把自定义类的数据属性都变成大写.
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午6:15
# @File : ex017.py
class MyMeta(type):
def __new__(cls, name, bases, args):
# print(name)
# print(bases)
rt_args = {}
for k, v in args.items():
if isinstance(v, str) and (not v.startswith("__")):
rt_args[k] = v.upper()
continue
else:
rt_args[k] = v
print(rt_args)
return type.__new__(cls, name, bases, rt_args)
class UpClass(metaclass=MyMeta):
name = "fbo"
age = 18
up = UpClass.__dict__
print(up)
19.编写程序, 在元类中控制自定义的类无需init方法.
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午8:43
# @File : ex018.py
class MyMetaClass(type):
def __call__(cls, *args, **kwargs):
if args:
raise TypeError("must use key arguments!")
obj = object.__new__(cls)
for k, v in kwargs.items():
obj.__dict__[k] = v
return obj
class Test(metaclass=MyMetaClass):
pass
t = Test(name = "hehe", age = 22, male = "F")
print(t.__dict__)
20.编写程序, 编写一个学生类, 要求有一个计数器的属性, 统计总共实例化了多少个学生.
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午9:00
# @File : ex020.py
import random
class Student:
count= 0
def __init__(self):
Student.count += 1
for i in range(random.randint(1, 100)):
s = Student()
print(Student.count)
21.编写程序, A 继承了 B, 俩个类都实现了 handle 方法, 在 A 中的 handle 方法中调用 B 的 handle 方法
class B:
def handle(self):
print("From B")
class A(B):
def handle(self):
print("From A")
super().handle()
a = A()
a.handle()
22.编写程序, 如下有三点要求:
- 自定义用户信息数据结构, 写入文件, 然后读取出内容, 利用json模块进行数据的序列化和反序列化
e.g
{
"egon":{"password":"123",'status':False,'timeout':0},
"alex":{"password":"456",'status':False,'timeout':0},
}
- 定义用户类,定义方法db,例如 执行obj.db可以拿到用户数据结构
- 在该类中实现登录、退出方法, 登录成功将状态(status)修改为True, 退出将状态修改为False(退出要判断是否处于登录状态).密码输入错误三次将设置锁定时间(下次登录如果和当前时间比较大于10秒即不允许登录)
#!/usr/bin/env python3
# Author : fbo
# @Time : 18-4-29 下午9:12
# @File : ex022.py
import json
import time
import os
class User_Info():
__default = {
"egon": {"password": "123", 'status': False, 'timeout': 0},
"alex": {"password": "456", 'status': False, 'timeout': 0},
}
__file_name = "db.json"
def init_db(self):
self.save_db(self.__default)
def save_db(self, data):
with open(self.__file_name, "w") as f:
json.dump(data, f)
def db(self):
with open(self.__file_name) as f:
rt = json.load(f)
return rt
def login(self):
exit_flag = False
user_account = self.db()
print(user_account)
try_user = []
while not exit_flag:
user = input("Name: ").strip()
password = input("Password: ").strip()
if user in user_account:
if user_account[user]["status"]:
print("You already login...")
exit_flag = True
elif user_account[user]["timeout"] and (time.time() - user_account[user]["timeout"] < 10):
print("You has been lock...")
else:
if user_account[user]["password"] == password:
user_account[user]["status"] = True
user_account[user]["timeout"] = 0
self.save_db(user_account)
print("%s login success.." % user)
exit_flag = True
else:
try_user.append(user)
print(try_user)
if try_user.count(user) == 3:
try_user = [i for i in try_user if i != user]
user_account[user]["timeout"] = time.time()
else:
print("%s not exist..." % user)
ui = User_Info()
if not os.path.isfile(ui._User_Info__file_name):
ui.init_db()
ui.login()
23.用面向对象的形式编写一个老师角色
实现以下功能, 获取老师列表, 创建老师、删除老师、创建成功之后通过 pickle 序列化保存到文件里,并在下一次重启程序时能读取到创建的老师, 例如程序目录结构如下: