淺談Python的自省Introspection和反射機(jī)制Reflection
1. 從dir()函數(shù)說起
對(duì)于dir()這個(gè)Python的內(nèi)置函數(shù),Python進(jìn)階群里的小伙伴們一定不陌生。
我不止一次地介紹過這個(gè)函數(shù)。
每當(dāng)想要了解一個(gè)類或類實(shí)例包含了什么屬性和方法時(shí),我都會(huì)求助于這個(gè)函數(shù)。
a = [3,4,5] type(a) # 返回a的類型,結(jié)果是list類 <class 'list'> dir(a) # 返回list類實(shí)例對(duì)象a包含的屬性和方法 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] dir(list) # 返回list類a包含的屬性和方法 ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
對(duì)于模塊、內(nèi)置函數(shù),以及自定義的類,dir()一視同仁,照樣可用。
import math dir(math) # 返回math模塊包含的子項(xiàng)(子模塊、類、函數(shù)、常量等) ['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'ceil', 'copysign', 'cos', 'cosh', 'degrees', 'e', 'erf', 'erfc', 'exp', 'expm1', 'fabs', 'factorial', 'floor', 'fmod', 'frexp', 'fsum', 'gamma', 'gcd', 'hypot', 'inf', 'isclose', 'isfinite', 'isinf', 'isnan', 'ldexp', 'lgamma', 'log', 'log10', 'log1p', 'log2', 'modf', 'nan', 'pi', 'pow', 'radians', 'remainder', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'tau', 'trunc'] dir(max) # 返回內(nèi)置函數(shù)的內(nèi)建子項(xiàng) ['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']
讀到這里,一定會(huì)有很多小伙伴會(huì)說,我的PyCharm(也可能是VSCode或者其他什么)也會(huì)告訴我,當(dāng)前的對(duì)象有什么屬性和方法,還是自動(dòng)顯示的,不需要我動(dòng)手。
沒錯(cuò),IDE的確為我們提供了很多便利,但是,你有沒有想過IDE是如何實(shí)現(xiàn)這些功能的呢?
假如你的任務(wù)就是設(shè)計(jì)一款類似的IDE,你真的不要深入理解Python內(nèi)在的機(jī)制嗎?
2. 內(nèi)建屬性和方法
下面的代碼中,類Player定義了兩個(gè)屬性和一個(gè)方法,p是Player的一個(gè)實(shí)例。
調(diào)用dir()顯示實(shí)例p的屬性和方法,就會(huì)發(fā)現(xiàn),除了代碼中定義name,rating和say_hello()外,其他都是以雙下劃線開頭、以雙下劃線結(jié)尾,這些就是傳說中的Python對(duì)象的內(nèi)建屬性和方法。
class Player: """玩家類""" def __init__(self, name, rating=1800): self.name = name self.rating = rating def say_hello(self): """自報(bào)姓名和等級(jí)分""" print('大家好!我是棋手%s,目前等級(jí)分%d分。'%(self.name, self.rating)) p = Player('天元浪子') > p.say_hello() 大家好!我是棋手天元浪子,目前等級(jí)分1800分。 for item in dir(p): print(item) __class__ __delattr__ __dict__ __dir__ __doc__ __eq__ __format__ __ge__ __getattribute__ __gt__ __hash__ __init__ __init_subclass__ __le__ __lt__ __module__ __ne__ __new__ __reduce__ __reduce_ex__ __repr__ __setattr__ __sizeof__ __str__ __subclasshook__ __weakref__ name rating say_hello
這些內(nèi)建屬性和方法中,似乎只有__init__和__new__看起來有點(diǎn)面熟,其他那些都有什么用途呢?
下面,我選其中的幾個(gè)演示一下。
2.1 _ doc _
__doc__是最常用的內(nèi)建屬性,有很多小伙伴并沒有意識(shí)到這一點(diǎn)。
一個(gè)規(guī)范的代碼文件,除了代碼本身,還會(huì)提供很多必要信息,比如類、函數(shù)的說明,這些說明,我們稱其為文檔字符串(DocString)。
__doc__就是對(duì)象的文檔字符串。
Player.__doc__ '玩家類' p.__doc__ '玩家類' p.say_hello.__doc__ '自報(bào)姓名和等級(jí)分'
這里顯示的文檔字符串,就是我在定義Player時(shí)寫在特定位置的注釋(沒有注意到這一點(diǎn)的小伙伴,請(qǐng)返回查看前面的Player類定義代碼)。
2.2 _ module _
很容易猜到,內(nèi)建屬性__mudule__表示對(duì)象所屬的模塊。
這里,Player類及其實(shí)例,都是當(dāng)前__main__模塊。
如我們引入一個(gè)模塊,更容易說明__module__的含義。
Player.__module__ '__main__' p.__module__ '__main__' p.say_hello.__module__ '__main__' import math math.sin.__module__ 'math'
2.3 _ dict _
內(nèi)建屬性__dict__,是一個(gè)由對(duì)象的屬性鍵值對(duì)構(gòu)成的字典。
類的__dict__和類實(shí)例的__dict__有不同的表現(xiàn)。
p.__dict__ {'name': '天元浪子', 'rating': 1800} Player.__dict__ mappingproxy({'__module__': '__main__', '__doc__': '玩家類', '__init__': <function Player.__init__ at 0x000002578CF399D8>, 'say_hello': <function Player.say_hello at 0x000002578CF39A68>, '__dict__': <attribute '__dict__' of 'Player' objects>, '__weakref__': <attribute '__weakref__' of 'Player' objects>})
2.4 _ class _
通過類的實(shí)例化,可以得到一個(gè)類實(shí)例。那么如何從一個(gè)類實(shí)例,逆向得到類呢?實(shí)際上,類實(shí)例的內(nèi)建屬性__class__就是類。我們完全可以用一個(gè)實(shí)例的__class__去初始化另一個(gè)實(shí)例。
pp = p.__class__('零下八段', 2100) pp.say_hello() 大家好!我是棋手零下八段,目前等級(jí)分2100分。
2.5 _ dir _
dir()函數(shù)是Python的內(nèi)置函數(shù),內(nèi)建方法__dir__類似于dir()函數(shù)。。
p.__dir__() ['name', 'rating', '__module__', '__doc__', '__init__', 'say_hello', '__dict__', '__weakref__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
2.6 _ getattribute _
顧名思義,__getattribute__返回對(duì)象的屬性——實(shí)際上是屬性或方法。
這是一個(gè)內(nèi)建方法,使用的時(shí)候其后必須有圓括號(hào),參數(shù)是指定的屬性或方法的名字。
p.__getattribute__('name') '天元浪子' p.__getattribute__('rating') 1800 p.__getattribute__('say_hello') <bound method Player.say_hello of <__main__.Player object at 0x000002578CF2CA88>> p.__getattribute__('say_hello')() 大家好!我是棋手天元浪子,目前等級(jí)分1800分。
3. 動(dòng)態(tài)加載及調(diào)用
學(xué)習(xí)任何一門編程語言的初級(jí)階段,我們幾乎都會(huì)遇到一個(gè)共同的問題:動(dòng)態(tài)創(chuàng)建一個(gè)變量或?qū)ο蟆?/p>
在這里,“動(dòng)態(tài)”只是強(qiáng)調(diào)變量或?qū)ο竺Q不是由程序員決定,而是由另外的參與方(比如交互程序中的操作者,C/S或B/S程序中的客戶端)決定。
也許不是一個(gè)準(zhǔn)確的說法,但我想不出一個(gè)更好的詞匯來表述此種應(yīng)用需求。
以Python為例:從鍵盤上讀入一個(gè)字符串,以該字符串為名創(chuàng)建一個(gè)整型對(duì)象,令其值等于3。
通常,這樣的問題我們使用exec()函數(shù)就可以解決。
為什么不是eval()函數(shù)呢?
eval()函數(shù)僅是計(jì)算一個(gè)字符串形式的表達(dá)式,無法完成賦值操作。
var_name = input('請(qǐng)輸入整型對(duì)象名:') 請(qǐng)輸入整型對(duì)象名:x exec('%s=3'%var_name) x 3
理解了“動(dòng)態(tài)”的概念,我們來看看如何動(dòng)態(tài)加載模塊、如何動(dòng)態(tài)調(diào)用對(duì)象等
3.1 動(dòng)態(tài)加載模塊
按照Python編碼規(guī)范,腳本文件一般會(huì)在編碼格式聲明和文檔說明之后統(tǒng)一導(dǎo)入模塊。
有些情況下,代碼需要根據(jù)程序運(yùn)行時(shí)的具體情況,臨時(shí)導(dǎo)入相應(yīng)的模塊——通常,這種情況下,導(dǎo)入的模塊命是由一個(gè)字符串指定的。
下面的代碼給出了動(dòng)態(tài)加載模塊的實(shí)例。
os.getcwd() # 此時(shí)沒有導(dǎo)入os模塊,所以拋出異常 Traceback (most recent call last): File "<pyshell#158>", line 1, in <module> os.getcwd() NameError: name 'os' is not defined os = __import__('os') # 動(dòng)態(tài)導(dǎo)入'os'模塊 os.getcwd() 'C:\\Users\\xufive\\AppData\\Local\\Programs\\Python\\Python37'
3.2 通過對(duì)象名取得對(duì)象
這個(gè)需求聽起來有點(diǎn)奇怪,但也有很多人會(huì)遇到。Player類實(shí)例p為例,如果我們只有字符串’p’,怎樣才能得到p實(shí)例呢?我們知道內(nèi)置函數(shù)globals()返回全局的對(duì)象字典,locals()返回所處層次的對(duì)象字典,這兩個(gè)字典的鍵就是對(duì)象名的字符串。有了這個(gè)思路,就很容易通過對(duì)象名取得對(duì)象了。
obj = globals().get('p', None) obj <__main__.Player object at 0x000002578CF2CA88> obj.say_hello() 大家好!我是棋手天元浪子,目前等級(jí)分1800分。
3.3 動(dòng)態(tài)調(diào)用對(duì)象
動(dòng)態(tài)調(diào)用對(duì)象最典型的應(yīng)用是服務(wù)接口的實(shí)現(xiàn)。假如客戶端通過發(fā)送服務(wù)的名字字符串來調(diào)用服務(wù)端的一個(gè)服務(wù),名字字符串和服務(wù)有者一一對(duì)應(yīng)的關(guān)系。如果沒有動(dòng)態(tài)調(diào)用,代碼恐怕就得寫成下面這個(gè)樣子。
if cmd == 'service_1': serv.service_1() elif cmd == 'service_2': serv.service_2() elif cmd == 'service_3': serv.service_3() ... ...
下面的代碼,演示了服務(wù)端如何根據(jù)接收到的命令動(dòng)態(tài)調(diào)用對(duì)應(yīng)的服務(wù)。
class ServiceDemo: def service_1(self): print('Run service_1...') def service_2(self): print('Run service_2...') def onconnect(self, cmd): if hasattr(self, cmd): getattr(self, cmd)() else: print('命令錯(cuò)誤') serv = ServiceDemo() serv.onconnect('service_1') Run service_1... serv.onconnect('service_2') Run service_2... serv.onconnect('hello') 命令錯(cuò)誤
4. 自省和反射機(jī)制
是時(shí)候說說自省和反射了。但是,截止到這里,我已經(jīng)把自省和反射全部講完了,只是沒有使用自省和反射這兩個(gè)詞罷了。僅從這一點(diǎn),就可以說明,自省和反射是完全多余的概念。如果有小伙伴搞不清楚這兩個(gè)概念,那也完全沒有關(guān)系,一點(diǎn)兒都不會(huì)影響你對(duì)編程的理解。
所謂的自省,就是對(duì)象自身提供可以查看自身屬性、方法、類型的手段。內(nèi)建方法__dir__不正是對(duì)象的自省嗎?另外,內(nèi)置函數(shù)dir()、type()、isinstance()都可以提供類似自省的部分或全部功能。
反射機(jī)制是Java和PHP等語言提供的一個(gè)特性,準(zhǔn)確描述起來有些費(fèi)勁,簡(jiǎn)而言之,就是在運(yùn)行態(tài)可以獲取對(duì)象的屬性和方法,并隨時(shí)調(diào)用他們,最典型的應(yīng)用就是通過字符串形式的對(duì)象名獲取對(duì)象。這不就是我說的“動(dòng)態(tài)加載和調(diào)用”嗎?
寫道這里,不由地再次致敬龜叔當(dāng)年的遠(yuǎn)見卓識(shí):早在Java誕生前好多年,龜叔就已經(jīng)全面地規(guī)劃了Python對(duì)象的內(nèi)建機(jī)制,其前瞻性遠(yuǎn)遠(yuǎn)超過了自省和反射機(jī)制。
到此這篇關(guān)于淺談Python的自省Introspection和反射機(jī)制Reflection的文章就介紹到這了,更多相關(guān)Python的自省和反射內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python中實(shí)現(xiàn)k-means聚類算法詳解
這篇文章主要介紹了python中實(shí)現(xiàn)k-means聚類算法詳解,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11Python可以實(shí)現(xiàn)棧的結(jié)構(gòu)嗎
在本篇文章里小編給各位整理的是關(guān)于Python實(shí)現(xiàn)棧的結(jié)構(gòu)的條件的相關(guān)知識(shí)點(diǎn),有需要的朋友們可以學(xué)習(xí)下。2020-05-05Python實(shí)現(xiàn)PDF掃描件生成DOCX或EXCEL功能
這篇文章主要介紹了如何利用Python實(shí)現(xiàn)將PDF掃描件轉(zhuǎn)為DOCX或EXCEL文件格式功能,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2022-03-03Python?time時(shí)間格式化和設(shè)置時(shí)區(qū)實(shí)現(xiàn)代碼詳解
這篇文章主要介紹了Python?time時(shí)間格式化和設(shè)置時(shí)區(qū)實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值2023-02-02python實(shí)現(xiàn)根據(jù)主機(jī)名字獲得所有ip地址的方法
這篇文章主要介紹了python實(shí)現(xiàn)根據(jù)主機(jī)名字獲得所有ip地址的方法,涉及Python解析IP地址的相關(guān)技巧,需要的朋友可以參考下2015-06-065分鐘快速掌握Python定時(shí)任務(wù)框架的實(shí)現(xiàn)
這篇文章主要介紹了5分鐘快速掌握 Python 定時(shí)任務(wù)框架,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01