重試機制工具
一 面向物件VS面向過程
面向過程的程式設計的核心是過程(流水線式思維),過程即解決問題的步驟,面向過程的設計就好比精心設計好一條流水線,考慮周全什麼時候處理什麼東西。
優點是:極大的降低了寫程式的複雜度,只需要順著要執行的步驟,堆疊程式碼即可。
缺點是:一套流水線或者流程就是用來解決一個問題,程式碼牽一髮而動全身。
應用場景:一旦完成基本很少改變的場景,著名的例子有Linux核心,git,以及Apache HTTP Server等。
面向物件的程式設計的核心是物件(上帝式思維),要理解物件為何物,必須把自己當成上帝,上帝眼裡世間存在的萬物皆為物件,不存在的也可以創造出來。面向物件的程式設計好比如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題需要四個人:唐僧,沙和尚,豬八戒,孫悟空,每個人都有各自的特徵和技能(這就是物件的概念,特徵和技能分別對應物件的屬性和方法),然而這並不好玩,於是如來又安排了一群妖魔鬼怪,為了防止師徒四人在取經路上被搞死,又安排了一群神仙保駕護航,這些都是物件。然後取經開始,師徒四人與妖魔鬼怪神仙互相纏鬥著直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取。
面向物件的程式設計的
優點是:解決了程式的擴充套件性。對某一個物件單獨修改,會立刻反映到整個體系中,如對遊戲中一個人物引數的特徵和技能修改都很容易。
缺點:可控性差,無法向面向過程的程式設計流水線式的可以很精準的預測問題的處理流程與結果,面向物件的程式一旦開始就由物件之間的互動解決問題,即便是上帝也無法預測最終結果。於是我們經常看到一個遊戲人某一引數的修改極有可能導致陰霸的技能出現,一刀砍死3個人,這個遊戲就失去平衡。
應用場景:需求經常變化的軟體,一般需求的變化都集中在使用者層,網際網路應用,企業內部軟體,遊戲等都是面向物件的程式設計大顯身手的好地方。
在python 中面向物件的程式設計並不是全部。
面向物件程式設計可以使程式的維護和擴充套件變得更簡單,並且可以大大提高程式開發效率 ,另外,基於面向物件的程式可以使它人更加容易理解你的程式碼邏輯,從而使團隊開發變得更從容。
瞭解一些名詞:類、物件、例項、例項化
類:具有相同特徵的一類事物(人、狗、老虎)
物件/例項:具體的某一個事物(隔壁阿花、樓下旺財)
例項化:類——>物件的過程(這在生活中表現的不明顯,我們在後面再慢慢解釋)
二 初識類和物件
python中一切皆為物件,型別的本質就是類。(類也是物件)
>>> dict #型別dict就是類dict <class 'dict'> >>> d=dict(name='eva') #例項化 >>> d.pop('name') #向d發一條訊息,執行d的方法pop 'eva'
從上面的例子來看,字典就是一類資料結構,我一說字典你就知道是那個用{}表示,裡面由k-v鍵值對的東西,它還具有一些增刪改查的方法。但是我一說字典你能知道字典裡具體存了哪些內容麼?不能,所以我們說對於一個類來說,它具有相同的特徵屬性和方法。
而具體的{'name':'eva'}這個字典,它是一個字典,可以使用字典的所有方法,並且裡面有了具體的值,它就是字典的一個物件。物件就是已經實實在在存在的某一個具體的個體。
在python中,用變量表示特徵,用函式表示技能,因而具有相同特徵和技能的一類事物就是‘類’,物件是則是這一類事物中具體的一個。
在現實世界中:先有物件,再有類。
物件:李軍
特徵:
國籍=中國
性別=男
技能:
學習
吃飯
在程式世界中:先有類,再有物件。
類:中國
成員:
物件1=李軍
物件2=張三
物件3=萬五
細說__init__方法
#方式一、為物件初始化自己獨有的特徵
class People:
country='China'
x=1
def run(self):
print('----->', self)
# 例項化出三個空物件
obj1=People()
obj2=People()
obj3=People()
# 為物件定製自己獨有的特徵
obj1.name='egon'
obj1.age=18
obj1.sex='male'
obj2.name='lxx'
obj2.age=38
obj2.sex='female'
obj3.name='alex'
obj3.age=38
obj3.sex='female'
# print(obj1.__dict__)
# print(obj2.__dict__)
# print(obj3.__dict__)
# print(People.__dict__)
#方式二、為物件初始化自己獨有的特徵
class People:
country='China'
x=1
def run(self):
print('----->', self)
# 例項化出三個空物件
obj1=People()
obj2=People()
obj3=People()
# 為物件定製自己獨有的特徵
def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
obj.name = x
obj.age = y
obj.sex = z
chu_shi_hua(obj1,'egon',18,'male')
chu_shi_hua(obj2,'lxx',38,'female')
chu_shi_hua(obj3,'alex',38,'female')
#方式三、為物件初始化自己獨有的特徵
class People:
country='China'
x=1
def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
obj.name = x
obj.age = y
obj.sex = z
def run(self):
print('----->', self)
obj1=People()
# print(People.chu_shi_hua)
People.chu_shi_hua(obj1,'egon',18,'male')
obj2=People()
People.chu_shi_hua(obj2,'lxx',38,'female')
obj3=People()
People.chu_shi_hua(obj3,'alex',38,'female')
# 方式四、為物件初始化自己獨有的特徵
class People:
country='China'
x=1
def __init__(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male'
obj.name = x
obj.age = y
obj.sex = z
def run(self):
print('----->', self)
obj1=People('egon',18,'male') #People.__init__(obj1,'egon',18,'male')
obj2=People('lxx',38,'female') #People.__init__(obj2,'lxx',38,'female')
obj3=People('alex',38,'female') #People.__init__(obj3,'alex',38,'female')
# __init__方法
# 強調:
# 1、該方法內可以有任意的python程式碼
# 2、一定不能有返回值
class People:
country='China'
x=1
def __init__(obj, name, age, sex): #obj=obj1,x='egon',y=18,z='male'
# if type(name) is not str:
# raise TypeError('名字必須是字串型別')
obj.name = name
obj.age = age
obj.sex = sex
def run(self):
print('----->', self)
# obj1=People('egon',18,'male')
obj1=People(3537,18,'male')
# print(obj1.run)
# obj1.run() #People.run(obj1)
# print(People.run)
!!!__init__方法之為物件定製自己獨有的特徵
三 屬性查詢
類有兩種屬性:資料屬性和函式屬性
1.類的資料屬性是所有物件共享的
2.類的函式屬性是繫結給物件用的
#類的資料屬性是所有物件共享的,id都一樣
print(id(OldboyStudent.school))
print(id(s1.school))
print(id(s2.school))
print(id(s3.school))
'''
4377347328
4377347328
4377347328
4377347328
'''
#類的函式屬性是繫結給物件使用的,obj.method稱為繫結方法,記憶體地址都不一樣
#ps:id是python的實現機制,並不能真實反映記憶體地址,如果有記憶體地址,還是以記憶體地址為準
print(OldboyStudent.learn)
print(s1.learn)
print(s2.learn)
print(s3.learn)
'''
<function OldboyStudent.learn at 0x1021329d8>
<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>>
<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>>
<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>>
'''
在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的class術語與c++有一定區別,與 Modula-3更像。
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]
五 物件之間的互動
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 #根據自己的攻擊力,攻擊敵人就減掉敵人的生命值。
我們可以仿照garen類再建立一個Riven類
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 #根據自己的攻擊力,攻擊敵人就減掉敵人的生命值。
例項出倆英雄
>>> g1=Garen('草叢倫')
>>> r1=Riven('銳雯雯')
互動:銳雯雯攻擊草叢倫,反之一樣
>>> g1.life_value
455
>>> r1.attack(g1)
>>> g1.life_value
401
六 繼承與派生
1 初識繼承
繼承是一種建立新類的方式,新建的類可以繼承一個或多個父類(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的類,以及該類的子類,都是新式類
3.在python3中,無論是否宣告繼承object,都預設繼承object,即python3中所有類均為新式類
#關於新式類與經典類的區別,我們稍後討論
提示:如果沒有指定基類,python的類會預設繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
2 繼承與抽象(先抽象再繼承)
繼承描述的是子類與父類之間的關係,是一種什麼是什麼的關係。要找出這種關係,必須先抽象再繼承
抽象即抽取類似或者說比較像的部分。
抽象分成兩個層次:
1.將奧巴馬和梅西這倆物件比較像的部分抽取成類;
2.將人,豬,狗這三個類比較像的部分抽取成父類。
抽象最主要的作用是劃分類別(可以隔離關注點,降低複雜度)
繼承:是基於抽象的結果,通過程式語言去實現它,肯定是先經歷抽象這個過程,才能通過繼承的方式去表達出抽象的結構。
抽象只是分析和設計的過程中,一個動作或者說一種技巧,通過抽象可以得到類
3 繼承與重用性
例子
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('汪汪叫')
# ######### 執行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()
c2 = Cat('小黑的小白貓')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
'''
類貓與狗都要相同的吃喝拉撒的功能,而這些功能都是他們相同的功能,於是抽象給了動物這個類,
並且各自繼承了這個,就避免在自己的類中重複定義,這種在類中呼叫父類的功能或資料的方式叫做重用
'''
提示:用已經有的類建立一個新的類,這樣就重用了已經有的軟體中的一部分設定大部分,大大生了程式設計工作量,這就是常說的軟體重用,不僅可以重用自己的類,也可以繼承別人的,比如標準庫,來定製新的資料型別,這樣就是大大縮短了軟體開發週期,對大型軟體開發來說,意義重大.
4 派生
當然子類也可以新增自己新的屬性或者在自己這裡重新定義這些屬性(不會影響到父類),需要注意的是,一旦重新定義了自己的屬性且與父類重名,那麼呼叫新增的屬性時,就以自己為準了。
class Riven(Hero):
camp='Noxus'
def attack(self,enemy): #在自己這裡定義新的attack,不再使用父類的attack,且不會影響父類
print('from riven')
def fly(self): #在自己這裡定義新的
print('%s is flying' %self.nickname)
5 組合與重用性
軟體重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的物件作為資料屬性,稱為類的組合
>>> 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
組合與繼承都是有效地利用已有類的資源的重要方式。但是二者的概念和使用場景皆不同,
1.繼承的方式
通過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬,人是動物。
當類之間有很多相同的功能,提取這些共同的功能做成基類,用繼承比較好,比如老師是人,學生是人
2.組合的方式
用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教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()
當類之間有顯著不同,並且較小的類是較大的類所需要的元件時,用組合比較好
6 繼承實現的原理(菱形問題)
(1) 繼承順序
在Java和C#中子類只能繼承一個父類,而Python中子類可以同時繼承多個父類,如A(B,C,D)
如果繼承關係為非菱形結構,則會按照先找B這一條分支,然後再找C這一條分支,最後找D這一條分支的順序直到找到我們想要的屬性
如果繼承關係為菱形結構,那麼屬性的查詢方式有兩種,分別是:深度優先和廣度優先
(2) 繼承原理
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列表並遵循如下三條準則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.如果對下一個類存在兩個合法的選擇,選擇第一個父類
7 子類呼叫父類的方法
方法一:
指名道姓,即父類名.父類方法()
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
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仍然會按照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'>]
七 多型與多型性
1 多型
多型指的是一類事物有多重形態
動物有多重形態:人,狗,豬
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')
注意:
多型是指一類事物有多重形態,
而抽象則是將則多重形態抽取相同的部分放在父類中。
2 多型性
(1)什麼是多型動態繫結
多型性是指在不考慮例項型別的情況下使用例項
在面向物件方法中一般是這樣表述多型性:向不同的物件傳送同一條訊息(!!!obj.func():是呼叫了obj的方法func,又稱為向obj傳送了一條訊息func),不同的物件在接收時會產生不同的行為(即方法)。也就是說,每個物件可以用自己的方式去響應共同的訊息。所謂訊息,就是呼叫函式,不同的行為就是指不同的實現,即執行不同的函式。
比如:老師.下課鈴響了(),學生.下課鈴響了(),老師執行的是下班操作,學生執行的是放學操作,雖然二者訊息一樣,但是執行的效果不同
多型性分為靜態多型性和動態多型性
靜態多型性:如任何型別都可以用運算子+進行運算
動態多型性:如下
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是動物,只要是動物肯定有talk方法
#於是我們可以不用考慮它們三者的具體是什麼型別,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更進一步,我們可以定義一個統一的介面來使用
def func(obj):
obj.talk()
(2) 為什麼要用多型性
1.增加了程式的靈活性
以不變應萬變,不論物件千變萬化,使用者都是同一種形式去呼叫,如func(animal)
2.增加了程式額可擴充套件性
通過繼承animal類建立了一個新的類,使用者無需更改自己的程式碼,還是用func(animal)去呼叫
(3) 鴨子型別
逗比時刻:
Python崇尚鴨子型別,即‘如果看起來像、叫聲像而且走起路來像鴨子,那麼它就是鴨子’
python程式設計師通常根據這種行為來編寫程式。例如,如果想編寫現有物件的自定義版本,可以繼承該物件
也可以建立一個外觀和行為像,但與它無任何關係的全新物件,後者通常用於儲存程式元件的鬆耦合度。
例1:利用標準庫中定義的各種‘與檔案類似’的物件,儘管這些物件的工作方式像檔案,但他們沒有繼承內建檔案物件的方法
#二者都像鴨子,二者看起來都像檔案,因而就可以當檔案一樣去用
class TxtFile:
def read(self):
pass
def write(self):
pass
class DiskFile:
def read(self):
pass
def write(self):
pass
例2:其實大家一直在享受著多型性帶來的好處,比如Python的序列型別有多種形態:字串,列表,元組,多型性體現如下
#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)
八 封裝
1 什麼是封裝
封裝即是隱藏物件的屬性和實現細節,僅對外公開介面,控制在程式中屬性的讀和修改的訪問級別,將抽象得到的資料行為(或功能)相結合,形成一個有機的整體。
2 如何隱藏
通過self.__屬性名把類的資料屬性設定成私有的,其隱藏是對外不對內,就是在外部繫結物件後,物件無法以物件.__屬性名的方式訪問到該屬性,但是在類的內部則是可以訪問。但在語法上外部可以通過物件._類名.__屬性名的方式訪問到該名字。
注意:
1.在定義類或者初始化物件時,在屬性前加__,就會將該屬性隱藏起來
但該隱藏起始只是一種變形_類名__屬性名,並沒有真正隱藏起來
2.該變形操作是在類定義階段掃描語法發生的變形,類定義之後新增的__開頭的屬性不會發生變形
3.該隱藏式對外不對內的
4.在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有
#其實這僅僅這是一種變形操作且僅僅只在類定義階段發生變形
#類中所有雙下劃線開頭的名稱如__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這個名字訪問到。
3 封裝不是單純意義的隱藏
封裝的真諦在於明確地區分內外,封裝的屬性可以直接在內部使用,而不能再外部使用,但外部想要用類隱藏的介面,就需要我們在類中為其開闢介面,讓外部間接用到我們隱藏的屬性名。
意義:
1.將資料隱藏起來不是目的,隱藏起來然後對外提供操作該資料的介面,
然後我們在介面附加上對該資料操作的限制,以此完成對資料屬性操作的嚴格控制。
class Teacher:
def __init__(self,name,age):
# self.__name=name
# self.__age=age
self.set_info(name,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()
2.隔離複雜度,在程式語言中,對外提供介面,可以是函式,稱為介面函式,這與介面的概念還不一樣,介面代表一組介面函式的集合體。
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、列印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其餘功能我們都可以隱藏起來,很明顯這麼做
#隔離了複雜度,同時也提升了安全性
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()
4 特性property
property是一種特殊的屬性,訪問它會執行一段功能(函式)然後返回值。
為何要用:
在外部即是用呼叫資料的方法呼叫這個功能,並直接得到返回值,這樣物件根本
無法察覺到自己是執行了一個函式計算出來的,這種特性的使用方式遵循了統一訪問的原則。
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
'''
九 繫結與非繫結方法
1 類中定義的函式分成兩大類
(1):繫結方法(繫結給誰,誰來呼叫就自動將它本身當做第一個引數傳入):
1.繫結到類的方法:用classmethod裝飾器裝飾的方法。
為類量身定製
2.繫結到物件的方法:沒有被任何裝飾器裝飾的方法。
為物件量身定製
(屬於類的函式,類可以呼叫,但是必須按照函式的規則來,沒有自動傳值一說)
(2):非繫結方法:用staticmethod裝飾器裝飾的方法
1.不與類或物件繫結,類和物件都可以呼叫,但是沒有自動傳值那麼一說,類與物件都可以呼叫
注意:與繫結到物件方法區分開,在類中直接定義的函式,沒有被任何裝飾器裝飾的,都是繫結到物件的方法,可不是普通函式,物件呼叫該方法會自動傳值,而staticmethod裝飾的方法,不管誰來呼叫,都沒有自動傳值一說
2 繫結方法
繫結給物件的方法(略)
繫結給類的方法(classmethod)
classmehtod是給類用的,即繫結到類,類在使用時會將類本身當做引數傳給類方法的第一個引數(即便是物件來呼叫也會將類當作第一個引數傳入),python為我們內建了函式classmethod來把類中的函式定義成類方法
HOST='127.0.0.1'
PORT=3306
DB_PATH=r'C:\Users\Administrator\PycharmProjects\test\面向物件程式設計\test1\db'
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() #物件也可以呼叫,但是預設傳的第一個引數仍然是類
3 非繫結方法
在類內部用staticmethod裝飾的函式即非繫結方法,就是普通函式
statimethod不與類或物件繫結,誰都可以呼叫,沒有自動傳值效果
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> #檢視結果為普通函式