概述
- 面向過程:根據業務邏輯從上到下寫壘代碼
- 函數式:將某功能代碼封裝到函數中,往後便無需重複編寫,僅調用函數便可
- 面向對象:對函數進行分類和封裝,讓開發「更快更好更強...」
在以前已經接觸過了面向過程和函數式的編程,還有那麼今天咱們來學習一種新的編程方式:面向對象編程(Object Oriented Programming,OOP,面向對象程序設計)python
建立類和對象
程序員
面向對象編程是一種編程方式,此編程方式的落地須要使用 「類」 和 「對象」 來實現,因此,面向對象編程其實就是對 「類」 和 「對象」 的使用。編程
類就是一個模板,模板裏能夠包含多個函數,函數裏實現一些功能(把一類事物的相同特徵和動做整合到一塊兒就是類)socket
對象則是根據模板建立的實例,經過實例對象能夠執行類中的函數(就是基於類而建立的一個具體的事物(具體存在的))ide
在這裏舉一個小例子:函數
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 建立類
class
Foo:
def
Bar(
self
):
print
'Bar'
def
Hello(
self
, name):
print
'i am %s'
%
name
# 根據類Foo建立對象obj
obj
=
Foo()
obj.Bar()
#執行Bar方法
obj.Hello(
'wupeiqi'
)
#執行Hello方法
|
面向對象:【建立對象】【經過對象執行方法】post
函數編程:【執行函數】學習
應用的場景不一樣形成的編程方式也不一樣,總結:函數式的應用場景 --> 各個函數之間是獨立且無共用的數據編碼
這裏再對面向對象再進行一下細分:spa
一、面向對象不是全部狀況都適用
二、面向對象編程
a 定義類
class Foo:
def 方法1(self,bb):
pass
obj = Foo(bb)
b 根據類建立對象(建立一個Foo類的實例)
可使用對象去執行類中的方法
三、self,形式參數,代指執行方法的對象。python內部自動傳遞
四、類+括號 => 自動執行類中的 __init__方法: 建立了一個對象
在__init__ 有一個特殊名字:構造方法
==》 初始化
__del__ 解釋器銷燬對象時候自動調用,特殊的名: 析構方法
靜態屬性,靜態方法,類方法,組合
靜態屬性的做用 將函數屬性假裝成數據屬性(封裝邏輯)
用法 在方法(函數屬性)上面加 @property

1 class Desk: 2 def __init__(self,name,width,length,heigh): 3 self.name = name 4 self.width = width 5 self.length = length 6 self.heigh = heigh 7 @property # 靜態 將函數屬性假裝成數據屬性 8 def cal_area(self): 9 x = "立方厘米" 10 return self.length*self.width*self.heigh,x 11 12 d1 = Desk("舒克",20,50,10) 13 print(d1.cal_area) 14 15 (10000, '立方厘米')
靜態方法 跟類和實例無關係,名義上歸屬類管理,但不能使用類變量和實例變量
用法 在方法(函數屬性)上面加 @staticmethod
特色: 只能使用靜態變量, 因此始終佔用同一個內存, 執行效率更高, 但不會被自動回收.

1 class Desk: 2 def __init__(self,name,width,length,heigh): 3 self.name = name 4 self.width = width 5 self.length = length 6 self.heigh = heigh 7 @staticmethod # 用函數修飾符來聲明靜態方法 8 def cal_area(): 9 print("我跟類和實例沒有任何關係哦!") 10 11 12 13 # 靜態方法不能調用類和實例的屬性
類方法 不須要self參數,自動加cls參數,python底層自動傳入類對象。
跟實例沒有關係,類本身調用;只能訪問類的屬性,不能訪問實例屬性
用法 在方法(函數屬性)上面加 @classmethod

1 #@classmethod 類方法 2 class Desk: 3 same = "study" 4 def __init__(self,name,width,length,heigh): 5 self.name = name 6 self.width = width 7 self.length = length 8 self.heigh = heigh 9 @classmethod 10 def cal_area(cls): 11 print(cls) # 接收一個類名,就是類自己,能夠訪問類的屬性,不能訪問實例的屬性 12 print(cls.same) 13 14 Desk.cal_area() # 類調用方法 此刻不須要傳參數,自動傳了參數進去
組合 大類包含小類,能夠做關聯
就是將不一樣的類混合並加入到其餘類中,來 增長類的功能 / 提升代碼的重用性 / 易於維護(對類的修改會直接反應到整個應用中) 。咱們能夠實例化一個更大的對象,同時還能夠添加一些實例屬性和實例方法的實現來豐富實例對象的功能。

