第四周:Python反射
阿新 • • 發佈:2020-12-04
反射(reflection):指的是在執行時獲取型別的定義資訊。
本質:利用字串的形式去物件(模組)中操作(查詢/獲取/刪除/新增)成員,是一種基於字串的事件驅動。
簡單來說,Python可以通過字串來操作物件(類/方法/模組的)屬性和方法(也可操作類),這就是Python的反射。
在Python中例項化物件、類、當前模組、其他模組可以使用反射機制。
Python反射的四個方法
getattr(obj, name,default):獲取指定物件的屬性
-
從物件和類中獲取屬性/方法
-
class A: id = 1 def eat(self): print("吃") a = A() # 分別從物件和類中獲取屬性/方法 print(getattr(A, "id")) # 1 print(getattr(a, "id")) # 1 print(getattr(A, "eat")) # <function A.eat at 0x0000021D287F5620> print(getattr(a, "eat")) # <bound method A.eat of <__main__.A object at 0x00000130DEC926A0>> e1 = getattr(A, "eat") print(e1, type(e1)) # <function A.eat at 0x000001D1391E6620> <class 'function'> 型別是方法 e1(a) # 放一個物件進去就可以執行該方法了,其實就是A().eat() e2 = getattr(a, "eat") print(e2, type(e2)) # <bound method A.eat of <__main__.A object at 0x0000021D28802748>> <class 'method'> 型別是函式 e2() # 從物件中獲取方法就可以直接執行該方法了
-
-
從其他模組獲取屬性和方法和類
-
# test.py class B: name = "這是類B" def run(self): print("跑") num = 10 def count(): print("加加加")
-
import test # 獲取其他模組中的屬性和方法 print(getattr(test, "num")) # 10 print(getattr(test, "count")) # <function count at 0x000001EE59ED6620> print(getattr(test, "B")) # <class 'test.B'> # print(getattr(test, "B.name")) # 這樣就會報錯 # print(getattr(test, "B().name")) # 這樣也會報錯 b = getattr(test, "B")() # 例項化這個獲取的類 # 這樣就可以獲取到其中的方法和屬性了 print(getattr(b, "name")) # 這是類B print(getattr(b, "run")) # <bound method B.run of <test.B object at 0x000001F4BBA42940>>
-
-
從當前模組獲取屬性和方法和類
-
# 當前模組的反射本體就是當前模組的檔案 import sys str = "abc" def say(s="cba")->str: print(s) class C: pass obj = sys.modules[__name__] # 這樣就獲得到了當前模組 print(getattr(obj, "str")) # abc print(getattr(obj, "say")) # <function say at 0x000001F455622E18> print(getattr(obj, "C")) # <class '__main__.C'>
-
hasattr(obj, name):判斷物件是否有對應的屬性
-
class A: id = 1 def eat(self): print("吃") a = A() print(hasattr(A, "id")) # True print(hasattr(A, "eat")) # True print(hasattr(a, "eat")) # True print(hasattr(A, "name")) # False print(hasattr(A, "say")) # False print(hasattr(a, "A")) # False
-
其他模組和當前模組判斷有沒有指定屬性和方法安照上面來就行,很簡單。
setattr(obj, name, value):設定指定類/物件的屬性和方法
-
class A: id = 1 def eat(self): print("吃") # 向類中設定屬性 setattr(A, "name", "哈哈哈") a = A() print(A.name) # 哈哈哈 print(a.name) # 哈哈哈 # 注意這裡!!!!!!!!!!!! # 要想把函式放進類中,一定要給一個引數!用於表示呼叫類中方法的例項物件 def drink(self): print("喝", self, type(self)) # 向類中設定方法 這裡取名drink0只為了區別drink函式本身,其實去掉0也沒事 setattr(A, "drink0", drink) print(getattr(A, "drink0")) # <function drink at 0x0000018830F767B8> print(a.drink0) # <bound method drink of <__main__.A object at 0x0000018830F82D68>> a.drink0() # 喝 <__main__.A object at 0x0000018830F82D68> <class '__main__.A'>
-
def func(): text = "文字" print("hhh") # 向方法中新增 setattr(func, "word", "一段話") # print(func.text) # 這樣會拋異常,沒有該屬性! print(func.word) # 一段話
-
# 那麼就可以進一步這樣寫 def func(): text = "文字" print("hhh") if(hasattr(func, "word")): # 列印後續傳進來的變數 print(getattr(func, "word")) print("*******") func() print("*******") # 設定新的 setattr(func, "word", "一段話") # print(func.text) # 這樣會拋異常,說沒有該屬性! print(func.word) # 一段話 print("-------") func() print("-------") """ 列印結果: ******* hhh ******* 一段話 ------- hhh 一段話 ------- """
-
有點Java反射的感覺了。可以在執行時新增和獲取指定類或指定方法等
-
import test class A: id = 1 def eat(self): print("吃") # 給別的模組新增一個新的類 setattr(test, "A", A) # 這時可以例項化這個新新增的類了 a = test.A() a.eat() # 吃
-
# 甚至可以新增初始化方法 def __init__(self, name="預設名"): print("這是初始化方法") self.name = name setattr(A, "__init__", __init__) a = A("哈哈哈") print(a.name) """ 這是初始化方法 哈哈哈 """
-
delattr(object, name):刪除物件的指定屬性/方法/類
# 刪除上面剛給test模組新增的類A
delattr(test, "A")
a2 = test.A() # 拋異常
刪除操作也好理解。
類中私有屬性的獲取和設定
class C:
def __init__(self):
self.__name = "CCC"
def get_id(self):
print(self.__id) # 假裝可以設定私有方法,其實不行
def print_page(self):
if(hasattr(C.print_page, "page")): # 有這個屬性時打印出來
print("page:", getattr(C.print_page, "page"))
c = C()
給__init__
方法新增的屬性,算作區域性變數而不是類的屬性
page = 10
setattr(C.__init__, "self.page", page)
setattr(C.__init__, "page", page)
print(hasattr(c, "self.page")) # False
print(hasattr(c, "page")) # False
print(hasattr(C, "self.page")) # False
print(hasattr(C, "page")) # False
# print(c.page) # 拋異常
# print(C.page)
print(hasattr(c.__init__, "page")) # True 因此page這時是區域性變數
print(hasattr(C.__init__, "page")) # True
# 可以給類中普通方法新增方法
setattr(C.print_page, "page", page)
c.print_page() # 10
但是可以獲取到私有方法
print(hasattr(c, "__name")) # False
print(hasattr(c, "_C__name")) # True 這有這樣才能
print(c._C_name) # CCC
那麼引申出
class D:
def __init__(self):
print("初始化D類")
self.page = 10
self.__ID = 2
def print_id(self):
print(self.__id)
def print_id2(self):
print(self._D_id)
def print_word(self):
print("列印word:" + self.__word)
def __init__(self, name="DDD"):
print("D類初始化")
self.name = name
self.__id = 1
self._D_word = "word" # 這樣可以曲線新增給__init__新增私有屬性
_D_text = "text" # 這不算類的屬性,只能算區域性變數
d = D() # 初始化D類
print(d.page) # 10
# 設定初始化方法,會覆蓋掉原有的初始化方法
setattr(D, "__init__", __init__)
d2 = D() # D類初始化
print(d.page) # 10
# print(d2.page) # 新例項化物件沒有page屬性
# 檢視私有屬性
# print(d.__ID)
print(d._D__ID) # 2
# 看看新替換的__init__方法的私有屬性可起作用
print(d2.__id) # 但這時__id就不是私有方法了,當作普通方法來了
# d2.print_id() # 拋異常
# d2.print_id2() # 拋異常
# 所以想這樣設定私有屬性,這能_D__word這樣類設定
print(d2._D__word) # word
d2.print_word() # 列印word:word
# print(d2._D_text) # 拋異常,找不到這個屬性
可以通過類的__dict__
來檢視物件有哪些屬性
print(d.__dict__) # {'page': 10, '_D__ID': 2}
print(d2.__dict__) # {'name': 'DDD', '__id': 1, '_D__word': 'word'}
可以發現修改__init__
初始化方法後,物件的屬性變了,也可以動態新增私有屬性了。
另外,也可以通過__init__
來檢視類有哪些屬性
print(D.__dict__)
"""
{'__module__': '__main__',
'__init__': <function __init__ at 0x0000022CD9B166A8>,
'print_id': <function D.print_id at 0x0000022CD9B16950>,
'print_id2': <function D.print_id2 at 0x0000022CD9B169D8>,
'print_word': <function D.print_word at 0x0000022CD9B16A60>,
'__dict__': <attribute '__dict__' of 'D' objects>,
'__weakref__': <attribute '__weakref__' of 'D' objects>,
'__doc__': None}
"""
總結:
- 反射的四個方法更常用在程式執行狀態下,而不是在程式設計階段。
- 給類中
setattr
方法時,一定要給方法加上self
形參並放在形參列表首位。- 新增前建立的物件可以呼叫新新增的方法和屬性
- 不能直接給類新增私有屬性,但是可以單獨寫一個
__init__(self,...)
方法,裡面按私有方法的存在方式_類名__屬性名
定義私有屬性,再把該方法替換原有的類中的方法。 - Python的私有方法其實就是
_類名__私有方法名
這樣儲存的。