目錄javascript
類型註解,即對變量的類型,進行標註或者說明,由於Python是一門動態編譯型語言,咱們沒法在賦值時就定義它的變量類型,因此在Python3.5
以上版本新增了類型註解,但僅僅是提示做用,並不能嚴格控制,這是動態編譯型語言的通病,下面來仔細看一下什麼是Python的類型註解。java
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
在函數中插入說明性文檔的方式成爲函數文檔。框架
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的,在這個所謂的敏捷開發時代,人們大多會以敏捷開發爲藉口沒時間寫,因此這種方法不是很用。工具
Python的函數註解是什麼呢?首先來看一下以下代碼:code
def add(x: int, y: int) -> int: return x + y
完成以上定義後,,主要的差異以下圖:
協程
當咱們在IDE中準備傳入非註釋類型變量時,IDE會幫咱們進行顏色提示,用於表示這裏傳入的變量有點問題。在編寫時咱們尚且可使用這種方式,對咱們產生一點'警示',可是當咱們寫的函數被其餘人調用的時候,那麼就沒法進行'提示'了,這個時候,咱們就須要對傳入的參數進行類型檢查了。對象
咱們來總結一下:
函數註解的信息,保存在函數的__annotation__
屬性中。
python3.6
以上還添加了變量的註解:i:int = 10
,固然也只是提示的做用。
在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中存儲的變量類型進行比較是否是就達到目的了呢?仔細思考一下:
下面咱們來了解一下inspect模塊,它能夠幫咱們完成這個事情。
官方解釋以下:inspect模塊提供了幾個有用的函數來幫助獲取關於活動對象的信息,例如模塊、類、方法、函數、回溯、框架對象和代碼對象。例如,它能夠幫助您檢查類的內容、檢索方法的源代碼、提取並格式化函數的參數列表,或者獲取顯示詳細回溯所需的全部信息。
分類 | 方法名稱 | 功能 |
---|---|---|
判斷 | 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) | 獲取對象的原碼(並不會解析裝飾器原碼) |
首先咱們要說的是函數的簽名信息:它包含了了函數的函數名、它的參數類型,它所在的類和名稱空間及其餘信息,簽名對象(signature object)表示可調用對象的調用簽名信息和它的註解信息,當咱們使用signature()時,它會從新返回一個包含可調用對象信息的簽名對象。
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中沒有被實現。
根據上面講的方法,咱們能夠經過以下方式,簡單的獲取參數的簽名:
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
經過它的屬性,搭配有序字典
這個特性,有沒有很興奮?參數有序,傳入的實參有序,還能獲取參數註解的類型,那麼就能夠開工進行參數檢查了!
以上面函數爲例子,當給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)