14 - 函數參數檢測-inspect模塊

1 python類型註解

        類型註解,即對變量的類型,進行標註或者說明,由於Python是一門動態編譯型語言,咱們沒法在賦值時就定義它的變量類型,因此在Python3.5以上版本新增了類型註解,但僅僅是提示做用,並不能嚴格控制,這是動態編譯型語言的通病,下面來仔細看一下什麼是Python的類型註解。java

2 函數定義的弊端

        Python是動態語言,變量隨時能夠被賦值,且賦值爲不一樣的類型,這就與靜態語言不一樣了,變量的類型是在運行期決定的,而靜態語言事先就已經定義好了變量的類型了。這是動態語言方便之處,但也是一種弊端,咱們沒法控制變量的類型,也就沒法控制異常的產生。舉個栗子python

def add(x,y):
    return x + y 
print(add(1,2))
print(add('s','b'))
print(add(1,'a'))

        當用戶傳入兩個數字時,返回它們的和,可是若是咱們傳遞其餘變量呢?好比字符串,由於Python中實現了+號的類型重載,因此說兩個字符串的確能夠加,可是若是是數字和字符串呢?在Python這種強類型語言中來講,屬於非法操做(javascript會隱式轉換),而這時,咱們就須要對用戶傳入的數據進行類型判斷,不符合本函數的需求,那麼就拋個異常,或者提示等等操做,這樣就不會引發後續代碼在執行期崩潰。如何解決呢?其實主要有兩種方式。app

  • 函數文檔
  • 函數註解

3 函數文檔

        在函數中插入說明性文檔的方式成爲函數文檔。框架

def add(x, y):
    """
    This function used to add something
    :param x: int object
    :param y: int object
    :return: int object
    """
    return x + y

在函數中,通常是定義語句後的首行使用三對雙引號表示。一般存儲在函數的__doc__屬性中。當用戶使用help(函數)時,會被打印在屏幕上。函數

In [68]: def add(x, y):
    ...:     """
    ...:     This function used to add something
    ...:     :param x: int object
    ...:     :param y: int object
    ...:     :return: int object
    ...:     """
    ...:     return x + y
    ...:

In [69]: help(add)
Help on function add in module __main__:

add(x, y)
    This function used to add something
    :param x: int object
    :param y: int object
    :return: int object


In [70]: print(add.__doc__)

    This function used to add something
    :param x: int object
    :param y: int object
    :return: int object

In [71]:

每次都要使用help來查看函數的說明,的確可讓使用者瞭解函數的參數以及返回值的類型,但並非全部人都願意寫doc的,在這個所謂的敏捷開發時代,人們大多會以敏捷開發爲藉口沒時間寫,因此這種方法不是很用。工具

4 函數註解

        Python的函數註解是什麼呢?首先來看一下以下代碼:code

def add(x: int, y: int) -> int:
    return x + y
  • 函數的位置形參,和默認值形參後使用冒號分隔,後面用於標識變量指望的類型。
  • 在def語句末尾,使用->符號後 指定用於標識函數執行後的返回值類型。

完成以上定義後,,主要的差異以下圖:
zhushi1協程

當咱們在IDE中準備傳入非註釋類型變量時,IDE會幫咱們進行顏色提示,用於表示這裏傳入的變量有點問題。在編寫時咱們尚且可使用這種方式,對咱們產生一點'警示',可是當咱們寫的函數被其餘人調用的時候,那麼就沒法進行'提示'了,這個時候,咱們就須要對傳入的參數進行類型檢查了。對象

咱們來總結一下:

  • 函數註解在Python3.5中引入
  • 對函數的參數、返回值進行類型註解
  • 只對函數的參數作一個輔助的說明,並不對函數參數進行類型檢查
  • 提供給第三方工具,作代碼分析,發現隱藏的BUG
  • 函數註解的信息,保存在函數的__annotation__屬性中。

    python3.6 以上還添加了變量的註解:i:int = 10,固然也只是提示的做用。

4.1 annotation屬性

在Python中使用__開頭的表示符通常被用特殊屬性,__annotation__存儲的就是函數的簽名信息

In [71]: def add(x: int, y: int) -> int:
    ...:     return x + y
    ...:

In [73]: add.__annotations__
Out[73]: {'x': int, 'y': int, 'return': int}

        當咱們使用變量註釋時,變量名和類型就會存放在函數的__annotations__屬性中。那麼即然有變量存儲,那麼咱們是否是隻須要獲取傳入的參數,而後和annotations中存儲的變量類型進行比較是否是就達到目的了呢?仔細思考一下:

  1. 參數檢查勢必要在函數執行前,想要在add執行前添加參數判斷那麼就須要使用裝飾器了
  2. __annotations__的值是一個字典,字典是無序的,用戶按照位置傳進來參數是有序的,如何讓它們造成對應關係方便咱們檢測呢?

