自省?使用inspect模塊探測python對象內容

python中一切皆對象,這使得python有着自然的自省特性,即可以運行過程當中獲取對象自身由哪些屬性和方法組成。在閱讀python源碼和基於第三方庫開發時,查看獲取某個對象的具體構成內容頗有必要。python自帶的inspect模塊便提供了將對象的各個成員總結出來並具備良好可讀性的功能。python

1.挑戰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

2.更高的類型檢查

咱們知道可使用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 方法能夠進行更高級的類型判斷。開發

3.操做函數參數簽名

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)的參數信息。

  1. inspect.signature 獲取到函數參數的對象Signature,包括兩個經常使用參數:
    1. Signature.parameters 參數值的名字和類型
    2. Signature.return_annotation 返回值類型
>>> 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">)])

複製代碼
  1. Signature.bind 將具體參數綁定到對象Signature上,得到BoundArguments對象(保存了參數信息)
>>> 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能夠將函數形參和實參封裝爲對象,對進一步操做具備意義。

4.受到欽定的類型檢查實現

使用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獲取到函數的參數和類型要求,完成強制類型檢查。

相關文章
相關標籤/搜索