1 class Hand: 2 pass 3 4 class Foot: 5 pass 6 7 class Trunk: 8 pass 9 10 class Head: 11 pass 12 13 class Person: 14 def __init__(self,id_num,name): 15 self.id_num=id_num #身份證號碼 16 self.name=name 17 self.hand=Hand() 18 self.foot=Foot() 19 self.trunk=Trunk() 20 self.head=Head() 21 22 p1=Person('111111','alex') #給實例傳值 23 print(p1.__dict__) #查看實例的屬性字典 24 25 {'foot': <__main__.Foot object at 0x011B4130>, 'trunk': <__main__.Trunk object at 0x011B4170>, 'id_num': '111111', 'hand': <__main__.Hand object at 0x011B40D0>, 'name': 'alex', 'head': <__main__.Head object at 0x011B4190>}

# 組合 class Sea: # def __init__(self,name,country,addr): self.name = name self.country = country self.addr = addr def sea_wave(self): print("一波兒海嘯正在來襲") s1 = Sea("東海","哥雅王國","風車村") class Seapoacher: def __init__(self,name,age,gender,ability): self.name = name self.age = age self.gender = gender self.ability = ability self.sea = s1 # 此時關聯到上一個類 def atk(self): print("攻擊力100") def show_info(self): print("%s %s %s 籍貫:%s %s %s" %(self.name,self.gender,self.age,self.sea.name, self.sea.country,self.sea.addr)) hz = Seapoacher("路飛",17,"男","橡膠") hz.show_info() 路飛 男 17 籍貫:東海 哥雅王國 風車村
========三大特性=========
封裝:
使用場景 當同一類型的方法具備相同參數時,直接封裝到對象便可。
使用場景 把類看成模板,建立多個對象(對象內封裝的數據能夠不同)

1 # 聲明定義一個類 2 class onepiece: 3 def __init__(self,name,ability): # 封裝數據到裏面 4 self.name = name 5 self.ability = ability 6 def long(self): 7 print("%s是擁有%s惡魔果實能力的能力者" %(self.name,self.ability)) 8 # 類實例化,類執行自動執行__init__方法 9 haizei = onepiece("路飛","橡膠") 10 haizei.long() # 根據對象能夠調用類下面的方法
封裝,顧名思義就是將內容封裝到某個地方,之後再去調用被封裝在某處的內容。
因此,在使用面向對象的封裝特性時,須要一、將內容封裝到某處 二、從某處調用被封裝的內容
第一步:將內容封裝到某處
self 是一個形式參數,當執行 obj1 = Foo('wupeiqi', 18 ) 時,self 等於 obj1
當執行 obj2 = Foo('alex', 78 ) 時,self 等於 obj2
因此,內容其實被封裝到了對象 obj1 和 obj2 中,每一個對象中都有 name 和 age 屬性,在內存裏相似於下圖來保存。
第二步:從某處調用被封裝的內容
調用被封裝的內容有倆種狀況
一、經過對象直接調用被封裝的內容
對象.屬性名

class Foo: def __init__(self, name, age): self.name = name self.age = age obj1 = Foo('lufei',20) print(obj1.name) # 直接調用obj1對象的name屬性 print(obj1.age) # 直接調用obj1對象的age屬性 obj2 = Foo('suolong',19) print(obj2.name) # 直接調用obj2對象的name屬性 print(obj2.age) # 直接調用obj2對象的age屬性
二、經過self間接調用被封裝的內容

1 class Foo: 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def detail(self): 8 print self.name 9 print self.age 10 11 obj1 = Foo('wupeiqi', 18) 12 obj1.detail() # Python默認會將obj1傳給self參數,即:obj1.detail(obj1),因此,此時方法內部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 13 14 obj2 = Foo('alex', 73) 15 obj2.detail() # Python默認會將obj2傳給self參數,即:obj1.detail(obj2),因此,此時方法內部的 self = obj2,即:self.name 是 alex ; self.age 是 78
綜上所述,對於面向對象的封裝來講,其實就是使用構造方法將內容封裝到 對象 中,而後經過對象直接或者self間接獲取被封裝的內容。
封裝是啥,拋開面向對象,你單去想什麼是裝,裝就是拿來一個麻袋,把小貓,小狗,小王八,還有alex一塊兒裝進麻袋,什麼是封,封就是把麻袋封上口子。
在面向對象中這個麻袋就是你的類或者對象,類或者對象這倆麻袋內部裝了數據屬性和函數屬性,那麼對於類和對象來講‘封’的概念從何而來,其實封的概念表明隱藏。
約定:任何以單下劃線開頭的名字和雙下劃線開頭的名字都應該是內部的,私有的。

