欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談Python的自省Introspection和反射機(jī)制Reflection

 更新時(shí)間:2023年08月21日 09:25:12   作者:天元浪子  
這篇文章主要介紹了淺談Python的自省Introspection和反射機(jī)制Reflection,反射就是通過字符串的形式去對(duì)象(模塊)中操作(查找/獲取/刪除/添加)成員,一種基于字符串的事件驅(qū)動(dòng),需要的朋友可以參考下

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)文章

最新評(píng)論