@(python)[筆記]
目錄
目錄
前言
一、類與對象
1.1 什么是對象,什么是類
1.2 類有兩種作用
1.2.1屬性引用
1.2.2 實例化
1.2.3 查看類的屬性
1.3對象的作用
1.4 對象之間的交互
1.5類名稱空間與對象名稱空間
二、繼承與派生
2.1 什么是繼承
2.2 繼承與抽象
2.3 繼承與重用性
2.4 組合與重用性
2.4.1 繼承的方式
2.4.2 組合的方式
2.5 接口與歸一化設(shè)計
2.5.1什么是接口
2.5.2 為何要用接口
2.6 抽象類
2.6.1 什么是抽象類
2.6.2 為什么要有抽象類
2.6.3 在python中實現(xiàn)抽象類
2.7 繼承實現(xiàn)原理
2.7.1 繼承順序
2.7.2 繼承原理
2.8 子類中調(diào)用父類的方法
2.8.1 不用super()引發(fā)的慘案
三、多態(tài)與多態(tài)性
3.1 多態(tài)
3.2 多態(tài)性
3.3 多態(tài)性的好處
四、封裝
4.1 為什么要封裝
4.2 如何實現(xiàn)封裝
4.3 特性(property)
4.3.1 什么是特性property
4.3.2 為什么要用property
五、綁定方法與非綁定方法
5.1 @staticmethod
5.2 @classmethod
5.3 比較@staticmethod和@classmethod的區(qū)別
六、 面向?qū)ο蟮能浖_發(fā)
七、python中關(guān)于OOP的常用術(shù)語
前言
在python中,一切皆對象
面向?qū)ο蟮某绦蛟O(shè)計的核心就是對象;
面向?qū)ο蟮某绦蛟O(shè)計的優(yōu)缺點:
- 優(yōu)點:擴展性好;對某一個對象單獨修改,會立刻反映到整個體系中,如對游戲中一個人物參數(shù)的特征和技能修改都很容易;
- 缺點:可控性差,無法像面向過程的程序設(shè)計流水線式的可以很精準的預(yù)測問題的處理流程與結(jié)果,面向?qū)ο蟮某绦蛞涣块_始就由對象之間的交互解決問題,即使是上帝也無法預(yù)測最張結(jié)果。
應(yīng)用場景:
需求經(jīng)常變化的軟件,一般需求的變化都集中在用戶層,如互聯(lián)網(wǎng)應(yīng)用、企業(yè)內(nèi)部軟件、游戲等都是面向?qū)ο蟮某绦蛟O(shè)計大顯身手的好地方。
對于一個軟件質(zhì)量來說,面向?qū)ο蟮某绦蛟O(shè)計只是用來解決擴展性:
一、類與對象
1.1 什么是對象,什么是類
python中一切皆對象,且python3中統(tǒng)一了類與類型的概念,即類型就是類。
類是所有對象都具有的特征和技能和結(jié)合體;
在python中,用變量表示特征,用函數(shù)表示技能,因而類是變量與函數(shù)的結(jié)合體,對象是變量與方法(指向類的函數(shù))的結(jié)合體。
類的語法定義:
class 類名:
'''類的文檔描述'''
類體
類是數(shù)據(jù)與函數(shù)的結(jié)合,二者稱為類的 屬性
class Chinese: #定義一個類,類名叫Chinese,可以用它實例化出一個中國人;
country = 'China' #所有中國人的國籍都是China
def talk(self): #所有中國人都具有的技能說漢語
print("speak chinese")
#通過Chinese類實例化出兩個對象
alex = Chinese()
lisi = Chinese()
print(type(alex)) #<class '__main__.Chinese'>
print(alex.country) #China
print(lisi.country) #China
alex.talk() #speak chinese
lisi.talk() #speak chinese
通過同一個類實例化出來的對象擁有相同的屬性,如上面的例子,通過Chinese類實例化出的兩個人alex和lisi都擁有相同的國籍China和相同的技能說漢語
1.2 類有兩種作用
- 屬性引用
- 實例化
1.2.1屬性引用
語法:類名.屬性
class Chinese: #定義一個類,類名叫Chinese,可以用它實例化出一個中國人;
country = 'China' #所有中國人的國籍都是China
def talk(self): #所有中國人都具有的技能說漢語
print("speak chinese")
#通過Chinese類實例化出兩個對象
alex = Chinese()
lisi = Chinese()
print(type(alex)) #<class '__main__.Chinese'>
print(alex.country) #China,引用類的數(shù)據(jù)屬性,該屬性與所有對象(實例)共享
print(lisi.country) #China
alex.talk() #speak chinese ,引用類的函數(shù)屬性,也是共享
lisi.talk() #speak chinese
Chinese.writing = 'Chinese character' #增加屬性
del Chinese.writing #刪除屬性
1.2.2 實例化
__init__
與self
類名加括號就是實例化,會自動觸發(fā)內(nèi)部的__init__
函數(shù)的運行,可以用它來為每個實例定制自己獨有的特征
比如每個中國人的名字、年齡、性別是不一樣的,就可以這樣來寫:
class Chinese:
country = 'China'
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
#如實例化的對象是alex,則這里self.name就相當于alex.name
def talk(self):
print("%s can speak chinese, age: %s, sex: %s"%(self.name,self.age,self.sex))
#通過Chinese類實例化出兩個對象
alex = Chinese('alex',20,'male')
#實際上就是在執(zhí)行Chinese.__init__(alex,'alex',20,'male')
lisi = Chinese('lisi',26,'female')
alex.talk() #alex can speak chinese, age: 20, sex: male
lisi.talk() #lisi can speak chinese, age: 26, sex: female
self的作用是在實例化時自動將對象(也叫實例)本身傳給
__init__
函數(shù)的第一個參數(shù)。
1.2.3 查看類的屬性
print(Chinese.__dict__) #查看類的屬性,字典形式
print(alex.__dict__) #查看對象的屬性,不包括共有的屬性
#特殊的類屬性
類名.__name__# 類的名字(字符串)
類名.__doc__# 類的文檔字符串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類所有父類構(gòu)成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性
類名.__module__# 類定義所在的模塊
類名.__class__# 實例對應(yīng)的類(僅新式類中)
1.3對象的作用
對象是關(guān)于類而實際存在的一個例子,即實例
print(type(alex)) #<class '__main__.Chinese'>
print(isinstance(alex,Chinese)) #判斷alex是否是Chinese的一個實例
對象只有一種作用:屬性引用
print(alex.country) #China
print(alex.name) #alex
print(alex.age) #20
'''
查看實例屬性
同樣是dir和內(nèi)置__dict__兩種方式
特殊實例屬性
__class__
__dict__
....
'''
對象本身只有數(shù)據(jù)屬性,但是python的class機制會將類的函數(shù)綁定到對象上,稱為對象的綁定方法,綁定方法唯一綁定一個對象,同一個類的方法綁定到不同的對象上,屬于不同的方法,內(nèi)存地址都不會一樣
print(alex.talk) #對象的綁定方法talk本質(zhì)就是調(diào)用類的函數(shù)talk的功能,二者是一種綁定關(guān)系
print(lisi.talk)
#輸出:
<bound method Chinese.talk of <__main__.Chinese object at 0x00000000006F19B0>>
<bound method Chinese.talk of <__main__.Chinese object at 0x00000000006F19E8>>
#從輸出結(jié)果可以看出,是綁定方法,并且兩個內(nèi)存地址不一樣
注:對象的綁定方法的特別之外在于:obj,func()會把obj付給func的第一個參數(shù)。
1.4 對象之間的交互
例如:模擬英雄聯(lián)盟里的角色蓋倫(Garen)和 銳雯(Riven)之間相互攻擊對方。
#模擬英雄聯(lián)盟里的角色蓋倫(Garen)和 銳雯(Riven)之間相互攻擊對方
class Garen: #定義英雄蓋倫的類,不同的玩家可以用它實例出自己英雄;
camp='Demacia' #所有玩家的英雄(蓋倫)的陣營都是Demacia;
def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻擊力58...;
self.nickname=nickname #為自己的蓋倫起個別名;
self.aggressivity=aggressivity #英雄都有自己的攻擊力;
self.life_value=life_value #英雄都有自己的生命值;
def attack(self,enemy): #普通攻擊技能,enemy是敵人;
enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。
class Riven:
camp='Noxus' #所有玩家的英雄(銳雯)的陣營都是Noxus;
def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻擊力54;
self.nickname=nickname #為自己的銳雯起個別名;
self.aggressivity=aggressivity #英雄都有自己的攻擊力;
self.life_value=life_value #英雄都有自己的生命值;
def attack(self,enemy): #普通攻擊技能,enemy是敵人;
enemy.life_value-=self.aggressivity #根據(jù)自己的攻擊力,攻擊敵人就減掉敵人的生命值。
#實例化出一個Garen角色,叫egon
egon = Garen("亮亮")
#實例化出一個Riven角色,叫alex
alex = Riven("飛飛")
#分別查看egon和alex的原始生命值
print(egon.life_value) #455
print(alex.life_value) #414
#egon用自己的技能攻擊一次alex
egon.attack(alex) #發(fā)送了一條消息,稱為向egon發(fā)送了attack指令
#查看alex的剩余生命值
print(alex.life_value) #356 = 414(alex生命值) - 58(egon攻擊力)
#alex用自己的技能攻擊egon一次
alex.attack(egon)
#查看egon的剩余生命值
print(egon.life_value) #401 = 455(egon生命值) - 54(alex攻擊力)
1.5類名稱空間與對象名稱空間
創(chuàng)建一個類就會創(chuàng)建一個類的名稱空間,用來存儲類中定義的所有名字,這些名字稱為類的屬性。
而類有兩屬性:數(shù)據(jù)屬性和函數(shù)屬性
其中類的數(shù)據(jù)屬性是共享給所有對象的
print(id(egon.camp))
#本質(zhì)就是在引用類的camp屬性,二者id一樣
print(id(Garen.camp))
'''
#輸出:
7542688
7542688
'''
而類的函數(shù)屬性是綁定到所有對象的:
print(id(egon.attack))
print(id(Garen.attack))
#輸出:
4897608
7332456
#id并不一樣
'''
egon.attack就是在執(zhí)行Garen.attack的功能,python的class機制會將Garen的函數(shù)屬性attack綁定給egon,egon相當于拿到了一個指針,指向Garen類的attack功能;
險些之外egon.attack()還會將egon傳給attack的第一個參數(shù)self
'''
- 創(chuàng)建一個對象就會創(chuàng)建一個對象的名稱空間,存放對象的名字,稱為對象的屬性;
- 在obj.name會先從obj自己的名稱空間里找name,找不到則去類中找,類也找不到就找父類。。。最后都找不到就拋出異常。
練習:編寫一個學生類,產(chǎn)生一堆學生對象,要求有一個計數(shù)器(屬性),統(tǒng)計總共實例化了多少個對象
class stu:
count = 0
def __init__(self,name,age,zy,sex='male'):
stu.count +=1 #類的count
#count += 1 # 全局的count
#self.count +=1 # 對象自己的count
self.name = name
self.age = age
self.zhuanyei = zy
self.sex = sex
def study(self):
print("%s正在學習%s專業(yè)課程"%(self.name,self.zhuanyei))
p1 = stu('alex',28,'python')
p2 = stu('egon',25,'java')
print(stu.count) #輸出2
print(p1.count) #輸出2
二、繼承與派生
2.1 什么是繼承
繼承是一種創(chuàng)建新類的方式,在python中,新建的類可以繼承一個或多個父類,父類又可以稱為基類或超類,新建的類稱為派生類或子類。
在開發(fā)過程中,如果定義了一個類A,此后又想新建一個類B,但是類B的大部分內(nèi)容與類A的相同時,這就用到了類的繼承的概念。
通過繼承的方式新建類B,讓B繼承A,B會遺傳到A的所有屬性(數(shù)據(jù)屬性和函數(shù)屬性),實現(xiàn)代碼重用,減少代碼量。
python中類的繼承分為:
- 單繼承:子類只繼承一個父類
- 多繼承:子類繼承多個父類
class ParentClass1: #定義父類1
pass
class ParentClass2: #定義父類2
pass
class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass1
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
pass
查看子類繼承了哪些父類
>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個父類,__bases__則是查看所有繼承的父類
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
提示:如果沒有指定基類(或叫父類),python的類會默認繼承object
類,object
是所有python類的基類,它提供了一些常見方法(如__str__
)的實現(xiàn)。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
2.2 繼承與抽象
先抽象再繼承
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
- 1.將奧巴馬和梅西這兩個對象比較相似的部分抽取成類;
- 2.將人、豬、狗這三個類比較相似的部分抽取成類;
抽象最主要的作用是劃分類別,這樣可以隔離關(guān)注點,降低復雜度。
繼承是基于抽象的結(jié)果,通過編程語言去實現(xiàn)它,肯定是先經(jīng)歷抽象這個過程,才能通過繼承的方式去表達出抽象的結(jié)構(gòu)。
抽象只是分析和設(shè)計的過程一個動作或者說一個技巧,通過抽象可以得到類。
2.3 繼承與重用性
使用繼承來重用代碼比較好的例子
#==========================第一部分
例如:
貓可以:喵喵叫、吃、喝、拉、撒
狗可以:汪汪叫、吃、喝、拉、撒
如果我們要分別為貓和狗創(chuàng)建一個類,那么就需要為 貓 和 狗 實現(xiàn)他們所有的功能,偽代碼如下:
#貓和狗有大量相同的內(nèi)容
class 貓:
def 喵喵叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
class 狗:
def 汪汪叫(self):
print '喵喵叫'
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
#==========================第二部分
上述代碼不難看出,吃、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現(xiàn):
動物:吃、喝、拉、撒
貓:喵喵叫(貓繼承動物的功能)
狗:汪汪叫(狗繼承動物的功能)
偽代碼如下:
class 動物:
def 吃(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
# 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 貓(動物):
def 喵喵叫(self):
print '喵喵叫'
# 在類后面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 狗(動物):
def 汪汪叫(self):
print '喵喵叫'
#==========================第三部分
#繼承的代碼實現(xiàn)
class Animal:
def eat(self):
print("%s 吃 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
def shit(self):
print ("%s 拉 " %self.name)
def pee(self):
print ("%s 撒 " %self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '貓'
def cry(self):
print('喵喵叫')
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed='狗'
def cry(self):
print('汪汪叫')
# ######### 執(zhí)行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()
c2 = Cat('小黑的小白貓')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
我們可以將《1.4 對象之間的交互》示例的代碼通過繼承的方式,修改成如下這樣:
class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
def move_forward(self):
print('%s move forward' %self.nickname)
def move_backward(self):
print('%s move backward' %self.nickname)
def move_left(self):
print('%s move forward' %self.nickname)
def move_right(self):
print('%s move forward' %self.nickname)
def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass
class Riven(Hero):
pass
g1=Garen('草叢倫',100,300)
r1=Riven('銳雯雯',57,200)
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)
'''
運行結(jié)果
243
'''
注意:像g1.life_value之類的屬性引用,會先從實例中找life_value然后去類中找,然后再去父類中找...直到最頂級的父類。
當然子類也可以添加自己新的屬性或者在自己這里重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那么調(diào)用新增的屬性時,就以自己為準了。
class Riven(Hero):
camp='Noxus'
def attack(self,enemy): #在自己這里定義新的attack,不再使用父類的attack,且不會影響父類
print('from riven')
def fly(self): #在自己這里定義新的
print('%s is flying' %self.nickname)
在子類中,新建的重名的函數(shù)屬性,在編輯函數(shù)內(nèi)功能的時候,有可能需要重用父類中重名的那個函數(shù)功能,應(yīng)該是用調(diào)用普通函數(shù)的方式,即:類名.func(),此時就與調(diào)用普通函數(shù)無異了,因此即便是self參數(shù)也要為其傳值
class Riven(Hero):
camp = 'Noxus'
def __init__(self,nickname,aggressivity,life_value,skin):
Hero.__init__(self,nickname,aggressivity,life_value) #調(diào)用父類的屬性
self.skin = skin #添加自己的新屬性
def attack(self,enemy): #在自己這里定義新attack,不再使用父類的attack,且不會影響父類
Hero.attack(self,enemy) #調(diào)用父類的attack功能
def fly(self): #在自己這里定義新功能
print('%s is flying' %self.nickname)
r1 = Riven('銳雯雯',57,200,'黃皮膚')
r1.fly()
print(r1.skin)
'''
輸出:
銳雯雯 is flying
黃皮膚
''
2.4 組合與重用性
軟件征用的重要方式除了繼承之外還有另外一種方式,即:組合
組合是指在一個類中以另外一個類的對象作為數(shù)據(jù)屬性,稱為類的組全
組合示例:
class Equip: #武器裝備類
def fire(self):
print('Release Fire skill')
class Guido: #英雄Guido的類,一個英雄需要有裝備,因而需要組合Equip類
camp = 'Malaysia'
def __init__(self,nickname):
self.nickname = nickname
self.equip = Equip() #用Equip類產(chǎn)生一個裝備,賦值給實例的equip屬性
r1 = Guido('馬利亞')
r1.equip.fire() #可以使用組合的類產(chǎn)生的對象所持有的方法
'''
輸出:
Release Fire skill
'''
組合與繼承都是有效地利用已有類的資源的重要方式,但是二者的概念和使用場景不同.
2.4.1 繼承的方式
通過繼承建立了派生類(子類)與基類(父類)之間的關(guān)系,它是一種 “是” 的關(guān)系,比如狗是動物,貓是動物;
當類之間有很多相同的功能時,提取這些共同的功能做成基類(父類),用繼承比較好。比如:教授是老師
class Teacher:
def __init__(self,name,age):
self.name = name
self.age = age
def teach(self):
print("teaching")
class Professor(Teacher):
pass
p1 = Professor('egon',28)
p1.teach('python')
'''
輸出:
teaching
'''
2.4.2 組合的方式
用組合的方式建立了“類”與“組合的類”之間的關(guān)系,它是一種"有"的關(guān)系,比如教授有生日,教授教python課程
class BirthDate:
def __init__(self,year,month,day):
self.year = year
self.month = month
self.day = day
class Course:
def __init__(self,name,price,period):
self.name = name
self.price = price
self.period = period
class Teacher:
def __init__(self,name,age):
self.name = name
self.age = age
def teach(self,course):
print('teaching')
class Professor(Teacher):
def __init__(self,name,age,birth,course):
Teacher.__init__(self,name,age)
self.birth = birth
self.course = course
p1 = Professor('egon',28,
BirthDate('1960','6','9'),
Course("python",'20000','7 months'))
#將BirthDate('1960','6','9')傳給birth
#將Course("python",'20000','7 months')傳給course
print(p1.birth.year,p1.birth.month,p1.birth.day)
print(p1.course.name,p1.course.price,p1.course.period)
'''
輸出:
1960 6 9
python 20000 7 months
'''
總結(jié):當類之間有顯著不同,并且較小的類是較大的類所需要的組件時,用組合比較好。
2.5 接口與歸一化設(shè)計
2.5.1什么是接口
- 接口是組功能的集合
- 接口的功能用于交互,所有的功能都是公共的(即:別的對象可操控)
- 接口只定義函數(shù),但不涉及函數(shù)的實現(xiàn)
繼承的兩種用途:
- 繼承基類(父類)的方法,并且做出自己的改變或擴展(代碼重用);
- 聲明某個子類兼容某個父類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數(shù)名)且并未實現(xiàn)接口的功能,子類接口類,并且實現(xiàn)接口中的功能;
class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關(guān)鍵字來定義一個接口。
def read(self): #定接口函數(shù)read
pass
def write(self): #定義接口函數(shù)write
pass
class Txt(Interface): #文本,具體實現(xiàn)read和write
def read(self):
print('文本數(shù)據(jù)的讀取方法')
def write(self):
print('文本數(shù)據(jù)的讀取方法')
class Sata(Interface): #磁盤,具體實現(xiàn)read和write
def read(self):
print('硬盤數(shù)據(jù)的讀取方法')
def write(self):
print('硬盤數(shù)據(jù)的讀取方法')
class Process(Interface):
def read(self):
print('進程數(shù)據(jù)的讀取方法')
def write(self):
print('進程數(shù)據(jù)的讀取方法')
實踐中,繼承的第一種含義意義并不是很大,甚至常常是有害的,因為它會使得子類與父類出現(xiàn)強耦合。
繼承的第二種含義非常重要,它又叫接口繼承。
接口繼承實質(zhì)上是要求做出一個良好的抽象,這個抽象規(guī)定了一個兼容接口,使得外部調(diào)用者無需關(guān)心具體細節(jié),可一視同仁的處理實現(xiàn)了特定接口的所有對象 ------這在程序設(shè)計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區(qū)分的處理所有接口兼容的對象集合,就像Linux中一切皆文件的理念一樣,所有東西都可以當成文件來處理, 不必關(guān)心它是內(nèi)存、磁盤、網(wǎng)絡(luò)、還是屏幕(當然,對底層設(shè)計者,也可以區(qū)分出字符設(shè)備和塊設(shè)備)。
2.5.2 為何要用接口
接口提取了一群類共同的函數(shù),可以把接口當做一個函數(shù)的集合,然后讓子類去實現(xiàn)接口中的函數(shù)。
這么做的意義在于歸一化。歸一化就是只要是基于同一個接口實現(xiàn)的類,那么所有的這些類產(chǎn)生的對象在使用時,從用法上來說都一樣。
歸一化讓使用者無需關(guān)心對象的類是什么,只需要知道這此對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
2.6 抽象類
2.6.1 什么是抽象類
與JAVA一樣,python也有抽象類的概念,但是同樣需要借助模塊實現(xiàn),抽象類是一個特殊的類,它的特殊之處在于只能被繼承,不能被實例化。
2.6.2 為什么要有抽象類
如果說類是從一堆對象中抽取相同的內(nèi)容而來的,那么抽象類就是從一堆類中抽取相同的內(nèi)容而來的,內(nèi)容包括數(shù)據(jù)屬性和函數(shù)屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內(nèi)容就是水果這個抽象的類,你吃水果時,要么是吃一個具體的香蕉,要么是吃一個具體的桃子。。。。。。你永遠無法吃到一個叫做水果的東西。
從設(shè)計角度去看,如果類是從現(xiàn)實對象抽象而來的,那么抽象類就是基于類抽象而來的。
從實現(xiàn)角度來看,抽象類與普通類的不同之處在于:抽象類中只能有抽象方法(沒有實現(xiàn)功能),該類不能被實例化,只能被繼承,且子類必須實現(xiàn)抽象方法。這一點與接口有點類似,但其實是不同的,即將揭曉答案
2.6.3 在python中實現(xiàn)抽象類
在python中實現(xiàn)抽象類需要利用abc模塊
#一切皆文件
import abc #利用abc模塊實現(xiàn)抽象類
class All_file(metaclass=abc.ABCMeta): #抽象類,括號中必須這樣寫
all_type='file'
@abc.abstractmethod #定義抽象方法,無需實現(xiàn)功能
def read(self):
'子類必須定義讀功能'
pass
@abc.abstractmethod #定義抽象方法,無需實現(xiàn)功能
def write(self):
'子類必須定義寫功能'
pass
# class Txt(All_file):
# pass
#
# t1=Txt() #報錯,子類沒有定義抽象方法
class Txt(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('文本數(shù)據(jù)的讀取方法')
def write(self):
print('文本數(shù)據(jù)的讀取方法')
class Sata(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('硬盤數(shù)據(jù)的讀取方法')
def write(self):
print('硬盤數(shù)據(jù)的讀取方法')
class Process(All_file): #子類繼承抽象類,但是必須定義read和write方法
def read(self):
print('進程數(shù)據(jù)的讀取方法')
def write(self):
print('進程數(shù)據(jù)的讀取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
#這樣大家都是被歸一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
抽象類的本質(zhì)還是類,指的是一組類的相似性,包括數(shù)據(jù)屬性(如all_type)和函數(shù)屬性(如read、write),而接口只強調(diào)函數(shù)屬性的相似性;
抽象類是一個介于類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現(xiàn)歸一化設(shè)計。
2.7 繼承實現(xiàn)原理
2.7.1 繼承順序
- Python的類可以繼承多個類,而Java和C#中則只能繼承一個類;
- Python的類如果繼承了多個類,那么其尋找方法的方式有兩種,分別是:深度優(yōu)先 和 廣度優(yōu)先
- 深度優(yōu)先:如上圖,A類繼承B類和C類,B類又繼承D類,那么查找順序是:A -> B -> D -> C
- 廣度優(yōu)先: 如上圖,A類繼承B類和C類,B類又繼承D類,那么查找順序是:A -> B -> C -> D
- 當類是經(jīng)典類時,多繼承情況下,會按照深度優(yōu)先方式查找
- 當類是新式類時,多繼承情況下,會按照廣度優(yōu)先方式查找
經(jīng)典類和新式類,從字面上可以看出一個老一個新,新的必然包含了更多的功能,也是之后推薦的寫法,從寫法上區(qū)分的話,如果當前類或者父類繼承了object類,那么該類便是新式類,否則便是經(jīng)典類。
注意:只有Python2中才有新式類和經(jīng)典類之分,Python3中統(tǒng)一都是新式類。
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__) #只有新式才有這個屬性可以查看線性列表,經(jīng)典類沒有這個屬性
#新式類繼承順序:F->D->B->E->C->A
#經(jīng)典類繼承順序:F->D->B->A->E->C
#python3中統(tǒng)一都是新式類
#pyhon2中才分新式類與經(jīng)典類
2.7.2 繼承原理
python到底是如何實現(xiàn)繼承的?
對于你定義的每一個類,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'>]
為了實現(xiàn)繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類為止。
而這個MRO列表的構(gòu)造是通過一個C3線性化算法實現(xiàn)的,不用去深究這個算法的數(shù)學原理,它實際上就是合并所有父類的MRO列表并遵循以下三條準則:
- 子類會先于父類被檢查;
- 多個父類會根據(jù)它們在列表中的順序被檢查;
- 如果對下一個類存在兩個合法的選擇,則選擇第一個父類。
2.8 子類中調(diào)用父類的方法
子類繼承了父類的方法,然后想進行修改,注意了是基于原有的基礎(chǔ)上修改,那么就需要在子類中調(diào)用父類的方法。
方法一: 父類名.父類方法()
class School: #定義一個學校類
school_name = "oldboy"
def __init__(self,course,price,):
self.course = course
self.price = price
def foo(self):
print("開學啦。。。")
class Students(School):
def __init__(self,course,price,name,age):
School.__init__(self,course,price)
#這里調(diào)用父類的__init__方法時,必須把self傳進去
self.name = name
self.age = age
def foo(self):
print("{_name}報了{_school}學校的{_course}專業(yè)".format(
_name = self.name,
_school = self.school_name,
_course = self.course
))
School.foo(self) #調(diào)用父類的方法,必須傳入self。否則報錯
stu_1 = Students("Python","11000","egon","18")
stu_1.foo()
'''
輸出:
egon報了oldboy學校的Python專業(yè)
開學啦。。。
'''
方法二:利用super()
函數(shù)
class School: #定義一個學校類
school_name = "oldboy"
def __init__(self,course,price):
self.course = course
self.price = price
def foo(self):
print("開學啦。。。")
class Students(School): #定義一個學生類,并繼承學校類
def __init__(self,course,price,name,age):
super().__init__(course,price)
#利用super()函數(shù),調(diào)用父類的__init__方法,就不需要再將self傳入了;
#在python3中super()就等同于python2中的super(Students,self),
#super(Students,self)就相當于實例本身
self.name = name
self.age = age
def foo(self):
print("{_name}報了{_school}學校的{_course}專業(yè)".format(
_name = self.name,
_school = self.school_name,
_course = self.course
))
super().foo() #利用super()函數(shù)調(diào)用父類的foo()方法,也無需再傳入self參數(shù)了
stu_1 = Students("Python","11000","egon","18")
stu_1.foo()
'''
輸出:
egon報了oldboy學校的Python專業(yè)
開學啦。。。
'''
2.8.1 不用super()引發(fā)的慘案
不用super()去調(diào)用父類的方法代碼示例:
#每個類中都繼承了且重寫了父類的方法
class A:
def __init__(self):
print('A的構(gòu)造方法')
class B(A):
def __init__(self):
print('B的構(gòu)造方法')
A.__init__(self)
class C(A):
def __init__(self):
print('C的構(gòu)造方法')
A.__init__(self)
class D(B,C):
def __init__(self):
print('D的構(gòu)造方法')
B.__init__(self)
C.__init__(self)
pass
f1=D()
print(D.__mro__) #python2中沒有這個屬性
'''
輸出:
D的構(gòu)造方法
B的構(gòu)造方法
A的構(gòu)造方法
C的構(gòu)造方法
A的構(gòu)造方法
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
'''
可以看出以上查找順序為:D -> B -> A -> C ->A,會出現(xiàn)重復查找的現(xiàn)象,并且出現(xiàn)了深度優(yōu)先的查找原則。
使用super()調(diào)用父親的方法的代碼示例:
#每個類中都繼承了且重寫了父類的方法
class A:
def __init__(self):
print('A的構(gòu)造方法')
class B(A):
def __init__(self):
print('B的構(gòu)造方法')
super(B,self).__init__()
class C(A):
def __init__(self):
print('C的構(gòu)造方法')
super(C,self).__init__()
class D(B,C):
def __init__(self):
print('D的構(gòu)造方法')
super(D,self).__init__()
f1=D()
print(D.__mro__) #python2中沒有這個屬性
'''
輸出結(jié)果:
D的構(gòu)造方法
B的構(gòu)造方法
C的構(gòu)造方法
A的構(gòu)造方法
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
''
總結(jié):只要重新定義的方法統(tǒng)一使用
super()
并只調(diào)用它一次,那么控制流最終會遍歷完整個MRO列表,每個方法也只會被調(diào)用一次;
注意:使用super()
調(diào)用的所有屬性,都是從MRO列表當前的位置往后找,千萬不要通過看代碼去找繼承關(guān)系,一定要看MRO列表。
三、多態(tài)與多態(tài)性
3.1 多態(tài)
多態(tài)指的是一類事物有多種形態(tài)。一個抽象類有多個子類,因而多態(tài)的概念依賴于繼承。
例如:
1、 序列類型有多種形態(tài):字符串、列表、元組
2、 動物有多種形態(tài):貓、狗、豬
import abc
class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
@abc.abstractmethod #定義抽象類
def talk(self):
pass
class Cat(Animal): #動物的形態(tài)之一:人
def talk(self):
print('say 喵喵喵')
class Dog(Animal): #動物的形態(tài)之二:狗
def talk(self):
print('say 汪汪汪')
class Pig(Animal): #動物的形態(tài)之三:豬
def talk(self):
print('say 哼哼哼')
3、 文件有多種形狀:文本文件、可執(zhí)行文件
import abc
class File(metaclass=abc.ABCMeta): #同一類事物:文件
@abc.abstractmethod #定義抽象類
def click(self):
pass
class Text(File): #文件的形態(tài)之一:文本文件
def click(self):
print('open file')
class ExeFile(File): #文件的形態(tài)之二:可執(zhí)行文件
def click(self):
print('execute file')
3.2 多態(tài)性
多態(tài)性是指具有不同功能的函數(shù)可以使用相同的函數(shù)名,這樣就可以用一個函數(shù)名調(diào)用不同功能的函數(shù)。
在面向?qū)ο蠓椒ㄖ幸话闶沁@樣表述多態(tài)性:向不同的對象發(fā)送同一條消息(!!!obj.func():是調(diào)用了obj的方法func,又稱為向obj發(fā)送了一條消息func),不同的對象在接收時會產(chǎn)生不同的行為(即方法)。也就是說,每個對象可以用自己的方式去響應(yīng)共同的消息,這里所謂的消息,就是調(diào)用函數(shù),不同的行為就是指不同的實現(xiàn),即執(zhí)行不同的函數(shù)。
比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執(zhí)行的是下班操作,學生執(zhí)行的是放學操作,雖然二者消息一樣,但是執(zhí)行的效果不同。
如下示例:
class animal:
def talk(self):
print("正在叫")
class people(animal):
def talk(self):
print("say hello")
class pig(animal):
def talk(self):
print("哼哼哼")
class dog(animal):
def talk(self):
print("汪汪汪")
peo = people()
pig1 = pig()
dog1 = dog()
def func(obj): #參數(shù)obj就是多態(tài)性的具體表現(xiàn)形式
obj.talk()
func(peo) #say hello
func(pig1) #哼哼哼
func(dog1) #汪汪汪
#每種動物都有talk功能,而每種動物實例執(zhí)行的talk功能,得到的結(jié)果并不相同
綜上,也可以說,多態(tài)性是“一個接口(即上例中的func()
函數(shù)),多種實現(xiàn)(如obj.talk()
)”
3.3 多態(tài)性的好處
增加了程序的靈活性
以不變應(yīng)萬變,不論對象怎么變化,使用者都是用同一種形式去調(diào)用,如func(peo)
,func(pig1)
,func(dog1)
。增加了程序的可擴展性
通過繼承animal
類創(chuàng)建了一個新的類,使用者無需更改自己的代碼,還是用func(animal)去調(diào)用
>>> class Cat(Animal): #屬于動物的另外一種形態(tài):貓
... def talk(self):
... print('say miao')
...
>>> def func(animal): #對于使用者來說,自己的代碼根本無需改動
... animal.talk()
...
>>> cat1=Cat() #實例出一只貓
>>> func(cat1) #甚至連調(diào)用方式也無需改變,就能調(diào)用貓的talk功能
say miao
'''
這樣我們新增了一個形態(tài)Cat,由Cat類產(chǎn)生的實例cat1,使用者可以在完全不需要修改自己代碼的情況下。使用和人、狗、豬一樣的方式調(diào)用cat1的talk方法,即func(cat1)
'''
四、封裝
- 數(shù)據(jù)封裝
- 方法封裝
4.1 為什么要封裝
- 封裝數(shù)據(jù)的主要原因是:保護隱私
- 封裝方法的主要原因是:隔離復雜度
4.2 如何實現(xiàn)封裝
在類中把某些屬性和方法隱藏起來(或者說定義成私有的),只在類的內(nèi)部使用,外部無法直接訪問這些隱藏的屬性,或者留下少量接口(函數(shù))供外部訪問。
在python中用雙下劃線的方式實現(xiàn)隱藏屬性(設(shè)置成私有的)
類中所有雙下劃線開頭的名稱如__name
都會自動變形成:_類名__name
這樣的形式。
注意:類名前面是一個下劃線,name前面是雙下劃線。
class A:
__N=0 #類的數(shù)據(jù)屬性就應(yīng)該是共享的,但是語法上是可以把類的數(shù)據(jù)屬性設(shè)置成私有的如__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() #只有在類內(nèi)部才可以直接通過__foo的形式訪問到.
print(A.__dict__)
#輸出:
'_A__foo': <function A.__foo at 0x000000000118E268>, '__dict__': <attribute '__dict__' of 'A' objects>, '__module__': '__main__', '__init__': <function A.__init__ at 0x000000000118E1E0>, 'bar': <function A.bar at 0x000000000118E2F0>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '_A__N': 0, '__doc__': None}
如下示例,隱藏name和age,讓外部不能直接訪問name和age,只能通過tell_info()
和set_info()
這兩個接口來訪問,set_into()
附加類型檢查邏輯
class Teacher:
def __init__(self,name,age):
self.__name=name
self.__age=age
def tell_info(self):
print('姓名:%s,年齡:%s' %(self.__name,self.__age))
def set_info(self,name,age):
if not isinstance(name,str):
raise TypeError('姓名必須是字符串類型')
if not isinstance(age,int):
raise TypeError('年齡必須是整型')
self.__name=name
self.__age=age
t=Teacher('egon',18)
t.tell_info()
t.set_info('egon',19)
t.tell_info()
這種自動變形的特點:
- 類中定義的
__x
只能在類的內(nèi)部使用,如self.__x
,引用的就是變形的結(jié)果。 - 這種變形其實只是針對外部的變形,在外部是無法通過
__x
這個名字訪問到的。 - 在子類定義的
__x
不會覆蓋在父類定義的__x
,因為子類中的變形成了:_子類名__x
,而父類中變形成了:_父類名__x
,所以雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
注意:對于這種封裝,我們需要在類中定義一個函數(shù)(接口函數(shù))在它內(nèi)部訪問被隱藏的屬性,然后外部就可以使用了。也可以用
property
來解決,下面即將介紹。
這種變形需要注意的問題:
1、 這種機制也并沒有真正意義上限制我們從外部直接訪問隱藏性,知道了類名和屬性名就可以拼出名字:_類名__屬性
,然后就可以通過拼出的名字直接訪問了,如a._A__N
class A:
__N = 10
def foo(self):
pass
a=A()
print(a._A__N)
#輸出:10
2、 變形的過程只在類的定義時發(fā)生一次,在定義后的賦值操作不會再變形
a.__M = 1
print(a.__M)
#輸出:1
3、 在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的(隱藏的)。
#正常情況
>>> class A:
... def fa(self):
... print('from A')
... def test(self):
... self.fa()
...
>>> class B(A):
... def fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from B
#把fa定義成私有的,即__fa
>>> class A:
... def __fa(self): #在定義時就變形為_A__fa
... print('from A')
... def test(self):
... self.__fa() #只會與自己所在的類為準,即調(diào)用_A__fa
...
>>> class B(A):
... def __fa(self):
... print('from B')
...
>>> b=B()
>>> b.test()
from A
注:python并不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時不能被導入,但是你from module import _private_module依然是可以導入的;
其實很多時候你去調(diào)用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內(nèi)部調(diào)用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點點。
python要想與其他編程語言一樣,嚴格控制屬性的訪問權(quán)限,只能借助內(nèi)置方法如
__getattr__
,詳見面向?qū)ο筮M階。
4.3 特性(property)
4.3.1 什么是特性property
property
是一種特殊的屬性,訪問它時會執(zhí)行一段功能(函數(shù)),然后返回值
例一:BMI指數(shù),是通過身高和體重計算得來的,但很明顯BMI聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,將更便于理解。
例:成人的BMI數(shù)值
過輕:低于18.5
正常:18.5-23.9
過重:24-27
肥胖:28-32
非常肥胖, 高于32
體質(zhì)指數(shù)(BMI)=體重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
class people:
def __init__(self,name,weight,height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
if not isinstance(self.weight,int):
raise TypeError("必須傳入整型參數(shù)")
if not isinstance(self.height,float):
raise TypeError("必須傳入浮點型參數(shù)")
return self.weight / (self.height**2)
p1 = people("lisi",65,1.70)
print(p1.bmi)
#在`bmi()`上方加上`@property`這個裝飾器后,再調(diào)用`bmi()`方法時就不用再加括號了,看起來就像是引用屬性
例二:圓的周長和面積聽起來也像是屬性,但顯示它們都是通過圓的半徑計算得來
import math
#導入meth模塊用以導入圓周率 "派" 的值
class Circle:
def __init__(self,radius): #radius表示圓的半徑
self.radius = radius
@property
def area(self): #不能加入多個參數(shù)
return math.pi * (self.radius**2) #計算面積
@property
def perimeter(self):
return 2*math.pi*self.radius #計算周長
c = Circle(10)
print(c.area) #可以向訪問數(shù)據(jù)屬性一樣去訪問area,會觸發(fā)一個函數(shù)的執(zhí)行,動態(tài)計算出一個值
print(c.perimeter) #同上
'''
輸出結(jié)果:
314.1592653589793
62.83185307179586
'''
注意:此時的特性
area
和perimeter
不能被賦,也不能傳入?yún)?shù)。
c.area=3 #為特性area賦值
'''
拋出異常:
AttributeError: can't set attribute
'''
4.3.2 為什么要用property
將一個類的函數(shù)定義成特性以后,對象再去使用的時候obj.name
根本無法察覺自己的name是執(zhí)行了一個函數(shù)然后計算出來的,這種特性的使用方式遵循了統(tǒng)一訪問的原則。
除此之外,面向?qū)ο蟮姆庋b有三種方式:
- public 這種其實就是不封裝,是對外公開的;
- protected 這種封裝方式對外不公開,但對子類公開;
- private 這種封裝對誰都不公開。
python并沒有在語法上把上面三種方式內(nèi)建到自己的class機制中,在C++ 里一般會將所有的數(shù)據(jù)都設(shè)置為私有的,然后提供 set 和 get 方法去設(shè)置和獲取,在python中通過property
方法可以實現(xiàn)設(shè)置、獲取、刪除:
利用@property
特性裝飾一個函數(shù)屬性,然后利用@name.setter
來重新設(shè)定值,利用@name.deleter
刪除設(shè)定的值。注意:這里的name
是被@property
裝飾的函數(shù)名。
代碼示例如下:
class Foo:
def __init__(self,val,permission=False):
self.__NAME=val #將所有的數(shù)據(jù)屬性都隱藏起來
self.permission = permission
@property #相當于定義獲取
def name(self):
return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)
@name.setter #相當于定義設(shè)置,這里的name是引用上面的name
def name(self,value):
if not isinstance(value,str): #在設(shè)定值之前進行類型檢查
raise TypeError('%s must be str' %value)
self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME
@name.deleter #相當于定義刪除,可以通過del去刪除屬性
def name(self):
if not self.permission: #加一個判斷,當有權(quán)限刪除時,才能刪除
raise TypeError('Can not delete')
del self.__NAME
f=Foo('egon')
print(f.name) #輸出egon
f.name="alex" #設(shè)定一個新的name,如果不是字符串,則會拋出異常'TypeError: 10 must be str'
print(f.name) #輸出alex
# 直接del f.name 會拋出異常'TypeError: Can not delete'
#先設(shè)定權(quán)限
f.permission = True
print(f.permission) #輸出True
#再次刪除,就不會再拋出異常了
del f.name #當permission=True時,可以正常刪除
# print(f.name) #刪除后,就不能再訪問到`f.name`,所以會報異常
property
非裝飾器用法:
class Foo:
def __init__(self,val):
self.__NAME=val #將所有的數(shù)據(jù)屬性都隱藏起來
def getname(self):
return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)
def setname(self,value):
if not isinstance(value,str): #在設(shè)定值之前進行類型檢查
raise TypeError('%s must be str' %value)
self.__NAME=value #通過類型檢查后,將值value存放到真實的位置self.__NAME
def delname(self):
raise TypeError('Can not delete')
name=property(getname,setname,delname) #不如裝飾器的方式清晰
一種property的古老用法
五、綁定方法與非綁定方法
類中定義的函數(shù)分成兩厭煩:
-
綁定方法(誰來調(diào)用就自動將它本身當作第一個參數(shù)傳入),綁定方法還分為綁定到類的方法和綁定到對象的方法;
1.1 綁定到類的方法:用
@classmethod
裝飾器裝飾的方法,是為類量身定制的,類名.boud_method()
,會自動將類名當作第一個參數(shù)傳入。(其實對象也可以調(diào)用,但是仍然會將類當作第一個參數(shù)傳入)1.2 綁定到對象的方法:沒有被任何裝飾裝飾的方法,為對象量身定制,
對象.boud_method()
自動將對象當作第一個參數(shù)傳入。(綁定到對象的方法實際上就是類內(nèi)部的函數(shù),類也可以調(diào)用,但是必須按照函數(shù)的規(guī)則來調(diào)用,沒有自動傳值的功能。)
-
非綁定方法:用
@staticmethod
裝飾器裝飾的方法,不與類或?qū)ο蠼壎ǎ惡蛯ο蠖伎梢哉{(diào)用,但是都沒有自動傳值的功能,就是一個普通工具而已。
注意:與綁定到對象方法區(qū)分開,在類中直接定義的函數(shù),沒有被任何裝飾器裝飾的,都是綁定到對象的方法,可不是普通函數(shù),對象調(diào)用該方法會自動傳值,而staticmethod裝飾的方法,不管誰來調(diào)用,都沒有自動傳值一說
5.1 @staticmethod
@staticmethod
裝飾的方法不與類或?qū)ο蠼壎ǎl都可以調(diào)用,沒有自動傳值的功能,python為我們內(nèi)置了函數(shù)@staticmethod
來把類中的函數(shù)定義成靜態(tài)方法:
如下:定義一個MySQL類,為每個MySQL類的實例生成一個id(要求唯一),這個id類和對象都能調(diào)用;
import hashlib
import time
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
self.id = self.create_id()
@staticmethod
def create_id(): #就是一個普通工具
m = hashlib.md5(str(time.clock()).encode('utf-8'))
#time.clock()是cpu時間,每時每刻都不一樣
#所以利用cpu時間生成的md5值是唯一的。
return m.hexdigest()
'''
print(MySQL.create_id) #<function MySQL.create_id at 0x0000000001E6B9D8> #查看結(jié)果為普通函數(shù)
conn=MySQL('127.0.0.1',3306)
print(conn.create_id) #<function MySQL.create_id at 0x00000000026FB9D8> #查看結(jié)果為普通函數(shù)
'''
5.2 @classmethod
@classmethod
裝飾的方法是給類調(diào)用的,即綁定到類,類在使用時會將類本身當作參數(shù)傳給類方法的第一個參數(shù)(即使是對象來調(diào)用也會將類當作第一個參數(shù)傳入)。python為我們內(nèi)置了函數(shù)@classmethod
來把類中的函數(shù)定義成類方法
示例如下:
除了實例化時自己傳入host和port,我們還想通過配置文件來獲取host和port,生成另外一個實例
#settings.py
HOST='127.0.0.1'
PORT=3306
import settings
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
def connection(self):
print("Host:%s , port:%s,connecting ..."%(self.host,self.port))
@classmethod
def from_conf(cls):
return cls(settings.HOST,settings.PORT)
#當類來調(diào)用時,會自動將類傳入
#這里的cls(settings.HOST,settings.PORT)就等同于mysql(settings.HOST,settings.PORT)
#return返回的實際上是一個實例化的對象
conn1 = mysql("192.168.1.87","3306")
conn1.connection()
conn2 = mysql.from_conf()
conn2.connection()
'''
輸出:
Host:192.168.1.87 , port:3306,connecting ...
Host:127.0.0.1 , port:3306,connecting ...
'''
conn3 = conn1.from_conf()
# 對象也可以調(diào)用,但是默認傳的第一個參數(shù)仍然是類
conn3.connection()
'''
輸出:
Host:127.0.0.1 , port:3306,connecting ...
'''
5.3 比較@staticmethod
和@classmethod
的區(qū)別
先來看看__str__
內(nèi)置方法的用法
l = list([1,2,3])
print(l) #打印的是[1, 2, 3]
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
conn = mysql("127.0.0.1","3306")
print(conn) #打印的是<__main__.mysql object at 0x0000000000701898>
從以上兩段代碼可以看到,我們自己定義的類,生成的對象直接被打印時,打印的是對象的內(nèi)存地址,而這并不是我們想要的,我們實際想要的也是像第一段代碼那樣返回一個有用的信息,這時就要用到__str__
這個內(nèi)置方法了,它定義在類的內(nèi)部,只要類被實例化,就會自動觸發(fā)__str__
的執(zhí)行,并返回一個值給實例化后的對象。如下示例:
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
def __str__(self):
return "Host:%s,Port:%s"%(self.host,self.port)
conn = mysql("127.0.0.1","3306")
#會將`__str__`方法的返回值賦值給對象conn
print(conn)
'''
輸出:
Host:127.0.0.1,Port:3306
'''
現(xiàn)在再來比較比較@staticmethod
和@classmethod
的區(qū)別:
先來看看用@staticmethod
效果:
#settings.py
HOST='127.0.0.1'
PORT=3306
import settings
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
@staticmethod
def from_conf():
return mysql(settings.HOST,settings.PORT)
#這里需要指明用mysql類去實例化
def __str__(self):
return "我是mysql實例化的對象"
class mariadb(mysql):
def __str__(self):
return "主機:%s,端口:%s"%(self.host,self.port)
conn = mariadb.from_conf()
print(conn) #我們的意圖是想觸發(fā)mariadb.__str__,但是結(jié)果觸發(fā)了mysql.__str__的執(zhí)行
#打印結(jié)果:我是mysql實例化的對象
將@staticmethod
換成@classmethod
看看有什么變化
import settings
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
@classmethod
def from_conf(cls):
return cls(settings.HOST,settings.PORT)
# @staticmethod
# def from_conf():
# return mysql(settings.HOST,settings.PORT)
#這里需要指明用mysql類去實例化
def __str__(self):
return "我是mysql實例化的對象"
class mariadb(mysql):
def __str__(self):
return "主機:%s,端口:%s"%(self.host,self.port)
conn = mariadb.from_conf()
print(conn) #觸發(fā)了mariadb.__str__的執(zhí)行
#打印結(jié)果:主機:127.0.0.1,端口:3306
總結(jié):
@classmethod
綁定到類的方法,誰調(diào)用,就會將誰當作第一個參數(shù)傳入,所以在這種繼承的環(huán)境下,就必須用@classmethod
。
練習:定義MySQL類
1.對象有id、host、port三個屬性
2.定義工具create_id,在實例化時為每個對象隨機生成id,保證id唯一
3.提供兩種實例化方式,方式一:用戶傳入host和port 方式二:從配置文件中讀取host和port進行實例化
4.為對象定制方法,save和get,save能自動將對象序列化到文件中,文件名為id號,文件路徑為配置文件中DB_PATH;get方法用來從文件中反序列化出對象
import time,hashlib
import settings
import pickle
import os
class mysql:
def __init__(self,host,port):
self.host = host
self.port = port
self.id = self.create_id()
@staticmethod
def create_id():
m = hashlib.md5(str(time.clock()).encode("utf-8"))
# 查看clock源碼注釋,指的是cpu真實時間,不要用time.time(),否則會出現(xiàn)id重復
return m.hexdigest()
@classmethod
def from_conf(cls):
return cls(settings.HOST,settings.PORT)
def save(self):
pickle.dump(self,open(self.id,'wb'))
def get(self):
return pickle.load(open(self.id,'rb'))
conn = mysql("192.168.1.87","3306")
conn1 = mysql.from_conf()
# print(conn.id)
# print(conn1.id)
# conn.save()
obj = conn.get() #從文件中獲取對象
print(obj.id) #獲得對象的id
六、 面向?qū)ο蟮能浖_發(fā)
面向?qū)ο蟮能浖こ贪ㄏ旅鎺讉€部分:
- 面向?qū)ο蠓治觯╫bject oriented analysis ,OOA)
- 面向?qū)ο笤O(shè)計(object oriented design,OOD)
- 面向?qū)ο缶幊蹋╫bject oriented programming,OOP)
- 面向?qū)ο鬁y試(object oriented test,OOT)
- 面向?qū)ο缶S護(object oriendted soft maintenance,OOSM)