面向对象的三大特征:封装、继承、多态
封装:隐藏内部实现 提供外部接口 易于修改,类的内部修改并不影响外部更好地保护类或对象的属性值的安全
(代码中:if __name__=='__main__'代表程序主入口是从这里进入的)
一、封装(接上篇部分)
<1>类属性:属于类的成员,对象共有的
修改方式: 类名.类属性=.../实例对象.__class__类属性
类属性与对象属性的比较使用:
类属性可以全部使用,而对象属性仅限于自己对象的使用
例:
class Person:
def __init__(self):
self.name='无名氏'
self.age=18
def show(self):
print('姓名:'+self.name+'年龄:',self
.age)
if '__main__'==__name__:
Person.phone='110' #类属性给参数赋值
zhangsan=Person()
zhangsan.name='张三'
zhangsan.age=20
zhangsan.gender='男' #对象属性 当两个对象需要区分时使用对象属性
zhangsan.show()
zhangsan.__class__.phone='123456' #当作为全局都能够访问时用类属性/给一个类赋值所以下面的李四电话显示与张三相同
print('zhangsan.gender'+zhangsan.gender)
print(zhangsan.__class__.phone)
lisi = Person()
lisi.name = '李四'
lisi.age = 20
lisi.show()
# lisi.__class__.phone = '520520' #取消注释会显示电话为520520
print(lisi.__class__.phone)
执行命令得
姓名:张三年龄: 20
zhangsan.gender男
123456
姓名:李四年龄: 20
123456
<2>类方法:用于拓展原来函数功能的一种函数
在方法上添加@classmethod 装饰器(类方法更倾向于工具方法,不需要重复创建对象.不是以对象的形式去调用)
用法:
@classmethod
def class_method(cls):
特点:可以通过类方法调用类属性,也可以通过对象调用类属性
class Person:
@classmethod
def sayHello(cls):
print('这个是一个Person类')
if '__main__'==__name__:
Person.sayHello()
执行命令得
这个是一个Person类
<3>静态方法:
方法前加@staticmethod,(静态方法可以加参数,静态方法既和类没关系,也和对象没关系,也可以通过类和对象调用)
一般情况下,静态方法会单独放在一个模块里
例:
class Person:
@staticmethod
def sayGood(say):#括号里可填写可不填写
print(say,'GOOD')
if '__main__'==__name__:
Person.sayGood(123)#上面的括号里填写了参数这里也必须填写内容否则报错
执行命令得
123 GOOD
二、继承
1.子类(父类)
概念:子类继承父类.子类可以使用父类的属性和方法,简化代码
当生成子类对象时,先初始化父类对象.如果父类有__init__()方法,并且有属性时,要通过子类的构造赋值.一个类可以有多个子类
初始化时,
在子类中调用父类的属性时,在__init__()方法中使用四种方法如下:
<1>super().__init__(属性)(调用有参)切记不能在属性前加self 否则报错
<2>self.属性=''一般不使用这类形式已在父类中存在
<3>父类.__init__(self,属性)(调用有参)切记必须在属性前加self否则报错
父类.__init__(self)(调用无参)
<4>super(子类,self).__init__(属性)
在子类中调用父类的方法时的用法:super().父类方法()
例:
class Pet:
def __init__(self,name,love,health):
'''
快速初始化自定义数据
:param name:
:param love:
:param health:
'''
self.name=name
self.love=love
self.health=health
def show(self):
print('宠物名'+self.name+' 健康值',self.health,'亲密度:',self.love)
class Dog(Pet):
def __init__(self,name,love,health):
# <1>super().__init__(name,love,health)
# <2>通过self.name='可乐'形式逐个代替
# <3>Pet.__init__(self,name,love,health)
super(Dog, self).__init__(name,love,health)
self.strain='二哈'
def eat(self):
print(self.strain+' 正在吃饭')
def showInfo(self):
super().show()
print('品种: ',self.strain)
if __name__=='__main__':
dog=Dog('可乐 ',100,0)
dog.eat()
dog.show()
dog.showInfo()
执行命令得
二哈 正在吃饭
宠物名可乐 健康值 0 亲密度: 100
宠物名可乐 健康值 0 亲密度: 100
品种: 二哈
练习
交通工具类:属性:名称 方法:行驶
子类:卡车,属性:载重,重写行驶的方法
子类:火车,属性:车箱个数,重写行驶的方法
class Traffic:
def __init__(self,name):
self.name=name
self.way='travel'
def run(self):
print('交通工具'+self.name+'正在行驶')
class Truck(Traffic):
def __init__(self,name,weight):
super(Truck, self).__init__(name)
self.weight=weight
def runInfo(self):
super().run()
print('卡车'+self.name+'载重'+self.weight+'飞驰')
class Train(Traffic):
def __init__(self,name,num):
super(Train, self).__init__(name)
self.num=num
def runInfo(self):
super().run()
print('火车'+self.name+'有'+str(self.num)+'节车厢')
if '__main__'==__name__:
kache=Truck('一汽','1吨')
kache.runInfo()
huoche=Train('和谐号',15)
huoche.runInfo()
执行命令得
交通工具一汽正在行驶
卡车一汽载重1吨飞驰
交通工具和谐号正在行驶
火车和谐号有15节车厢
总结:
当子类继承父类时,子类的构造方法应该包含父类和子类共同的属性,在子类的初始化__init__()方法中,将父类属性传递给父类,子类的属性赋值给子类(即name为父类属性传递给父类 weight和num为自己独特属性复制给自己)
方法重写:
子类继承父类时,子类的方法签名和父类一样,此时子类重写了父类的方法,当生成子类对象时,调用的格式子类重写得方法
2.继承的特性之一:传递性
三代继承:子类初始化方法需要祖父、父类及自己的属性,可以调用父类的初始化方法传参,可以重写父类的方法
构造顺序:祖父类-->父类-->自己
class Pet:
def __init__(self,name,love,health):
self.name=name
self.love=love
self.health=health
print('pet')
def show(self):
print('宠物名'+self.name+' 健康值',self.health,'亲密度:',self.love)
class Dog(Pet):
def __init__(self,name,love,health):
super(Dog, self).__init__(name,love,health)
self.strain='泰迪'
print('dog')
def eat(self):
print(self.strain+' 正在吃饭')
def showInfo(self):
super().show()
print('品种: ',self.strain)
class Taidi(Dog):
def __init__(self,name,love,health):
super(Taidi, self).__init__(name,love,health)
print('taidi')
if __name__=='__main__':
dog=Taidi('可乐 ',100,0)
dog.eat()
dog.showInfo()
执行命令得
pet
dog
taidi #pet. dog. taidi打印的先后顺序代表构造顺序
泰迪 正在吃饭
宠物名可乐 健康值 0 亲密度: 100
品种: 泰迪
类根源顶级父类-->继承object
被继承的类叫:基类 父类
继承的类叫:子类 派生类
方法重写:
如果子类重写的方法想调用父类方法时,在子类方法中写法:父类.方法(self)或super().父类方法()
3.私有属性、私有方法均不能在类外面被调用
多继承:类同时继成多个父类 如:class C(A,B),当有AB均有相同方法,而子类又重写时,调用水的方法(子类)如果子类没有重写方法,则调用那个父类的方法?(左侧,优先继承得类)
class Pet:
def __init__(self,name,love,health):
self.name=name
self.love=love
self.health=health
def show(self):
print('宠物名'+self.name+' 健康值',self.health,'亲密度:',self.love)
class Dog(Pet):
def __init__(self,name,love,health):
super(Dog, self).__init__(name,love,health)
self.strain='哈士奇'
def showInfo(self):
super().show()
print('品种: ',self.strain)
class lang:
def show(self):
print('我是一匹来自北方的狼')
class Hashiqi(lang,Dog):#Hashiqi中没有重写show时调用左侧的lang父类
def __init__(self,name,love,health):
super(Hashiqi, self).__init__(name,love,health)
if __name__=='__main__':
dog=Hashiqi('可乐 ',100,0)
dog.showInfo()
dog.show()
执行命令得
哈士奇 正在吃饭
宠物名可乐 健康值 0 亲密度: 100
品种: 哈士奇
我是一匹来自北方的狼
类名.mro(),可以看到所有父类,及搜索顺序
这就是动态语言的"鸭子类型",她并不要求严格的继承体系,一个对象只要"看起来像鸭子,走起路来像鸭子",那他就可以被看做鸭子
练习
(图书\DVD管理系统)
----DVD管理系统----:
1.查询所有DVD
2.增加DVD
3.借出DVD
4.归还DVD
5.退出
class DVD:
def __init__(self,name,price,state):
self.name=name
self.price=price
self.state=state
if '__main__'==__name__:
xiyouji=DVD('西游记',100,0)#0代表未借出 1 代表已借出
sanguo=DVD('三国',100,1)
shuihuzhuan=DVD('水浒传',100,0)
dvds={xiyouji.name:xiyouji,sanguo.name:sanguo,shuihuzhuan.name:shuihuzhuan}
while True:
print('1.查询所有DVD')
print('2.增加DVD')
print('3.借出DVD')
print('4.归还DVD')
print('5.退出')
num=int(input('请输入数字:'))
if num==1:
print('名称\t价格\t状态')
for key in dvds.keys():
if dvds.get(key).state==0:
print(key+' \t'+str(dvds.get(key).price)+' \t'+'未借出')
else:
print(key+' \t'+str(dvds.get(key).price)+' \t'+'已借出')
elif num==2:
name=input('请输入新的DVD:')
while name in dvds.keys():
name=input('此DVD已存在 请重新输入名字:')
price=int(input('请输入新DVD价格:'))
new_name=DVD(name,price,0)
dvds[name]=new_name
print('增加成功')
elif num==3:
name = input('请输入你所需要的DVD:')
while name not in dvds.keys():
name=input('此DVD不存在 请重新输入名字:')
if dvds.get(name).state==1:
print(name,'已借出')
else:
dvds.get(name).state=1
print('借出成功')
elif num==4:
name = input('请输入你所归还的DVD:')
while name not in dvds.keys():
name=input('此DVD不存在,请重新输入DVD名字')
if dvds.get(name).state==0:
print('此DVD未借出')
else:
dvds.get(name).state=0
date=int(input('请输入借书日期'))
print('扫一扫付款',int(date*int(dvds.get(name).price)),'元')
print('归还成功')
elif num==5:
print('欢迎使用')
break
4.__new__(cls):#用来创建对象,而且必须有返回值
return object.__new__(cls);
#return super(子类,cls).__new__(cls,args,kwargs)
可以用id(cls)看地址
当有属性时,需要在__new__()中也添加属性
单例模式: 该模式的主要目的是确保某一个类只有一个实例存在(实例等同于一个对象等同于一个内存地址等同于一块空间)
应用:资源共享
class singleton: __instace=None def __new__(cls, *args, **kwargs): print('__new__') if cls.__instace==None: #说明没有创建过 cls.__instace=object.__new__(cls) return cls.__instace else:#创建过 return cls.__instace def __init__(self): print('__init__') s1=singleton() s2=singleton() s3=singleton() print(id(s1)) print(id(s2)) print(id(s3))
执行命令得
__new__
__init__
__new__
__init__
__new__
__init__
77853488
77853488
77853488
三、多态
多态:多种形态 .不同的类对同一个消息/动作做出不同的解释执行不一样的代码
多态的3个必要条件:1.发生继承关系2.子类重写父类方法3.里氏代换原则
使用方式:以父类类型作为形参
<1>工厂类:在一个类中生成很多对象,简单工厂模式(Simple Factory Pattern)
是通过专门定义一个类来负责创建其他类的实例,被创建的事例同床都具有共同得父类 ,并且重写父类方法.
四则运算的父类,接受用户输入得数值 Operation
OperationAdd
OperationSub
OperationMul
OperationDiv
OperationFactroy(object)
# 四则运算得父类 ,接受用户输入的数值Operation
class Operation:#四则运算父类
def __init__(self,num1,num2):
self.num1=num1
self.num2=num2
def yunsuan(self):
pass
class OperationAdd(Operation):
def __init__(self,num1,num2):
super(OperationAdd, self).__init__(num1,num2)
def yunsuan(self):
return self.num1 + self.num2
class OperationSub(Operation):
def __init__(self,num1,num2):
super(OperationSub, self).__init__(num1,num2)
def yunsuan(self):
return self.num1 - self.num2
class OperationMul(Operation):
def __init__(self,num1,num2):
super(OperationMul, self).__init__(num1,num2)
def yunsuan(self):
return self.num1 * self.num2
class OperationDiv(Operation):
def __init__(self,num1,num2):
super(OperationDiv, self).__init__(num1,num2)
def yunsuan(self):
return self.num1 // self.num2
#工厂类 需要加减乘除时 工厂类来进行创建
class OperationFactory(object):
@classmethod#添加修饰器 类名.方法即可调用
def getOperation(self,sign,num1,num2):#获取运算符算的方法
if '+'.__eq__(sign):
return OperationAdd(num1,num2)
if '-'.__eq__(sign):
return OperationSub(num1,num2)
if '*'.__eq__(sign):
return OperationMul(num1,num2)
if '/'.__eq__(sign):
return OperationDiv(num1,num2)
if '__main__'==__name__:
num1=int(input('请输入第一个操作数:'))
num2 = int(input('请输入第二个操作数:'))
sign = input('请输入操作符:')
#返回与操作符对应的 运算对象
Operation=OperationFactory.getOperation(sign,num1,num2)
result=Operation.yunsuan()
print('运算结果:',result)
<2>isinstance()函数 判断是否继承关系或者某一个变量是否是某一个数据类型
#连接上面的加减乘除为例
'''
'''
if '__main__'==__name__:
num1=int(input('请输入第一个操作数:'))
num2 = int(input('请输入第二个操作数:'))
sign = input('请输入操作符:')
#返回与操作符对应的 运算对象
Oper=OperationFactory.getOperation(sign,num1,num2)
result=Oper.yunsuan()
print('运算结果:',result)
# 判断是否是Oper得数据类型
if isinstance(Oper,OperationAdd):
print('创建的是OperationAdd 类型的对象')
if isinstance(Oper,OperationSub):
print('创建的是OperationSub 类型的对象')
if isinstance(Oper,OperationMul):
print('创建的是OperationMul 类型的对象')
if isinstance(Oper,OperationDiv):
print('创建的是OperationDiv 类型的对象')
执行命令得
创建的是OperationAdd 类型的对象
创建的是OperationSub 类型的对象
创建的是OperationMul 类型的对象
创建的是OperationDiv 类型的对象
判断是否继承关系
'''
'''
print(isinstance(Oper,Operation))
print(isinstance(Oper,object))
执行命令得
True
True
<3>获取类名,对象名,属性名
getattr(类名/对象名) setattr(类名/对象名)
hasattr(类名/对象名,'属性名')只能判断不能获取
用法
'''
'''
#获取属性值
Oper=OperationFactory.getOperation('+',1,5)
print(Oper.__getattribute__('num2'))
#当所要的是属性值没有时
Oper.__setattr__('num3',7)
print(Oper.__getattribute__('num3'))
print(hasattr(Oper,'num3'))
执行命令得
5
7
True
<4>动态语言
为类动态添加函数 所有的对象都能用
用法:
#以加减乘除为例
class Operation:'''
class OperationAdd(Operation):'''
class OperationSub(Operation):'''
class OperationMul(Operation):'''
class OperationDiv(Operation):'''
class OperationFactory(object):
def setName(self,name):#动态添加函数(方法)
self.name=name
if '__main__'==__name__:
Oper=OperationFactory.getOperation('+',1,5)
Oper2=OperationFactory.getOperation('-',10,5)
Operation.setName=setName
Oper.setName('加法')
print('运算方法:'+Oper.name)
Oper2.setName('减法')
print('运算方法:'+Oper2.name)
执行命令得
运算方法:加法
运算方法:减法
<5>__call__直接在实例本身上调用
通过callable()函数,我们就可以判断一个对象是否是可调用的对象
class Person:
def __call__(self, *args, **kwargs):
print('执行了call函数')
if '__main__'.__eq__(__name__):
zhangsan=Person()
zhangsan()# 或者填写print(callable(zhangsan))
执行命令得
执行了call函数#True
<6>@property得使用
@property 注解优化getter setter-->@函数者.setter
@property装饰器是负责把一个方法变成属性调用,它的实现比较复杂,把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身创建了另一个装饰器@xx.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作
class Person:
@property
def Age(self):
return self.age
@Age.setter
def Age(self,age):
if age<0 or age>100:
self.age=18
else:
self.age=age
print('赋值了',self.age)
if '__main__'.__eq__(__name__):
zhangsan=Person()
zhangsan.Age=30
print(zhangsan.Age)
执行命令得
赋值了 30
30
<7>动态语言的灵活性,为了达到限制的目的,Python允许在定义class得时候定义一个特殊的__slots__变量.来限制该class实例能添加的属性
class student:
__slots__ = ('name','age')
stu=student()
stu.name='zhangsan'
stu.score=99
执行命令得
#报错AttributeError: 'student' object has no attribute 'score'
四、里氏替换原则
1.子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
2.子类中可以增加自己特有方法
3.当子类覆盖或实现父类得方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
4.当子类的方法实现父类得抽象方法时,方法的后置条件(及方法的返回值)要比父类更严格
始终记住里氏替换原则的最终目的就是保持父类的方法不被覆盖