面向對象的三大特徵:封裝、繼承、多態
封裝:隱藏內部實現 提供外部接口 易於修改,類的內部修改並不影響外部更好地保護類或對象的屬性值的安全
(代碼中: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.當子類的方法實現父類得抽象方法時,方法的後置條件(及方法的返回值)要比父類更嚴格
始終記住里氏替換原則的最終目的就是保持父類的方法不被覆蓋