# 封裝下的 _ 和 __ # _和__表示一種隱藏的約定 python並不會真正阻止訪問 class Onepiece: _BOSS = "哥爾 D 羅傑" __FIRST = "白鬍子" def __init__(self,name): self.name = name def func(self): pass l = Onepiece("路飛") print(l._BOSS) # __ python會自動進行從新命名的操做 # print(l.__FIRST) # 報錯 print(l._Onepiece__FIRST) # 從新命名的形式就是此種 print(Onepiece.__dict__) # 在屬性字典中能夠查看到
python並不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,若是模塊名以單下劃線開頭,那麼from module import *時不能被導入,可是你from module import _private_module依然是能夠導入的。
其實不少時候你去調用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部調用的,做爲外部的你,仍然能夠用。
第三個層面的封裝:明確區份內外,內部的實現邏輯,外部沒法知曉,而且爲封裝到內部的邏輯提供一個訪問接口給外部使用。(這纔是真正的封裝。) 理解不深入

class People: __star='earth' def __init__(self,id,name,age,salary): self.id=id self.name=name self.__age=age self._salary=salary def _get_id(self): print('我是私有方法啊,我找到的id是[%s]' %self.id) class Korean(People): __star = '火星' pass print(People.__dict__)#__star存到類的屬性字典中被重命名爲_People__star print(Korean.__dict__) # print(Korean.__star)#傻逼,這麼訪問固然報錯啦,__star被重命名了,忘記了? print(Korean._Korean__star) print(Korean._People__star) 雙下劃線開頭在繼承中的應用
總結:
上面提到有兩種不一樣的編碼約定(單下劃線和雙下劃線 )來命名私有屬性,那麼問 題就來了:到底哪一種方式好呢?大多數而言,你應該讓你的非公共名稱以單下劃線開 頭。可是,若是你清楚你的代碼會涉及到子類,而且有些內部屬性應該在子類中隱藏 起來,那麼才考慮使用雙下劃線方案。 可是不管哪一種方案,其實python都沒有從根本上限制你的訪問。
繼承:
繼承又分爲單繼承和多繼承
1 class ParentClass1: 2 pass 3 4 class ParentClass2: 5 pass 6 7 class SubClass(ParentClass1): #單繼承 8 pass 9 10 class SubClass(ParentClass1,ParentClass2): #多繼承 11 pass

#繼承 class Dad: '這個是爸爸類' money=10 def __init__(self,name): print('爸爸') self.name=name def hit_son(self): print('%s 正在打兒子' %self.name) '這個是兒子類' class Son(Dad): money = 1000 s1=Son('alex') #繼承父類的屬性 print(s1.money) print(Dad.money) # print(Dad.__dict__) # print(Son.__dict__)
繼承也有倆種含義
一、繼承基類的方法,而且作出本身的改變或者擴展(代碼重用)
二、聲明某個子類兼容於某基類,定義一個接口類,子類繼承接口類,而且實現接口中定義的方法(又稱接口繼承)

# 接口繼承 import abc """接口類 通常不用實現方法,不必實例化,通常用來規範子類的 接口類有的方法,再派生類中定義方法,必需要有基類下的方法""" class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): pass @abc.abstractmethod def write(self): pass class Cd(All_file): def read(self): print("cd read") def write(self): print("cd write") class Mem(All_file): def read(self): print("mem read") def write(self): print("mem write") m1 = Mem()
在子類中繼承父類的方法,爲了不修改類名的關係,這裏用super()更好的解決方式
super() 不用傳父類名,也不須要傳self的參數

1 # 在子類當中調用父類方法 2 class A: 3 def __init__(self,name): 4 self.name = "-->%s"%name 5 def run(self): 6 print("地裂波動劍-->") 7 class B(A): 8 def __init__(self,name,age): 9 # A.__init__(self,name) 10 # super(B, self).__init__(name) 11 super().__init__(name) # super內不用傳參數 上面倆種方式與此相同 12 self.age = age 13 def show_info(self): 14 print(self.name) 15 def run(self): 16 super().run() 17 print("形成9999+傷害") 18 a = A("狂戰士") 19 b = B("鬼泣",18) 20 b.run() 21 print(b.name) 22 print(a.name) 23 24 地裂波動劍--> 25 形成9999+傷害 26 -->鬼泣 27 -->狂戰士
這裏介紹一下繼承的順序
在python3中,基類默認繼承 object 因此python中的類都稱之爲新式類,新式類的順序

# 繼承順序 # 在python3中都默認繼承object 先優先本身,繼而自左向右 class A: def test(self): print("A") class B(A): def test(self): print("B") class C(A): def test(self): print("C") class D(B): def test(self): print("D") class E(C): def test(self): print("A") class F(D,E): def test(self): print("F") print(F.__mro__) # 繼承順序就是基於mro 定義的順序 (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
多態:
多態:代表了動態綁定的存在,調用了不一樣的方法。才能展現出來。反映的是運行時候的狀態。
什麼是多態?
由不一樣的類實例化獲得的對象,調用同一個方法,執行的邏輯不一樣。
1 s1='abc' # 示例 2 l=[1,2] 3 print(s1.__len__()) 4 print(l.__len__()) 5 6 3 7 2

