1. 程式人生 > 實用技巧 >第四周:Python反射

第四周:Python反射

反射(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的私有方法其實就是_類名__私有方法名這樣儲存的。