第五章| 5.1面向对象-继承 |封装 |多态
1. 面向过程与面向对象编程
面向过程
复杂的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
面向过程:核心是过程二字,过程指的是解决问题的步骤,设计一条流水线,机械式的思维方式
优点:复杂的问题流程化,进而简单化
缺点:可扩展性差
import json
import re
def interactive(): #与用户交互
name=input('>>: ').strip()
pwd=input('>>: ').strip()
email=input('>> ').strip()
return { #把结果返回
'name':name,
'pwd':pwd,
'email':email
}
def check(user_info): #检测 user_info 接收上一步返回的结果
is_valid=True #默认是有效 True的
if len(user_info['name']) == 0:
print('用户名不能为空')
is_valid=False #现在这个数据是无效的
if len(user_info['pwd']) < 6:
print('密码不能少于6位')
is_valid=False
if not re.search(r'@.*?\.com$',user_info['email']):
print('邮箱格式不合法')
is_valid=False
return { #返回结果
'is_valid':is_valid,
'user_info':user_info
}
def register(check_info): #注册
if check_info['is_valid']:
with open('db.json','w',encoding='utf-8') as f:
json.dump(check_info['user_info'],f)
def main():
user_info=interactive()
check_info=check(user_info)
register(check_info)
if __name__ == '__main__':
main()
如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。
面向对象
面向对象:核心就是对象二字,对象就是特征与技能的结合体
优点:可扩展性强
缺点:编程复杂度高
应用场景:用户需求经常变化,互联网应用,游戏,企业内部应用
类就是一系列对象相似的特征与技能的结合体
强调:站在不同的角度,得到的分类是不一样的
在现实世界中:一定先有对象,后有类
在程序中:一定得先定义类,后调用类来产生对象
站在路飞学院的角度,大家都是学生
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
在现实世界中: 对象1:王二丫 特征: 学校='luffycity' 名字='王二丫' 性别='女' 年龄=18 技能: 学习 吃饭 睡觉 对象2:李三炮 特征: 学校='luffycity' 名字='李三炮' 性别='男' 年龄=38 技能: 学习 吃饭 睡觉 对象3:张铁蛋 特征: 学校='luffycity' 名字='张铁蛋' 性别='男' 年龄=48 技能: 学习 吃饭 睡觉 总结现实中路飞学院的学生类: 相似的特征 学校='luffycity' 相似的技能 学习 吃饭 睡觉
#先定义类
class LuffyStudent:
school='luffycity'
def learn(self):
print('is learning')
def eat(self):
print('is sleeping')
#后产生对象
stu1=LuffyStudent()
stu2=LuffyStudent()
stu3=LuffyStudent()
print(stu1)
print(stu2)
print(stu3)
如何使用类
#先定义类
class LuffyStudent:
school='luffycity' #数据属性
def learn(self): #函数属性
print('is learning')
def eat(self): #函数属性
print('is sleeping')
#查看类的名称空间
#print(LuffyStudent.__dict__) #{'__module__': '__main__', 'school': 'luffycity', 'learn': <function LuffyStudent.learn at 0x000000000226CAE8>, '__dict__': <attribute '__dict__' of 'LuffyStudent' objects>, '__weakref__': <attribute '__weakref__' of 'LuffyStudent' objects>, '__doc__': None}
#print(LuffyStudent.__dict__['school']) #luffycity
#print(LuffyStudent.__dict__['learn']) #<function LuffyStudent.learn at 0x000000000294CAE8>
#查
#print(LuffyStudent.school) #就相当于这样查找 :LuffyStudent.__dict__['school'] #luffycity
#print(LuffyStudent.learn) #LuffyStudent.__dict__['learn'] 与上边输出结果一样的
#增
LuffyStudent.county='China'
# print(LuffyStudent.__dict__)
print(LuffyStudent.county) #China
#删
del LuffyStudent.county
#改
LuffyStudent.school='Luffycity'
__init__方法
#__init__方法用来为对象定制对象自己独有的特征
#该方法是在对象产生之后才会执行,只用来为对象进行初始化操作,可以有任意代码,但一定不能有返回值
class LuffyStudent:
school='luffycity'
#stu1, '王二丫', '女', 18 传给self,name,sex,age
def __init__(self,name,sex,age):
self.Name=name
self.Sex=sex
self.Age=age
#stu1.Name='王二丫'
#stu1.Sex='女'
#stu1.Age=18
def learn(self):
print('is learning')
def eat(self):
print('is sleeping')
#后产生对象
stu1=LuffyStudent('王二丫','女',18) ##LuffyStudent.__init__(stu1,'王二丫','女',18)
#加上__init__方法后,实例化的步骤:
# 1、先产生一个空对象stu1
# 2、然后调用LuffyStudent.__init__(stu1,'王二丫','女',18)
#查
print(stu1.__dict__) #{'Name': '王二丫', 'Sex': '女', 'Age': 18}
#print(stu1.Name)
#print(stu1.Sex)
#print(stu1.Age) #18
#改
# stu1.Name='李二丫'
# print(stu1.__dict__)
# print(stu1.Name)
#删除
# del stu1.Name
# print(stu1.__dict__)
#增
# stu1.class_name='python开发'
# print(stu1.__dict__) #{'Name': '王二丫', 'Sex': '女', 'Age': 18, 'class_name': 'python开发'}
#实例化
# stu2=LuffyStudent('李三炮','男',38) #Luffycity.__init__(stu2,'李三炮','男',38)
# print(stu2.__dict__)
# print(stu2.Name)
# print(stu2.Age)
# print(stu2.Sex)
属性查找
类有两种属性:数据属性和函数属性。
对象:特征与技能的结合体;
类:类是一系列对象相似的特征与相似的技能的结合体;
类中的数据属性:是所以对象共有的;
#类中的函数属性:是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方式时,会把对象本身当作第一个传入,传给self
class LuffyStudent:
school='luffycity'
def __init__(self,name,sex,age):
self.Name=name
self.Sex=sex
self.Age=age
def learn(self):
print('%s is learning %s' %(self.Name))
def eat(self):
print('%s is sleeping' %self.Name)
#后产生对象
stu1=LuffyStudent('王二丫','女',18)
stu2=LuffyStudent('李三炮','男',38)
stu3=LuffyStudent('张铁蛋','男',48)
# print(stu1.__dict__)
# print(stu2.__dict__)
# print(stu3.__dict__)
#对象:特征与技能的结合体
#类:类是一系列对象相似的特征与相似的技能的结合体
#类中的数据属性:是所以对象共有的,id都一样
# print(LuffyStudent.school,id(LuffyStudent.school)) #luffycity 43599280
#
# print(stu1.school,id(stu1.school)) #luffycity 43599280
# print(stu2.school,id(stu2.school)) #luffycity 43599280
# print(stu3.school,id(stu3.school))
#类中的函数属性:是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方式时,会把对象本身当作第一个传入,传给self
##obj.method称为绑定方法,内存地址都不一样
# print(LuffyStudent.learn) #<function LuffyStudent.learn at 0x000000000225CB70>
# print(stu1.learn) ##绑定方法 bond
# LuffyStudent.learn(stu1) #is learning
# LuffyStudent.learn(stu2) #is learning
# LuffyStudent.learn(stu3)
类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数。类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法
强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
类中传参是给对象使用的。注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
#Author:Kris
x='global' ##函数中的参数
class LuffyStudent:
school='luffycity'
def __init__(self,name,sex,age):
self.Name=name
self.Sex=sex
self.Age=age
def learn(self,x):
print('%s is learning %s' %(self.Name,x))
def eat(self):
print('%s is sleeping' %self.Name)
#后产生对象
stu1=LuffyStudent('王二丫','女',18)
stu2=LuffyStudent('李三炮','男',38)
stu3=LuffyStudent('张铁蛋','男',48)
print(stu1.__dict__)
# print(stu2.__dict__)
# print(stu3.__dict__)
##加参数x后
print(stu1.learn) #加不加参数它都是绑定方法<bound method LuffyStudent.learn of <__main__.LuffyStudent object at 0x0000000001EF1EF0>>
stu1.learn(1) #learn(stu1,1)#把stu1传给self,1传给x # is learning
print(stu2.learn) #<bound method LuffyStudent.learn of <__main__.LuffyStudent object at 0x0000000001EF1F28>>
print(stu3.learn)
stu2.learn(2)
stu3.learn(3)
##在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
##代码同上
# stu1.x='from stu1' #没有之后再去类里边找 # LuffyStudent.x='from Luffycity class'
#print(stu1.x) ##打印出 from stu1
print(stu1.__dict__) # {'Name': '王二丫', 'Sex': '女', 'Age': 18, 'x': 'from stu1'} print(stu1.x) ##只在类里边找,类里边没有,不去全局里找,所以不会打印 x='global'这个一开始定义的全局 from
- 站的角度不同,定义出的类是截然不同的;
- 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类等;
- 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类。
Python中一切皆对象 ,在py3中统一了类与类型的概念
print(type[1,2]) # 打印<class 'list'>
print(list)
print(Luffycity) #打印类,代码见上 输出 <class '__main__.Luffystudent'>
l1 = [1,2,3] ##就等于l = list([1,2,3])
l1.append(4) ##等同于list.append(l1,4)
print(l1)
类可以将数据与专门操作该数据的功能整合到一起,可扩展性高。
小节练习
1、编写一个学生类,产生一堆学生对象。
要求:有一个计数器(属性),统计总共实例了多少个对象
class student(): school = 'luffycity' count = 0 def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex ##self.count += 1 ##这样写不行 student.count += 1 def learn(self): print('%s is learnning'%self.name) stu1 = student('alex', 22,'male') stu2 = student('sindy', 24,'female') print(student.count) print(stu1.count) print(stu2.count) print(stu1.__dict__) print(stu2.__dict__)
2、模仿王者荣耀定义两个英雄类
要求:
- 英雄需要有昵称、攻击力、生命值等属性;
- 实例化出两个英雄对象;
- 英雄之间可以互殴,被殴打的一方掉血,血量小于0则判定为死亡。
class Garen(): camp = 'Demacia' def __init__(self,nickname,life_value,aggresivity): self.nicakname = nickname self.life_value = life_value self.aggresivity = aggresivity def attack(self,enemy): enemy.life_value -= self.aggresivity class Raren(): camp = 'Noxus' def __init__(self,nickname,life_value,aggresivity): self.nicakname = nickname self.life_value = life_value self.aggresivity = aggresivity def attack(self,enemy): enemy.life_value -= self.aggresivity g1 = Garen('草丛伦',100,30) r1 = Riven('瑞雯雯',80,50) print(r1.life_value) g1.attack(r1) print(r1.life_value)
2. 继承(解决代码重用)
继承指的是类与类之间的关系,是一种什么“是”什么的关系,先学会找继承关系。继承的功能之一就是用来解决代码重用问题。继承是一种创建新类的方式,在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'>)
总结对象之间的特征得到类,总结类之间的特征得到父类,
class Hero:
camp = 'Demacia'
def __init__(self,nickname,life_value,aggresivity):
self.nicakname = nickname
self.life_value = life_value
self.aggresivity = aggresivity
def attack(self,enemy):
enemy.life_value -= self.aggresivity
class Garen(Hero):
pass
# class Raren(Hero):
# pass
g1 = Garen('kris',100,30) #Garen类里边没有__init__方法,回去父类里边找__init__
print(g1.nicakname,g1.life_value,g1.aggresivity)
属性查找
对象自己---自己类---父类--....
没有__init__方法就没有自己的属性
class Hero:
camp = 'Demacia'
#x = 3
def __init__(self,nickname,life_value,aggresivity):
self.nicakname = nickname
self.life_value = life_value
self.aggresivity = aggresivity
def attack(self,enemy):
enemy.life_value -= self.aggresivity
class Garen(Hero):
x = 2
pass
# class Raren(Hero):
# pass
g1 = Garen('kris',100,30) #G aren类里边没有__init__方法,回去父类里边找__init__
print(g1.nicakname,g1.life_value,g1.aggresivity)
g1.x = 1
print(g1.x) # 1
#属性查找练习
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('from Foo.f2')
self.f1() ##相当于b.f1() b对象本身里边找不到f1,然后去b的类Bar里边找
class Bar(Foo):
def f1(self):
print('from Bar.f2')
b = Bar()
b.f2() #对象没有定义__init__方法,自己的找不到f2,就去自己的类里边找,没有再去父类里边 #打印 from Foo.f2 from Bar.f2
派生
class Hero:
def __init__(self,nickname,life_value,aggresivity):
self.nicakname = nickname
self.life_value = life_value
self.aggresivity = aggresivity
def attack(self,enemy):
enemy.life_value -= self.aggresivity
class Garen(Hero):
camp = 'Demacia'
def attack(self,enemy):
print('from Garen class') #当子类中派生出新的属性,就以派生的新的属性为准,如果没有就遗传父类的
enemy.life_value -= self.aggresivity
class Raren(Hero):
camp = 'Noxus'
g1 = Garen('kris',100,20)
r1 = Raren('瑞雯雯',80,50)
# print(g1.camp) #Demacia
# g1.attack(r1) #from Garen class
# print(r1.life_value) #60
g1.attack(r1)
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参
数也要为其传值。
继承的实现原理
多继承的情况下该怎么去找
新式类和经典类,py2有区分 py3中都是新式类。经典类的深度优先,新式类的广度优先。
新式类:先从对象自己这去找,没有再去对象的类中找,然后再从左边第一个父中找.....
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')
pass
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
#查找顺序 F--D--B--E--C--A
f1=F()
f1.test() #先找到test这个属性,找到发现是一个方法才可以加括号 #from B
print(F.mro())
打印出:
from B
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
子类重用父类的属性
在子类派生出的新的方法中重用父类方法,有两种实现方式:
方式一:指名道姓,不依赖继承;方式二:super()依赖继承
class Hero:
def __init__(self,nickname,life_value,aggresivity):
self.nicakname = nickname
self.life_value = life_value
self.aggresivity = aggresivity
def attack(self,enemy):
enemy.life_value -= self.aggresivity
class Garen(Hero):
camp = 'Demacia'
def attack(self,enemy):
Hero.attack(self,enemy) #类中使用函数要把它的参数都传进去;指名道姓的使用
print('from Garen class') #当子类中派生出新的属性,就以派生的新的属性为准,如果没有就遗传父类的
class Raren(Hero):
camp = 'Noxus'
g1 = Garen('kris',100,30)
r1 = Raren('瑞雯雯',80,50)
print(r1.life_value) #80
g1.attack(r1) #from Garen class
print(r1.life_value) #50
class Hero:
def __init__(self,nickname,life_value,aggresivity):
self.nickname = nickname
self.life_value = life_value
self.aggresivity = aggresivity
def attack(self,enemy):
enemy.life_value -= self.aggresivity
class Garen(Hero):
camp = 'Demacia'
def __init__(self,nickname,life_value,aggresivity,weapon):
Hero.__init__(self,nickname,life_value,aggresivity) #相当于 self.nickname = nickname ...
self.weapon = weapon
def attack(self,enemy):
Hero.attack(self,enemy) #类中使用函数要把它的参数都传进去;###第一种方式:指名道姓的使用
print('from Garen class') #当子类中派生出新的属性,就以派生的新的属性为准,如果没有就遗传父类的
class Raren(Hero):
camp = 'Noxus'
g = Garen('kris',100,40,'屠龙刀')
print(g.__dict__) #{'nickname': 'kris', 'life_value': 100, 'aggresivity': 40, 'weapon': '屠龙刀'}
方法二:super() 依赖继承
class Hero:
def __init__(self,nickname,life_value,aggresivity):
self.nicakname = nickname
self.life_value = life_value
self.aggresivity = aggresivity
def attack(self,enemy):
enemy.life_value -= self.aggresivity
class Garen(Hero):
camp = 'Demacia'
def attack(self,enemy):
super(Garen,self).attack(enemy) #对象来调用 ##依赖继承 #重用父类方法 #可以理解为得到了父类的对象
print('from Garen class') #super(Garen,self)得到的是一个特殊对象,对象得到的就是它的绑定方法而不是函数了,第一个参数self就不用传了 ,只需传enemy即可
class Raren(Hero):
camp = 'Noxus'
g1 = Garen('kris',100,30)
r1 = Raren('瑞雯雯',80,50)
g1.attack(r1) #from Garen class
print(r1.life_value) #50 #80-30
class Hero:
def __init__(self,nickname,life_value,aggresivity):
self.nickname = nickname
self.life_value = life_value
self.aggresivity = aggresivity
def attack(self,enemy):
enemy.life_value -= self.aggresivity
class Garen(Hero):
camp = 'Demacia'
def __init__(self,nickname,life_value,aggresivity,weapon):
#Hero.__init__(self,nickname,life_value,aggresivity) #相当于 self.nickname = nickname ...
#super(Garen,self).__init__(nickname,life_value,aggresivity) #在py2中
super().__init__(nickname,life_value,aggresivity) #在py3中简写
self.weapon = weapon
def attack(self,enemy):
Hero.attack(self,enemy) #指名道姓的使用
print('from Garen class')
g = Garen('kris',100,40,'屠龙刀')
print(g.__dict__) #{'nickname': 'kris', 'life_value': 100, 'aggresivity': 40, 'weapon': '屠龙刀'}
super依赖继承
class A:
def f1(self):
print('from A')
super().f1() #它是基于C类往下找的,走到这的时候接着往下找,所以去找了B类
class B:
def f1(self):
print('from B')
class C(A,B):
pass
print(C.mro())
#[<class '__main__.C'>,
# <class '__main__.A'>,
# <class '__main__.B'>,
# <class 'object'>]
c=C()
c.f1()
打印:
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
from A
from B
组合(基于什么有什么的关系)
把两个类或多个类组合到一起。不用继承,用组合也可以节省代码。
软件重用的重要方式除了继承之外还有另外一种方式,即:组合,组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合。
二者的概念和使用场景皆不同,
1.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
class People:
school = 'luffycity'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People):
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex)
self.level = level
self.salary = salary
def teach(self):
print('%s is teaching'%self.name)
class Student(People):
school = 'luffycity'
def __init__(self,name,age,sex,class_time):
super().__init__(name,age,sex)
self.class_time = class_time
def learn(self):
print('%s is learning'%self.name)
class Course:
def __init__(self,course_name,course_price,course_period):
self.course_name = course_name
self.course_price = course_price
self.course_period = course_period
def tell_info(self):
print('课程名<%s> 课程价钱<%s> 课程周期<%s>' %(self.course_name,self.course_price,self.course_period))
teacher1 = Teacher('alex',22,'male',10,3000)
teacher2 = Teacher('egon',24,'female',30,4000)
python = Course('python',3000,'3months')
linux=Course('linux',2000,'4mons')
print(python.course_name) #python
teacher1.course = python ###把两个类组合到一起 Course类以Teacher类的对象作为数据属性 ;course作为数据属性使用。
teacher2.course = python
print(python) #<__main__.Course object at 0x000000000298E208>
print(teacher1.course) #<__main__.Course object at 0x000000000229F240>
print(teacher2.course) #<__main__.Course object at 0x000000000229F240>
print(teacher1.course.course_name) #python
print(teacher2.course.course_name) #python
teacher1.course.tell_info() #课程名<python> 课程价钱<3000> 课程周期<3months>
student1=Student('张三',28,'female','08:30:00')
student1.course1=python #组合 course1
student1.course2=linux
student1.course1.tell_info() ##可以得到学生1学的课程的信息 #课程名<python> 课程价钱<3000> 课程周期<3months>
student1.course2.tell_info() #课程名<linux> 课程价钱<2000> 课程周期<4mons>
student1.courses=[] #可扩展性比较强,定义个列表,所有学生学的课程都放这里边了; #把两个类或者多个类组合到一起就是组合
student1.courses.append(python)
student1.courses.append(linux)
print(student1.courses) #[<__main__.Course object at 0x00000000029772E8>, <__main__.Course object at 0x0000000002977320>]
学生有生日,日期能单独定义一个类是因多个学生可能同年同月同日生日
class People:
school='luffycity'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
class Teacher(People):
def __init__(self,name,age,sex,level,salary,):
super().__init__(name,age,sex)
self.level=level
self.salary=salary
def teach(self):
print('%s is teaching' %self.name)
class Student(People):
def __init__(self, name, age, sex, class_time,):
super().__init__(name,age,sex)
self.class_time=class_time
def learn(self):
print('%s is learning' % self.name)
class Course:
def __init__(self,course_name,course_price,course_period):
self.course_name = course_name
self.course_price = course_price
self.course_period = course_period
def tell_info(self):
print('课程名<%s> 课程价钱<%s> 课程周期<%s>' %(self.course_name,self.course_price,self.course_period))
class Date:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def tell_info(self):
print('%s-%s-%s' %(self.year,self.mon,self.day))
student1=Student('张三',28,'female','08:30:00')
d=Date(1988,4,20)
python=Course('python',3000,'3mons')
student1.birh=d
student1.birh.tell_info() #1988-4-20
student1.course=python
student1.course.tell_info() #课程名<python> 课程价钱<3000> 课程周期<3mons>
抽象类与归一化
把多个类抽象出比较像的部分定义一个类
接口是提供给使用者来调用自己功能的方式\方法\入口,接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化的好处在于:
- 归一化让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
- 归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
- 就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
- 再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
import abc #第三方模块---抽象类把子类给规范化
class Animal(metaclass=abc.ABCMeta): #只能被继承,不能被实例化 #把它做成一个抽象类
all_type='animal' #数据属性
@abc.abstractmethod ##保证子类必须有这个方法,必须叫这个名字,给它规范化加个装饰器
def run(self):
pass
@abc.abstractmethod
def eat(self):
pass
#animal=Animal() #不能实例化抽象类,会报错
class People(Animal): #继承抽象类
def run(self):
print('people is running')
def eat(self):
print('people is eating')
class Pig(Animal):
def run(self):
print('people is walking')
def eat(self):
print('people is eating')
class Dog(Animal):
def run(self):
print('people is walking')
def eat(self):
print('people is eating')
#peo1=People()
#pig1=Pig()
#dog1=Dog()
#peo1.eat()
#pig1.eat()
#dog1.eat()
#
# print(peo1.all_type) #进行查找,找到抽象类里边,抽象类本质还是个类
3. 多态
多态指的是一类事物有多种形态。动物有多种形态:人,狗,猪等
多态性指在不考虑实例类型的情况下使用实例;多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算; 字符串、列表、数字都能用上加上,站在+号的角度。
举例:动态性的---动态多态性
#多态:同一类事物的多种形态
##下面的多态性是动态多态性;
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')
class Cat(Animal): #新扩展的
def talk(self):
print('say miamiao')
#多态性:指的是可以在不考虑对象的类型的情况下而直接使用对象
peo1=People() #对象
dog1=Dog()
pig1=Pig()
cat1=Cat()
# peo1.talk() #它们的类型就是这些类,不考虑它们的类型,它们三个都是动物,我照着动物的标准直接用就行了,这就是多态性
# dog1.talk()
# pig1.talk()
def func(animal): #用户直接使用 ##定义一个统一的接口,同一个接口直接调用就可以了,这就是多态性的好处。
animal.talk()
func(peo1) #say hello
func(pig1) #say aoao
func(dog1) #say wangwang
func(cat1) #say miamiao
python本身就是支持多态性的,这么做的好处是什么呢?
1.增加了程序的灵活性
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)
2.增加了程序额可扩展性
通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用
鸭子类型
没有继承关系,我把你做的模拟的像父类;
#class File: ##这个父类没有意义
# def read(self):
# pass
# def write(self):
# pass
class Disk: #磁盘不是文件,直接继承就给人误解了
def read(self):
print('disk read')
def write(self):
print('disk write')
class Text:
def read(self):
print('text read')
def write(self):
print('text write')
# f = open()
# f.read()
# f.write()
disk=Disk() #得到磁盘的对象
text=Text() #得到Text的对象
disk.read() #大家用的都是同一套接口,不用管你是不是文件,我直接使用就可以了,降低了使用者的复杂度,这就叫鸭子类型
disk.write()
text.read()
text.write()
#类与类之间不用继承父类,只要把它们做的像就可以了
#序列类型:列表list,元祖tuple,字符串str, 同一种类型的三种形态
l=list([1,2,3])
t=tuple(('a','b'))
s=str('hello')
# print(l.__len__()) #3 打印统计长度 ,只要它是序列类型我就可以用len
# print(t.__len__()) #2
# print(s.__len__()) #5
# def len(obj): #本来就是内置函数,不用写了 统一的接口
# return obj.__len__()
print(len(l)) #3
print(len(t)) #2
print(len(s)) #5
python崇尚鸭子类型, 看着像鸭子就可以了,不用真的继承父类 用抽象类来约束子类,只要做的像就可以了。
4. 封装
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的);
其实这仅仅这是一种变形操作
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式 (注,下划线结尾的不是,只有下划线开头)
如何隐藏呢
class A: __x=1 ##实际上是这种形式 _A__x=1 #只是一种变形操作,在类定义阶段就发生了,执行之前 def __init__(self,name): self.__name=name #self._A__name=name def __foo(self): # _A__foo print('run foo') def bar(self): self.__foo() #self._A__foo() print('from bar') print(A.__dict__) #查看类的名称空间 #{'__module__': '__main__', '_A__x': 1, '__init__': <function A.__init__ at 0x000000000298CB70>, '_A__foo': <function A.__foo at 0x000000000298CBF8>, 'bar': <function A.bar at 0x000000000298CC80>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} # print(A.__x) #会报错 # print(A.__foo) #报错 a = A('egon') #a._A__foo()#只要就可以调用到了,变行之后的 #打印run foo #a._A__x #print(a.__name) #会报错,访问不到了,它原本是a.__dict__['__name'] #print(a.__dict__) #查看对象a的名称空间 #打印:{'_A__name': 'egon'}
a.bar() ###在类内部可以之间使用 打印:run foo from bar
class Foo: def __func(self): #_Foo__func print('from foo') class Bar(Foo): def __func(self): #_Bar__func print('from bar') # b=Bar() # b.func() #会报错的
这种变形的特点:
1、在类外部无法直接obj.__AttrName
2、在类内部是可以直接使用:obj.__AttrName (因为在访问时,在类内部就已经改成正确格式了)
3、子类无法覆盖父类__开头的属性(它俩根本就不是一个名字)
#特点3 class Foo: def __func(self): #_Foo__func print('from foo') class Bar(Foo): def __func(self): #_Bar__func print('from bar') # b=Bar() # b.func()
这种变形需要注意的问题是:
1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
# class B:
# __x=1
# def __init__(self,name):
# self.__name=name #self._B__name=name
#验证问题一:
# print(B._B__x) #1 python这个语法就是要隐藏,你就别这样做了,直接不隐藏就可以了
#验证问题二:
# B.__y=2 #类定义之后,没有变形
# print(B.__dict__)
#打印:{'__module__': '__main__', '_B__x': 1, '__init__': <function B.__init__ at 0x000000000297CB70>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None, '__y': 2}
# b=B('egon')
# print(b.__dict__) #{'_B__name': 'egon'} 查看变形了,在类定义阶段已经变形了
# b.__age=18 ##在定义之后再变形,不会变了
# print(b.__dict__) #{'_B__name': 'egon', '__age': 18}
# print(b.__age) ##输出打印出 18
#验证问题三:
# class A:
# def foo(self):
# print('A.foo')
#
# def bar(self):
# print('A.bar')
# self.foo() #实际上就是b.foo()了
#
# class B(A):
# def foo(self):
# print('B.foo')
#
# b=B()
# b.bar() 打印出 A.bar B.foo
#对比
# class A:
# def __foo(self): #_A__foo
# print('A.foo')
#
# def bar(self):
# print('A.bar')
# self.__foo() #实际上就是b._A__foo()了 我只调用自己类里边的,不到其他类里边找
#
# class B(A):
# def __foo(self): #_B__foo
# print('B.foo')
#
# b=B()
# b.bar() 打印:A.bar A.foo
封装的意义
封装数据属性、方法属性
#一:封装数据属性:明确的区分内外,控制外部对隐藏的属性的操作行为
class People:
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print('Name:<%s> Age:<%s>' %(self.__name,self.__age))
def set_info(self,name,age): #一个接口
if not isinstance(name,str): #附加一些操作逻辑, “谁是谁的实例,谁是谁的对象的意思”,name是不是str的实例
print('名字必须是字符串类型')
return
if not isinstance(age,int):
print('年龄必须是数字类型')
return
self.__name=name ##修改名字和年龄
self.__age=age
p=People('egon',18)
# p.tell_info() # Name:<egon> Age:<18>
# p.set_info('EGON',38) #用的别人给你开的接口给修改的名字和年龄
# p.tell_info() # Name:<EGON> Age:<38> 来查看的 ##对属性、年龄的修改不是直接修改,已经隐藏起来了不让你修改;
# 通过一个接口间接修改,接口上又附加了一些操作逻辑来控制你对属性的操作
# p.set_info(123,38) #名字必须是字符串类型
p.set_info('egon','38') #年龄必须是数字类型
p.tell_info() #Name:<egon> Age:<18>
#二、 封装方法:隔离复杂度
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() #直接调用取款功能,类外部的使用者不用关心其他的
#插卡 用户认证 输入存款金额 打印账单 取款
傻瓜相机,一按快门,里边自动调焦自动取景等那些功能就自动执行了。
封装与可扩展性
可扩展性体现在接口上,计算面积、体积,改变内部属性就可以了,用户只需要调用那一个接口
class Room:
def __init__(self,name,owner,weight,length,height):
self.name=name
self.owner=owner
self.__weight=weight
self.__length=length
self.__height=height #高度
def tell_area(self): #开一个接口,只访问面积或者体积就可以了;可扩展性体现在接口上,用户记住接口名就可以了
return self.__weight * self.__length * self.__height
r=Room('卫生间','alex',10,10,10)
# print(r.tell_area())
print(r.tell_area())#1000
property的使用
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) #返回值作为它的结果
p=People('egon',75,1.81)
# p.bmi=p.weight / (p.height ** 2) ##会报错,不能设置属性; 它原来就是这样算的
# print(p.bmi) #22.89307408198773 加上装饰器调用的时候就不用加bmi()了
#本质是触发了bmi这个方法的执行,拿到它的返回值打印就可以了 使用者感受不到
# print(p.bmi()) #会报错; 加括号,代表你要去调用这个函数,这个功能,去做什么、去干什么,一个动词,bmi应该是一个名词,一个属性。
# print(p.bmi) #22.89307408198773;这样子调用是最好的,要加一个装饰器
# p.height=1.82
# print(p.bmi) #22.6421929718633 会跟着传入参数而变
p.bmi=3333 #报错AttributeError: can't set attribute #bmi背后是一个方法,不能赋值
class People:
def __init__(self,name):
self.__name=name
@property
def name(self): #接口 ###查看
# print('getter')
return self.__name
@name.setter #这个name是经过上边的装饰器装饰过后的,再去调用setter ###修改
def name(self,val):
# print('setter',val)
if not isinstance(val,str):
print('名字必须是字符串类型')
return
self.__name=val
@name.deleter ###删除
def name(self):
print('deleter')
print('不允许删除')
p=People('egon')
# print(p.get_name()) #AttributeError: 'People' object has no attribute 'get_name' #可以拿到结果,最好是数据属性
# print(p.name) #egon 这样是最好的,需要加装饰器伪装下。
# p.name #对应的是一个方法,访问行为,触发的是查看; 不能赋值,要想赋值需要再定义一个name
#修改
# p.name='EGON' #触发修改
# p.name=123 ##名字必须是字符串类型
# print(p.name) #看看有没有改成功 EGON
del p.name #deleter 不能删除