天天看點

Python實作類似Java的反射

反射是通過字元串的形式操作對象的相關成員

反射也就是通過字元串的形式,導入子產品,通過字元串的形式去子產品中尋找指定函數,并執行。

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兩個内置函數。從字面上已經很好了解他們的作用了。