python 反射机制
一、前言
| 1 2 3 4 5 | def?f1(): ????print("f1是這個函數的名字!") ? s?=?"f1" print("%s是個字符串"?%?s) |
在上面的代碼中,我們必須區分兩個概念,f1和“f1"。前者是函數f1的函數名,后者只是一個叫”f1“的字符串,兩者是不同的事物。我們可以用f1()的方式調用函數f1,但我們不能用"f1"()的方式調用函數。說白了就是,不能通過字符串來調用名字看起來相同的函數!
二、web實例
考慮有這么一個場景,根據用戶輸入的url的不同,調用不同的函數,實現不同的操作,也就是一個url路由器的功能,這在web框架里是核心部件之一。下面有一個精簡版的示例:
首先,有一個commons模塊,它里面有幾個函數,分別用于展示不同的頁面,代碼如下:
| 1 2 3 4 5 6 7 8 9 10 | def?login(): ????print("這是一個登陸頁面!") ? ? def?logout(): ????print("這是一個退出頁面!") ? ? def?home(): ????print("這是網站主頁面!") |
其次,有一個visit模塊,作為程序入口,接受用戶輸入,展示相應的頁面,代碼如下:(這段代碼是比較初級的寫法)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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,頁面結果如下:
| 1 2 | 請輸入您想訪問頁面的url:? home 這是網站主頁面! |
這就實現了一個簡單的WEB路由功能,根據不同的url,執行不同的函數,獲得不同的頁面。
然而,讓我們考慮一個問題,如果commons模塊里有成百上千個函數呢(這非常正常)?。難道你在visit模塊里寫上成百上千個elif?顯然這是不可能的!那么怎么破?
三、反射機制
仔細觀察visit中的代碼,我們會發現用戶輸入的url字符串和相應調用的函數名好像!如果能用這個字符串直接調用函數就好了!但是,前面我們已經說了字符串是不能用來調用函數的。為了解決這個問題,python為我們提供一個強大的內置函數:getattr!我們將前面的visit修改一下,代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 | 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里沒有同名的函數,肯定會產生運行錯誤,具體如下:
| 1 2 3 4 5 6 7 | 請輸入您想訪問頁面的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中是否具有某個成員。我們將代碼修改一下:
| 1 2 3 4 5 6 7 8 9 10 11 12 | 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兩個內置函數。從字面上已經很好理解他們的作用了。
python的四個重要內置函數:getattr、hasattr、delattr和setattr較為全面的實現了基于字符串的反射機制。他們都是對內存內的模塊進行操作,并不會對源文件進行修改。
五、基礎回顧
hasattr
判斷對象中是否有這個方法或變量
class Person(object):def __init__(self,name):self.name = namedef 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 = namedef talk(self):print("%s正在交談"%self.name) p = Person("laowang")n = getattr(p,"name") # 獲取name變量的內存地址 print(n) # 此時打印的是:laowangf = 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 = namep = 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 = namedef talk(self):print("%s正在交談"%self.name)p = Person("laowang")delattr(p,"name") # 刪除name變量 print(p.name) # 此時將報錯參考自:
https://www.cnblogs.com/kongk/p/8645202.html?
總結
以上是生活随笔為你收集整理的python 反射机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux之trap命令
- 下一篇: python并发编程3-进程