反射是通過字元串的形式操作對象的相關成員
反射也就是通過字元串的形式,導入子產品,通過字元串的形式去子產品中尋找指定函數,并執行。
Python中的反射功能有四個主要内置函數提供:
- 1、hasattr(子產品, '成員'):根據字元串的形式,去某個子產品中檢查是否含有某個成員
- 2、getattr(子產品, '成員'):根據字元串的形式,去某個子產品中擷取成員
- 3、setattr(子產品, '成員'):根據字元創的形式,去某個某個子產品中設定成員
- 4、delattr(子產品,'成員'):根據字元串的形式,去某個子產品中删除成員
hasattr
判斷對象中是否有這個方法或變量
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交談"%self.name)
p = Person("laowang")
print(hasattr(p,"talk")) # True。因為存在talk方法
print(hasattr(p,"name")) # True。因為存在name變量
print(hasattr(p,"abc")) # False。因為不存在abc方法或變量
getattr
擷取對象中的方法或變量的記憶體位址
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交談"%self.name)
p = Person("laowang")
n = getattr(p,"name") # 擷取name變量的記憶體位址
print(n) # 此時列印的是:laowang
f = getattr(p,"talk") # 擷取talk方法的記憶體位址
f() # 調用talk方法
我們發現getattr有三個參數,那麼第三個參數是做什麼用的呢?
s = getattr(p,"abc","not find")
print(s) # 列印結果:not find。因為abc在對象p中找不到,本應該報錯,屬性找不到,但因為修改了找不到就輸出not find
setattr
為對象添加變量或方法
def abc(self):
print("%s正在交談"%self.name)
class Person(object):
def __init__(self,name):
self.name = name
p = Person("laowang")
setattr(p,"talk",abc) # 将abc函數添加到對象中p中,并命名為talk
p.talk(p) # 調用talk方法,因為這是額外添加的方法,需手動傳入對象
setattr(p,"age",30) # 添加一個變量age,複制為30
print(p.age) # 列印結果:30
delattr
删除對象中的變量。注意:不能用于删除方法
class Person(object):
def __init__(self,name):
self.name = name
def talk(self):
print("%s正在交談"%self.name)
p = Person("laowang")
delattr(p,"name") # 删除name變量
print(p.name) # 此時将報錯
對程式設計語言比較熟悉的朋友,應該知道“反射”這個機制。Python作為一門動态語言,當然不會缺少這一重要功能。然而,在網絡上卻很少見到有詳細或者深刻的剖析論文。下面結合一個web路由的執行個體來闡述python的反射機制的使用場景和核心本質。
一、web執行個體
考慮有這麼一個場景,根據使用者輸入的url的不同,調用不同的函數,實作不同的操作,也就是一個url路由器的功能,這在web架構裡是核心部件之一。下面有一個精簡版的示例:
首先,有一個commons子產品,它裡面有幾個函數,分别用于展示不同的頁面,代碼如下:
def login():
print("這是一個登陸頁面!")
def logout():
print("這是一個退出頁面!")
def home():
print("這是網站首頁面!")
其次,有一個visit子產品,作為程式入口,接受使用者輸入,展示相應的頁面,代碼如下:(這段代碼是比較初級的寫法)
import commons
def run():
inp = input("請輸入您想通路頁面的url: ").strip()
if inp == "login":
commons.login()
elif inp == "logout":
commons.logout()
elif inp == "home":
commons.home()
else:
print("404")
if __name__ == '__main__':
run()
我們運作visit.py,輸入:home,頁面結果如下:
請輸入您想通路頁面的url: home
這是網站首頁面!
這就實作了一個簡單的WEB路由功能,根據不同的url,執行不同的函數,獲得不同的頁面。
然而,讓我們考慮一個問題,如果commons子產品裡有成百上千個函數呢(這非常正常)?。難道你在visit子產品裡寫上成百上千個elif?顯然這是不可能的!那麼怎麼破?
二、反射機制
仔細觀察visit中的代碼,我們會發現使用者輸入的url字元串和相應調用的函數名好像!如果能用這個字元串直接調用函數就好了!但是,前面我們已經說了字元串是不能用來調用函數的。為了解決這個問題,python為我們提供一個強大的内置函數:getattr!我們将前面的visit修改一下,代碼如下:
import commons
def run():
inp = input("請輸入您想通路頁面的url: ").strip()
func = getattr(commons,inp)
func()
if __name__ == '__main__':
run()
首先說明一下getattr函數的使用方法:它接收2個參數,前面的是一個對象或者子產品,後面的是一個字元串,注意了!是個字元串!
例子中,使用者輸入儲存在inp中,這個inp就是個字元串,getattr函數讓程式去commons這個子產品裡,尋找一個叫inp的成員(是叫,不是等于),這個過程就相當于我們把一個字元串變成一個函數名的過程。然後,把獲得的結果指派給func這個變量,實際上func就指向了commons裡的某個函數。最後通過調用func函數,實作對commons裡函數的調用。這完全就是一個動态通路的過程,一切都不寫死,全部根據使用者輸入來變化。
執行上面的代碼,結果和最開始的是一樣的。
這就是python的反射,它的核心本質其實就是利用字元串的形式去對象(子產品)中操作(查找/擷取/删除/添加)成員,一種基于字元串的事件驅動!
這段話,不一定準确,但大概就是這麼個意思。
三、進一步完善
上面的代碼還有個小瑕疵,那就是如果使用者輸入一個非法的url,比如jpg,由于在commons裡沒有同名的函數,肯定會産生運作錯誤,具體如下:
請輸入您想通路頁面的url: jpg
Traceback (most recent call last):
File "F:/Python/pycharm/s13/reflect/visit.py", line 16, in <module>
run()
File "F:/Python/pycharm/s13/reflect/visit.py", line 11, in run
func = getattr(commons,inp)
AttributeError: module 'commons' has no attribute 'jpg'
那怎麼辦呢?其實,python考慮的很全面了,它同樣提供了一個叫hasattr的内置函數,用于判斷commons中是否具有某個成員。我們将代碼修改一下:
import commons
def run():
inp = input("請輸入您想通路頁面的url: ").strip()
if hasattr(commons,inp):
func = getattr(commons,inp)
func()
else:
print("404")
if __name__ == '__main__':
run()
通過hasattr的判斷,可以防止非法輸入錯誤,并将其統一定位到錯誤頁面。
其實,研究過python内置函數的朋友,應該注意到還有delattr和setattr兩個内置函數。從字面上已經很好了解他們的作用了。