1 class H2O: 2 def __init__(self,name,temperature): #名字,溫度 3 self.name=name 4 self.temperature=temperature 5 def turn_ice(self): 6 if self.temperature < 0: 7 print('[%s]溫度過低結冰了' %self.name) 8 elif self.temperature >0 and self.temperature < 100: 9 print('[%s]液化成水' % self.name) 10 elif self.temperature > 100: 11 print('[%s]溫度過高變成了水蒸氣' % self.name) 12 13 class Water(H2O): #繼承父類(H2O) 14 pass 15 16 class Ice(H2O): 17 pass 18 19 class Steam(H2O): 20 pass 21 22 w1 = Water('水', 25) 23 i1 = Ice('冰', -20) 24 s1 = Steam('蒸汽', 3000) 25 26 def func(obj): 27 obj.turn_ice() 28 29 func(w1) #---->w1.turn_ice() 30 func(i1) #---->i1.turn_ice() 31 func(s1) #---->s1.turn_ice()
類的繼承有兩層意義:1.改變 2.擴展
一、多態就是類的這兩層意義的一個具體的實現機制
二、調用不一樣的類實例化獲得對象下的相同的方法,實現的過程不同。
三、python中的標準類型就是多態概念的一個很好的示範 。
說完以後,面向對象都有些什麼專業化的術語
抽象/實現
抽象指對現實世界問題和實體的本質表現,行爲和特徵建模,創建一個相關的子集,能夠用於 繪程序結構,從而實現這種模型。抽象不只包括這種模型的數據屬性,還定義了這些數據的接口。
對某種抽象的實現就是對此數據及與之相關接口的現實化(realization)。現實化這個過程對於客戶 程序應當是透明並且無關的。
封裝/接口
封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。經過任何客戶端直接對數據的訪問,無視接口,與封裝性都是背道而馳的,除非程序員容許這些操做。做爲實現的 一部分,客戶端根本就不須要知道在封裝以後,數據屬性是如何組織的。在Python中,全部的類屬性都是公開的,但名字可能被「混淆」了,以阻止未經受權的訪問,但僅此而已,再沒有其餘預防措施了。這就須要在設計時,對數據提供相應的接口,以避免客戶程序經過不規範的操做來存取封裝的數據屬性。
注意:封裝毫不是等於「把不想讓別人看到、之後可能修改的東西用private隱藏起來」
真正的封裝是,通過深刻的思考,作出良好的抽象,給出「完整且最小」的接口,並使得內部細節能夠對外透明
(注意:對外透明的意思是,外部調用者能夠順利的獲得本身想要的任何功能,徹底意識不到內部細節的存在)
合成
合成擴充了對類的 述,使得多個不一樣的類合成爲一個大的類,來解決現實問題。合成 述了 一個異常複雜的系統,好比一個類由其它類組成,更小的組件也多是其它的類,數據屬性及行爲, 全部這些合在一塊兒,彼此是「有一個」的關係。
派生/繼承/繼承結構
派生描述了子類衍生出新的特性,新類保留已存類類型中全部須要的數據和行爲,但容許修改或者其它的自定義操做,都不會修改原類的定義。
繼承描述了子類屬性從祖先類繼承這樣一種方式
繼承結構表示多「代」派生,能夠述成一個「族譜」,連續的子類,與祖先類都有關係。
泛化/特化
基於繼承
泛化表示全部子類與其父類及祖先類有同樣的特色。
特化描述全部子類的自定義,也就是,什麼屬性讓它與其祖先類不一樣。
多態
多態的概念指出了對象如何經過他們共同的屬性和動做來操做及訪問,而不需考慮他們具體的類。
多態代表了動態(又名,運行時)綁定的存在,允計重載及運行時類型肯定和驗證。
舉例:
水是一個類
不一樣溫度,水被實例化成了不一樣的狀態:冰,水蒸氣,霧(然而不少人就理解到這一步就職務此乃多態,錯,fuck!,多態是運行時綁定的存在)
(多態體如今由同一個類實例化出的多個對象,這些對象執行相同的方法時,執行的過程和結果是不同的)
冰,水蒸氣,霧,有一個共同的方法就是變成雲,可是冰.變雲(),與水蒸氣.變雲()是大相徑庭的兩個過程,雖然調用的方法都同樣
自省/反射
自省也稱做反射,這個性質展現了某對象是如何在運行期取得自身信息的。若是傳一個對象給你,你能夠查出它有什麼能力,這是一項強大的特性。若是Python不支持某種形式的自省功能,dir和type內建函數,將很難正常工做。還有那些特殊屬性,像__dict__,__name__及__doc__