下面咱們來了解一下inspect模塊,它能夠幫咱們完成這個事情。

5 inspect模塊

        官方解釋以下:inspect模塊提供了幾個有用的函數來幫助獲取關於活動對象的信息,例如模塊、類、方法、函數、回溯、框架對象和代碼對象。例如,它能夠幫助您檢查類的內容、檢索方法的源代碼、提取並格式化函數的參數列表,或者獲取顯示詳細回溯所需的全部信息。

5.1 經常使用方法

分類 方法名稱 功能
判斷 inspect.getmodulename(path) 獲取模塊名稱
inspect.ismodule(object) 是否是個模塊
inspect.isclass(object) 是否是個類
inspect.ismethod(object) 是否是一個方法
inspect.isfunction(object) 是否是一個函數
inspect.isgeneratorfunction(object) 是否是一個生成器函數
inspect.isgenerator(object) 是否是一個生成器
inspect.iscoroutinefunction(object) 是否是一個協程函數
獲取信息 inspect.getmodulename(path) 獲取模塊名稱
inspect.getsource(object) 獲取對象的原碼(並不會解析裝飾器原碼)

5.2 signature類

        首先咱們要說的是函數的簽名信息:它包含了了函數的函數名、它的參數類型,它所在的類和名稱空間及其餘信息,簽名對象(signature object)表示可調用對象的調用簽名信息和它的註解信息,當咱們使用signature()時,它會從新返回一個包含可調用對象信息的簽名對象。

5.3 parameters屬性

        signature類的parameters屬性,它裏面存放的是函數的參數註解和返回值註解,組成的有序字典,其中參數註解的格式爲:參數名稱,使用inspect.Parameters類包裝的參數註解,這個參數註解很強大,它包含以下經常使用的方法:

方法名稱 含義
empty 等同於inspect._empty表示一個參數沒有被類型註釋
name 參數的名稱
default 參數的默認值,若是一個參數沒有默認值,這個屬性的值爲inspect.empty
annotation 參數的註解類型,若是參數沒有定義註解,這個屬性的值爲inspect.empty
kind 參數的類型

這裏的參數類型表示的是inspect內置參數類型(其實就是幾個經常使用的函數參數定義類型而已,只是換個名字而已)

_POSITIONAL_ONLY         = _ParameterKind.POSITIONAL_ONLY       # 位置參數_only
_POSITIONAL_OR_KEYWORD   = _ParameterKind.POSITIONAL_OR_KEYWORD # 位置或關鍵字參數
_VAR_POSITIONAL          = _ParameterKind.VAR_POSITIONAL        # 可變位置參數
_KEYWORD_ONLY            = _ParameterKind.KEYWORD_ONLY          # keyword-only參數
_VAR_KEYWORD             = _ParameterKind.VAR_KEYWORD           # 可變關鍵字參數

其中POSITIONAL_ONLY,Python中沒有被實現。

5.4 獲取對象的參數簽名

根據上面講的方法,咱們能夠經過以下方式,簡單的獲取參數的簽名:

In [11]: import inspect
    ...:
    ...: def add(x: int, y: int) -> int:
    ...:     return x + y
    ...:
    ...: sig = inspect.signature(add)
    ...: params = sig.parameters
    ...: print(params)
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)])

In [21]: params['x'].annotation
Out[21]: int    # 若是沒有定義x的參數註解,那麼這裏就是inspect._empty

經過它的屬性,搭配有序字典這個特性,有沒有很興奮?參數有序,傳入的實參有序,還能獲取參數註解的類型,那麼就能夠開工進行參數檢查了!

6 檢查參數

以上面函數爲例子,當給add函數傳入的x,y時進行參數檢查,若是x,y不是int類型,那麼返回異常,並退出函數

import inspect
import functools

def check(fn):
    @functools.wraps(fn)   # 等於 wrapper.__annotation__ = fn.__annotation__ 還有其餘的屬性好比__doc__,__module__等
    def wrapper(*args, **kwargs):
        sig = inspect.signature(fn)   # 獲取add函數簽名信息
        params = sig.parameters     # 獲取add函數的參數信息
        values = list(params.values())   # 因爲params是個有序字典,那麼values也是有序的,只需根據索一一對應判斷便可
        for i, k in enumerate(args):   # 遍歷用戶傳入的位置參數
            if values[i].annotation != inspect._empty:   # 若是定義了參數註解,則開始檢查
                if not isinstance(k, values[i].annotation):   # 若是檢查不經過,曝出異常
                    raise('Key Error')
        for k,v in kwargs.items():
            if params[k].annotation != inspect._empty:
                if not isinstance(v,params[k].annotation):
                    raise('Key Error')
        return fn(*args, **kwargs)
    return wrapper

@check
def add(x: int, y: int) -> int:
    return x + y
    
add(4,y=5)
相關文章
相關標籤/搜索