一、多態性
多態指的是一類事物有多種形態,比如動物有多種形態:貓、狗、豬
class Animal: # 同一類事物:動物
def talk(self):
pass
class Cat(Animal): # 動物的形態之一:貓
def talk(self): # 重寫父類talk方法
print('喵喵喵')
class Dog(Animal): # 動物的形態之二:狗
def talk(self):
print('汪汪汪')
class Pig(Animal): # 動物的形態之三:豬
def talk(self):
print('哼哼哼')
# 實例化得到三個對象
>>> cat=Cat()
>>> dog=Dog()
>>> pig=Pig()
多態性指的是可以在不用考慮對象具體類型的情況下而直接使用對象
,這就需要在設計時,把對象的使用方法統一成一種:例如cat、dog、pig都是動物,但凡是動物肯定有talk方法,於是我們可以不用考慮它們三者的具體是什麼類型的動物,而直接使用
>>> cat.talk()
喵喵喵
>>> dog.talk()
汪汪汪
>>> pig.talk()
哼哼哼
更進一步,我們可以定義一個統一的接口來使用
>>> def Talk(animal):
... animal.talk()
...
>>> Talk(cat)
喵喵喵
>>> Talk(dog)
汪汪汪
>>> Talk(pig)
哼哼哼
接下來我們來看一個我們常用的python函數,來看下多態性的應用場景
:
# 以__len__函數爲例,我們都知道字符串、列表、元組等等都有一個內置方法__len__來計算它們各自的長度
>>> print([1,2,3].__len__(),'1234'.__len__(),(1,2,3,4,5).__len__())
3 4 5
# 注意這些len方法不是一個,列表、字符串等等他們都有自己的__len__方法!
# Python內置了一個統一的接口,len函數無論你傳入的是什麼類型的數據,都可以計算出長度,這是怎麼實現的呢?
len([1,2,3])
len('1234')
len((1,2,3,4,5))
# 這就用到了多態性
>>> def len(obj):
... obj.__len__()
綜上我們得知,多態性的本質在於不同的子類中定義有相同的方法名,對父類的同名方法進行重寫
,這樣我們就可以不考慮類統一實現這個同名方法,交給子類使用,而是讓子類自己實現方法,百花齊放,體現多態性,可以通過在父類引入抽象類的概念來硬性限制子類必須有某些方法名
import abc # 還真有abc這個模塊!!
# 指定metaclass屬性將類設置爲抽象類,抽象類本身只是用來約束子類的,不能被實例化
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod # 該裝飾器限制子類必須定義有一個名爲talk的方法
def talk(self): # 抽象方法中無需實現具體的功能
pass
class Cat(Animal): # 但凡繼承Animal的子類都必須遵循Animal規定的標準
def talk(self):
pass
cat=Cat() # 若子類中沒有一個名爲talk的方法則會拋出異常TypeError,無法實例化
二、鴨子類型
但其實我們完全可以不依賴於繼承,只需要製造出外觀和行爲相同對象,同樣可以實現不考慮對象類型而使用對象,這正是Python崇尚的“鴨子類型”(duck typing):“如果看起來像、叫聲像而且走起路來像鴨子,那麼它就是鴨子”。比起繼承的方式,鴨子類型在某種程度上實現了程序的鬆耦合度,如下
# 二者看起來都像文件,因而就可以當文件一樣去用,然而它們並沒有直接的關係
class Txt: # Txt類有兩個與文件類型同名的方法,即read和write
def read(self):
pass
def write(self):
pass
class Disk: # Disk類也有兩個與文件類型同名的方法:read和write
def read(self):
pass
def write(self):
pass
注意:目前我還有點不明白鴨子類型這個到底有啥用,感覺很雞肋,知道它的應用場場景的可以在評論區說下,萬分感謝!
三、綁定方法
類中定義的函數分爲兩大類:綁定方法和非綁定方法
其中綁定方法又分爲綁定到對象的對象方法和綁定到類的類方法
。
在類中正常定義的函數默認是綁定到對象
的,而爲某個函數加上裝飾器@classmethod後,該函數就綁定到了類
。
我們在之前的章節中已經介紹過對象方法了,本節我們主要介紹類方法。類方法是一種生成對象的方法
1、問題引入
# 配置文件settings.py的內容
HOST='127.0.0.1'
PORT=3306
# 類方法的應用
import settings
class MySQL:
def __init__(self,host,port):
self.host=host
self.port=port
obj1 = Mysql(settings.HOST,settings.PORT)
obj2 = Mysql(settings.HOST,settings.PORT)
obj3 = Mysql(settings.HOST,settings.PORT)
# 假如我經常要用這樣實例化,每次settings.HOST,settings.PORT,是不是很麻煩,類方法就是解決這樣的問題。
2、綁定給類的方法
# 配置文件settings.py的內容
HOST='127.0.0.1'
PORT=3306
# 類方法的應用
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)
>>> MySQL.from_conf # 綁定到類的方法
<bound method MySQL.from_conf of <class ‘__main__.MySQL'>>
>>> conn=MySQL.from_conf() # 調用類方法,自動將類MySQL當作第一個參數傳給cls
綁定到類的方法就是專門給類用的
,但其實對象也可以調用,只不過自動傳入的第一個參數仍然是類,也就是說這種調用是沒有意義的,並且容易引起混淆,這也是Python的對象系統與其他面嚮對象語言對象系統的區別之一,比如Smalltalk和Ruby中,綁定到類的方法與綁定到對象的方法是嚴格區分開的。
四、非綁定方法
爲類中某個函數加上裝飾器@staticmethod後,該函數就變成了非綁定方法,也稱爲靜態方法
。該方法不與類或對象綁定,類與對象都可以來調用它,但它就是一個普通函數
而已,因而沒有自動傳值那麼一說
import uuid
class MySQL:
def __init__(self,host,port):
self.id=self.create_id()
self.host=host
self.port=port
@staticmethod
def create_id():
return uuid.uuid1()
>>> conn=MySQL(‘127.0.0.1',3306)
>>> print(conn.id) #100365f6-8ae0-11e7-a51e-0088653ea1ec
# 類或對象來調用create_id發現都是普通函數,而非綁定到誰的方法
>>> MySQL.create_id
<function MySQL.create_id at 0x1025c16a8>
>>> conn.create_id
<function MySQL.create_id at 0x1025c16a8>
總結綁定方法與非綁定方法的使用:若類中需要一個功能,該功能的實現代碼中需要引用對象則將其定義成對象方法、需要引用類則將其定義成類方法、無需引用類或對象則將其定義成靜態方法。