1. 程式人生 > 其它 >Python反射介紹

Python反射介紹

反射機制是面向物件程式語言中比較重要的功能,可以動態獲取物件資訊以及動態呼叫物件,Python作為一門動態程式語言,當然也有反射機制,本文介紹Python反射函式使用方法。

目錄

反射

反射的概念是由Smith在1982年首次提出的,主要是指程式可以訪問、檢測和修改它本身狀態或行為的一種能力。

在程式執行時可以獲取物件型別定義資訊,例如,Python中的type(obj)將返回obj物件的型別,這種獲取物件的type、attribute或者method的能力稱為反射。通過反射機制,可以用來檢查物件裡的某個方法,或某個變數是否存在。也就是可以通過字串對映物件的方法或者屬性

Python反射函式

Python反射常用的內建函式

  • type(obj):返回物件型別
  • isinstance(object, classinfo):判斷一個物件是否是一個已知的型別,類似 type()
  • callable(obj):物件是否可以被呼叫
  • dir([obj]):返回obj屬性列表
  • getattr(obj, attr):返回物件屬性值
  • hasattr(obj, attr):判斷某個函式或者變數是否存在
  • setattr(obj, attr, val):給模組新增屬性(函式或者變數)
  • delattr(obj, attr):刪除模組中某個變數或者函式

反射函式使用方法

先建立一個類:

class Person():
    def __init__(self, x, y):
        self.age = x
        self.height = y
        
    def __new__(cls, *args, **kwargs):
        print("begin!!!")
        return object.__new__(cls)
        
    def __call__(self, *args, **kwargs):
        print("hello!!!")

    def talk(self):
        print(f"My age is {self.age} and height is {self.height}")

dir()

利用反射的能力,我們可以通過屬性字典__dict__來訪問物件的屬性:

p = Person(20, 180)
print(p)
p()
print(p.__dict__)
p.__dict__['age']=22
print(p.__dict__)
p.weight = 60
print(p.__dict__)
print(dir(p))

執行輸出:

begin!!!
<__main__.Person object at 0x000002484557BCC8>
hello!!!
{'age': 20, 'height': 180}
{'age': 22, 'height': 180}
{'age': 22, 'height': 180, 'weight': 60}
['__call__', '__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__', 'age', 'height', 'talk', 'weight']

  • 在例項建立之前呼叫__new__方法,返回值(例項)將傳遞給__init__方法的第一個引數。__new__方法的詳細介紹可參考:Python中的__new____init__
  • 例項化物件時會自動執行 __init__ 方法
  • 列印一個物件時,會自動執行 __str__ 方法
  • 呼叫例項化物件時,會自動觸發 __call__ 方法
  • 通過dir()方法可以打印出了物件p的屬性。

接下來測試一下其他反射函式:

callable()

if (callable(p)):
    print("p is callable")
else:
    print("p is not callable")

Out:

p is callable

isinstance()和type()

print(isinstance(p, Person))
print(type(p) == Person)
print(isinstance(p.age, int))
print(type(p.age) == int)

Out:

True
True
True
True

hasattr()

print(hasattr(p,"talk"))

print(hasattr(p.talk,"__call__"))

Out:

True
True

getattr()

print(getattr(p,"talk"))
print(getattr(p.talk, "__call__"))

if hasattr(p,'walk'):
    print(getattr(p,'walk'))
else:
    print("I can't walk")

print(getattr(p, "walk", None)) # 如果沒有walk屬性就返回None

Out:

<bound method Person.talk of <__main__.Person object at 0x000001FF52868288>>
<method-wrapper '__call__' of method object at 0x000001FF52155048>
I can't walk
None

setattr()

setattr(p,'walk','ON')
if hasattr(p,'walk'):
    print(getattr(p,'walk'))
else:
    print("I can't walk")
print(p.__dict__)

Out:

ON
{'age': 22, 'height': 180, 'weight': 60, 'walk': 'ON'}

delattr()

delattr(p,'walk')
if hasattr(p,'walk'):
    print(getattr(p,'walk'))
else:
    print("I can't walk")
print(p.__dict__)

Out:

I can't walk
{'age': 22, 'height': 180, 'weight': 60}

應用

下面介紹兩種Python反射的應用場景。

動態呼叫

從前面舉的例子中,我們瞭解到可以通過字串來獲取物件的屬性(getattr()),這是非常有用的一個功能。比如,一個類中有很多方法,它們提供不同的服務,通過輸入的引數來判斷執行某個方法,一般的使用如下寫法:

class MyService():
    def service1(self):
        print("service1")

    def service2(self):
        print("service2")

    def service3(self):
        print("service3")

if __name__ == '__main__':
    Ser = MyService()
    s = input("請輸入您想要的服務: ").strip()
    if s == "service1":
        Ser.service1()
    elif s == "service2":
        Ser.service2()
    elif s == "service3":
        Ser.service3()
    else:
        print("error!")

如果函式比較少這樣寫沒有太大問題,如果有很多,這樣寫就比較複雜了,需要寫大量else語句,可以使用反射機制來寫:

if __name__ == '__main__':
    Ser = MyService()
    s = input("請輸入您想要的服務: ").strip()
    if hasattr(Ser, s):
        func = getattr(Ser, s)
        func()
    else:
        print("error!")

這樣是不是簡潔了很多,上面的例子中,通過反射,將字串變成了函式,實現了對物件方法的動態呼叫。

動態屬性設定

可以通過setattr()方法進行動態屬性設定,在使用scapy庫構造報文時,我們需要設定某些報文欄位,然而網路協議的報文欄位很多,在需要設定大量欄位時,一個一個的賦值就很麻煩:

>>> ls(IP)
version    : BitField  (4 bits)                  = ('4')
ihl        : BitField  (4 bits)                  = ('None')
tos        : XByteField                          = ('0')
len        : ShortField                          = ('None')
id         : ShortField                          = ('1')
flags      : FlagsField                          = ('<Flag 0 ()>')
frag       : BitField  (13 bits)                 = ('0')
ttl        : ByteField                           = ('64')
proto      : ByteEnumField                       = ('0')
chksum     : XShortField                         = ('None')
src        : SourceIPField                       = ('None')
dst        : DestIPField                         = ('None')
options    : PacketListField                     = ('[]')

可以使用setattr()方法來賦值:

from scapy.all import *

fields = {"version":4, "src":"192.168.0.1","dst":"192.168.10.1"}
ip = IP()
for key, val in fields.items():
    setattr(ip, key, val)
--THE END--

歡迎關注公眾號:「測試開發小記」及時接收最新技術文章!