目錄
- 1. 反射的四個函數(shù)
- 2. 類的反射操作
- 3. 當前模塊的反射操作
- 4. 其他模塊反射操作
- 5. 反射應用場景之一
- 6. 反射應用場景之二
- 7. 總結
通常,我們操作對象的屬性或者方法時,是通過點“.”操作符進行的。例如下面的代碼:
class Person:
type = "mammal"
def __init__(self, name):
self.name = name
def say_hi(self):
print('Hello, my name is', self.name)
@staticmethod
def feed():
print("Three times per day.")
@classmethod
def sleep(cls):
print("8 hours!")
p = Person('Chunming')
p.say_hi()
print(p.name)
上面代碼的輸出是
Hello, my name is Nikhil
Nikhil
反射是另外一種操作對象屬性和方法的手段,例如:
func = getattr(p, 'say_hi')
func()
print(getattr(p, "name"))
上面這段代碼的輸出是:
Hello, my name is Nikhil
Nikhil
可見與通過點操作符的結果一致。
1. 反射的四個函數(shù)
getattr是獲取對象屬性或方法的函數(shù),Python的官方文檔是這樣描述其用法的:
getattr(object, name, value)
返回對象命名屬性的值。name必須是字符串。如果該字符串是對象的屬性之一,則返回該屬性的值。例如, getattr(x, ‘foobar')等同于 x.foobar。如果指定的屬性不存在,且提供了 default值,則返回它,否則觸發(fā) AttributeError。
根據(jù)文檔理解上述代碼,getattr(p, ‘say_hi') 獲取了p對象的say_hi屬性值并賦值給func變量,因為say_hi屬性在Person類中是一個方法,要想調(diào)用這個方法, 需要執(zhí)行func(),getattr(p, “name”) 則是獲取p對象的name屬性。
除了獲取對象屬性和方法的getattr函數(shù),python還內(nèi)置了判斷、設置、刪除對象屬性和方法的函數(shù),來看看Python官方文檔對這三個函數(shù)的說明:
setattr(object, name, value)
此函數(shù)與 getattr() 兩相對應。其參數(shù)為一個對象、一個字符串和一個任意值。字符串指定一個現(xiàn)有屬性或者新增屬性。函數(shù)會將值賦給該屬性,只要對象允許這種操作。例如,setattr(x, ‘foobar', 123) 等價于 x.foobar = 123。
hasattr(object, name)
該實參是一個對象和一個字符串。如果字符串是對象的屬性之一的名稱,則返回 True,否則返回 False。(此功能是通過調(diào)用 getattr(object, name) 看是否有 AttributeError 異常來實現(xiàn)的。)
delattr(object, name)
setattr() 相關的函數(shù)。實參是一個對象和一個字符串。該字符串必須是對象的某個屬性。如果對象允許,該函數(shù)將刪除指定的屬性。例如 delattr(x, ‘foobar') 等價于 del x.foobar 。
Python中通過getattr、setattr、hasattr和delattr四個函數(shù)操作屬性的機制就是反射。是通過字符串的形式操作對象屬性和方法的機制。下面對p屬性應用setattr、hasattr和delattr這三個函數(shù)看看效果:
判斷p對象是否有say_bye屬性和say_hi屬性:
print(hasattr(p, 'say_bye')) # 輸出False
print(hasattr(p, 'say_hi')) # 輸出True
給p對象增加say_bye的方法和age屬性:
setattr(p, 'say_bye', say_bye)
setattr(p, 'age', 18)
現(xiàn)在可以訪問這兩個屬性了,通過反射訪問:
bye = getattr(p, 'say_bye')
bye()
print(getattr(p, 'age'))
或者通過點操作符訪問:
刪除age屬性:
delattr(p, 'age')
print(hasattr(p, 'age')) # 輸出False
2. 類的反射操作
除了對象的反射操作,還有類的反射操作,當前模塊的反射操作,還有其他模塊的反射操作,其他包的反射操作。
類的反射操作,指的是對類屬性、類方法或者靜態(tài)方法執(zhí)行反射操作。
獲取類屬性:
t = getattr(Person, 'type')
print(t) # 輸出mammal
f = getattr(Person, 'feed')
f() # 輸出Three times per day.
s = getattr(Person, 'sleep')
s() # 8 hours!
判斷類屬性是否存在:
print(hasattr(Person, 'type')) # 輸出True
print(hasattr(Person, 'name')) # 輸出False
print(hasattr(Person, 'say_hi')) # 輸出True
print(hasattr(Person, 'sleep')) # 輸出True
print(hasattr(Person, 'feed')) # 輸出True
此外,還可以對類添加和刪除屬性和方法。
print(delattr(Person, 'feed'))
print(hasattr(Person, 'feed'))
setattr(Person, 'feed', lambda x: print("Three times per day."))
print(hasattr(Person, 'feed'))
3. 當前模塊的反射操作
當前模塊也就是當前代碼所在的py文件,反射也可以對當前模塊中的變量和函數(shù)進行操作。例如某個模塊包含一個al函數(shù),用來判斷迭代對象中每個元素是否都是True,內(nèi)容如下:
import sys
def al(iterable):
for element in iterable:
if not element:
return False
return True
this_module = sys.modules[__name__]
if hasattr(this_module, 'al'):
all_true = getattr(this_module, 'al')
result = all_true([1, 2, 3, 4, True, 0])
print(result)
通過sys.modules[name]方法獲取當前模塊的名稱。getattr第一個參數(shù)是模塊名稱,第二個參數(shù)是想要從模塊中獲取的屬性。
4. 其他模塊反射操作
對import進來的其他模塊中的函數(shù)、屬性、變量進行反射操作。例如,我們導入Python的heapq模塊,這塊模塊提供了堆隊列算法的實現(xiàn),也稱為優(yōu)先隊列算法。下面的代碼是一個實現(xiàn)堆排序的函數(shù)。
import heapq
h = [(5, 'write code'), (7, 'release product'), (1, 'write spec'), (3, 'create tests')]
if hasattr(heapq, 'heapify'):
heapi = getattr(heapq, 'heapify') # 獲取heapify屬性
heapi(h) # 建堆
if hasattr(heapq, 'heappop'):
heapp = getattr(heapq, 'heappop') # 獲取heappop屬性
print([heapp(h) for _ in range(len(h))]) # 彈出并從返回堆中最小的項
這里,我們并沒有通過heapq.heapify和heapq.heappop方式調(diào)用heapq模塊中的函數(shù)。而是通過反射達到的同樣的效果。
5. 反射應用場景之一
了解了反射中四個函數(shù)的基本用法。那么反射到底有什么用呢?它的應用場景是什么呢?答案是,當不確定所需要的屬性和函數(shù)是否存在時,可以使用反射。另外一個重要作用是,可以提高代碼的擴展性和可維護性。
假如我們把所有的加密算法都放到一個叫做encryption的模塊中維護 ,并且允許使用這個模塊的用戶添加更多的加密算法到這個模塊中。encryption的模塊內(nèi)容如下:
import hashlib
import os
import sys
def md5(content=None):
"""生成字符串的SHA256值"""
if content is None:
return ''
md5_gen = hashlib.md5()
md5_gen.update(content.encode('utf-8'))
md5code = md5_gen.hexdigest()
return md5code
def sha256(content=None):
"""生成字符串的SHA256值"""
if content is None:
return ''
sha256_gen = hashlib.sha256()
sha256_gen.update(content.encode('utf-8'))
sha256code = sha256_gen.hexdigest()
return sha256code
def sha256_file(filename):
"""生成文件的SHA256值"""
if not os.path.isfile(filename):
return ""
sha256gen = hashlib.sha256()
size = os.path.getsize(filename) # 獲取文件大小,單位是Byte
with open(filename, 'rb') as fd: # 以二進制方式讀取文件
while size >= 1024 * 1024: # 當文件大于1MB時分塊讀取文件內(nèi)容
sha256gen.update(fd.read(1024 * 1024))
size -= 1024 * 1024
sha256gen.update(fd.read())
sha256code = sha256gen.hexdigest()
return sha256code
def md5_file(filename):
"""生成文件的MD5值"""
if not os.path.isfile(filename):
return ""
md5gen = hashlib.md5()
size = os.path.getsize(filename) # 獲取文件大小,單位是Byte
with open(filename, 'rb') as fd:
while size >= 1024 * 1024: # 當文件大于1MB時分塊讀取文件內(nèi)容
md5gen.update(fd.read(1024 * 1024))
size -= 1024 * 1024
md5gen.update(fd.read())
md5code = md5gen.hexdigest()
return md5code
def encrypt_something(something, algorithm):
"""
通用加密算法
:param something: 待加密的內(nèi)容,字符串或者文件
:param algorithm: 加密算法
:return: 加密后的內(nèi)容
"""
result = ""
if algorithm == "md5":
result = md5(something)
elif algorithm == "sh256":
result = sha256(something)
elif algorithm == "sh256_file":
result = sha256_file(something)
elif algorithm == "md5_file":
result = md5_file(something)
return result
其中,encrypt_something函數(shù)提供了通用加密算法,需要調(diào)用者傳入待加密的內(nèi)容和加密算法,這樣當調(diào)用者使用encryption.py模塊時,只需導入encrypt_something函數(shù)即可。就像這樣:
import encryption
print(encryption.encrypt_something("learn_python_by_coding", "sh256"))
print(encryption.encrypt_something("learn_python_by_coding", "md5"))
通過分析encrypt_something函數(shù)發(fā)現(xiàn),當我們在encryption.py模塊添加更多的加密算法后,就要修改encrypt_something函數(shù),在其中增加新的if分支,隨著加密算法的增加,encrypt_something函數(shù)的分支會越來越多。
學了反射之后,encrypt_something代碼部分就可以這樣寫:
def encrypt_something(something, algorithm):
"""
通用加密算法
:param something: 待加密的內(nèi)容,字符串或者文件
:param algorithm: 加密算法
:return: 加密后的內(nèi)容
"""
this_module = sys.modules[__name__]
if hasattr(this_module, algorithm):
algorithm = getattr(this_module, algorithm)
result = algorithm(something)
else:
raise ValueError("Not support {} algorithm".format(algorithm))
return result
相比前面的采用if分支語句方式,反射更加簡潔明了,可維護性更強,要想增加新的加密方法,只需要在encryption.py模塊添加更多的加密算法即可,encrypt_something代碼不需要任何變更。
6. 反射應用場景之二
再看一個基于Pytest測試框架的測試腳本中應用反射的例子,比如conftest文件內(nèi)容如下:
# content of conftest.py
import pytest
import smtplib
@pytest.fixture(scope="module")
def smtp_connection(request):
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
smtp_connection = smtplib.SMTP(server, 587, timeout=5)
yield smtp_connection
print("finalizing {} ({})".format(smtp_connection, server))
smtp_connection.close()
request.module 是所有測試腳本,就是那些以_test結尾或者test_開頭的py文件。
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
含義就是從測試腳本文件中找smtpserver屬性,如果找不到,默認使用smtp.gmail.com作為smtpserver的值。如果測試腳本文件有這個屬性,則使用測試腳本中的值,例如下面這個測試腳本,smtpserver則會使用mail.python.org這個值:
# content of test_anothersmtp.py
smtpserver = "mail.python.org" # will be read by smtp fixture
def test_showhelo(smtp_connection):
assert 0, smtp_connection.helo()
7. 總結
在很多開源框架中普遍采用,是提高可維護性和擴展性的利器。如果工作中也涉及到框架開發(fā),反射一定會助一臂之力,,希望大家以后多多支持腳本之家!
您可能感興趣的文章:- python對驗證碼降噪的實現(xiàn)示例代碼
- 爬蟲Python驗證碼識別入門
- Python機器學習入門(一)序章
- 6個Python辦公黑科技,助你提升工作效率
- Python機器學習入門(三)之Python數(shù)據(jù)準備
- 用python寫個顏值評分器篩選最美主播
- Python代碼實現(xiàn)粒子群算法圖文詳解
- 我用Python做個AI出牌器斗地主把把贏
- python通過PyQt5實現(xiàn)登錄界面的示例代碼
- Python圖片驗證碼降噪和8鄰域降噪