python中一切皆對象,這使得python有着自然的自省特性,即可以運行過程當中獲取對象自身由哪些屬性和方法組成。在閱讀python源碼和基於第三方庫開發時,查看獲取某個對象的具體構成內容頗有必要。python自帶的inspect模塊便提供了將對象的各個成員總結出來並具備良好可讀性的功能。python
不使用模塊inspect,僅使用內置函數,也能實現自省和反射的功能。bash
自省意味着獲取對象的屬性和方法;反射經過字符串映射的方式獲取或者修改對象的方法或者屬性(好比以某個屬性的名字字符串做爲參數傳入某個函數),是自省的一種具體實現方法。app
python中相關功能的內置函數以下:函數
dir(obj)->將對象的全部屬性、方法以列表形式返回
hasattr(obj,name_str)->判斷objec是否有name_str這個方法或者屬性
getattr(obj,name_str)->獲取object對象中與name_str同名的方法或者函數
setattr(obj,name_str,value)->爲object對象設置一個以name_str爲名的value方法或者屬性
delattr(obj,name_str)->刪除object對象中的name_str方法或者屬性
複製代碼
可是使用inspect.getmembers(obj)這個方法可以獲取到更詳盡的自省信息,且可讀性更佳,下面將其和dir內置函數進行比較:ui
import inspect
#示例對象--某個函數
def foo(a: int, b: str) -> int:
return a + int(b)
dir(foo)
-->['__annotations__', '__call__', '__class__',...]
inspect.getmembers(foo)
-->[
('__annotations__', {'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'int'>}),
('__call__', <method-wrapper '__call__' of function object at 0x032C7B70>),
('__class__', <class 'function'>),
...]
複製代碼
能夠看到使用dir()僅僅得到一個字符串列表,而使用inspect.getmembers()能夠得到每一個屬性的類型信息。spa
咱們知道可使用type(),isinstance()等內置函數進行類型檢查,經常使用於基本數據類型或者對象實例的class判別,好比:code
type(1)==int #比較數據類型
isinstance(cat,Cat) #比較對象實例是否屬於某個類
複製代碼
但若是要進行更"元"一點的類型比較呢?好比判斷一個對象是否爲一個模組,一個內置函數,一個生成器,甚至一個 await 表達式:對象
>>> import inspect
>>> inspect.ismodule(inspect) # 檢查 inspect 是否爲模組
True
>>> inspect.ismethod(inspect) # 檢查 inspect 是否爲對象方法
False
>>> inspect.isfunction(len) # 檢查 len 是否爲函數
True
>>> inspect.isbuiltin(len) # 檢查 len 是否爲內置函數
True
>>> inspect.isgenerator(inspect) # 檢查 inspect 是否爲生成器
False
>>> inspect.isawaitable(inspect) # 檢查 inspect 是否可用於 await 表達式
False
>>>
複製代碼
所以,使用inspect.isXXX 方法能夠進行更高級的類型判斷。開發
python3中新增了函數註解,提示做用大於約束做用(沒有約束):字符串
def foobar(a: int, b: "it's b", c: str = 5) -> tuple:
return a, b, c
複製代碼
python原生有__annotations__屬性用於獲取函數註解:
>>> foobar.__annotations__
{'a': int, 'b': "it's b", 'c': str, 'return': tuple}
複製代碼
使用inspect模塊一樣可用獲取到函數(callable)的參數信息。
>>> def foo(name, a: int, b: float):
... pass
...
>>> sig = inspect.signature(foo)
>>> sig
<Signature (name, a:int, b:float)>
>>> str(sig)
'(name, a:int, b:float)'
>>> sig.parameters
OrderedDict([('name', <Parameter "name">), ('a', <Parameter "a:int">), ('b', <Parameter "b:float">)])
複製代碼
>>> args = ('foobar', 10)
>>> kwargs = {'b': 23.4}
>>> bound = sig.bind(*args, **kwargs) # bind args to signature parameters
>>> bound
<BoundArguments (name='foobar', a=10, b=23.4)>
>>> bound.arguments['name']
'foobar'
>>> bound.arguments['a']
10
>>> bound.arguments['b']
23.4
複製代碼
綜上,使用inspect能夠將函數形參和實參封裝爲對象,對進一步操做具備意義。
使用inspect獲取到參數對象後,結合函數註解屬性__annotations__,能夠寫一個很優雅的強制類型檢查裝飾器。
from functools import wraps
def checked(func):
ann=func.__annotations__
sig=inspect.signature(func)
@wraps(func)
def wrapper(*args,**kwargs):
bound=sig.bind(*args,**kwargs)
for k,v in bound.arguments.items():
if k in ann:
assert isinstance(v,ann[k]),f'Type Error Expected {ann[k]}'
return func(*args,**kwargs)
return wrapper
>>> @checked
... def add(a: int, b: int) -> int:
... while b:
... a, b = b, a % b
... return a
>>> add(2.7, 3.6)
Traceback (most recent call last):
AssertionError: Type Error Expected <class 'int'>
>>> add(27, 36)
9
複製代碼
綜上,沒有額外在裝飾器參數中指明所須要檢查類型,直接利用python自省的特性和inspect獲取到函數的參數和類型要求,完成強制類型檢查。