反射機制是面向對象編程語言中比較重要的功能,可以動態獲取對象信息以及動態調用對象,Python/ target=_blank class=infotextkey>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)