以下內容基於Python 3x
涉及的知識前提:
- 建議理解Python裝飾器后學習此內容
函數注解概述
函數注解可以針對函數的參數、返回值添加元數據,其為注解。
python是動態語言
,變量賦值時不會強制聲明類型
,且能隨時重新賦值
。無法像靜態編譯型語言一樣,在編譯時發現基本問題。
函數的參數要求,沒有詳細的doc string或更新沒跟上,以至后續的使用者不能夠清晰明白設計者要求的參數類型。以上行為導致的出錯率變高,難以使用等問題。
函數注解可以對參數的要求類型進行注解,對函數返回值進行注解;其只是對做一個輔助性的說明,並不強制進行類型檢查
。
一些第三方工具(PyCharm)會對已定義的函數注解進行檢查標記提示
等,在編寫代碼時同樣也可以通過編寫一個裝飾器,來進行一些檢查。
實際應用
inspect模塊
注解信息保存在函數的__annotations__
屬性中,函數的參數檢查,一定是在函數實際運行之前,檢查傳遞進來的形參與原有的注解類型是否匹配。
__annotations__屬性是一個字典
,對於位置參數的判斷,較為復雜,可直接使用inspect模塊獲取函數簽名信息。
# 注解x和y參數要求為int類型,返回結果為int類型
# Syntax:
def foo(x: int, y: int) -> int:
return x + y
查看其__annotations__屬性信息:
def foo(x: int, y: int) -> int:
return x + y
print(type(foo.__annotations__))
print(foo.__annotations__)
# 輸出結果:
<class 'dict'>
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
使用inspect模塊獲取簽名信息:
inspect.signature返回的是一個OrderedDict(有序字典)
import inspect
def foo(x: int, y: int) -> int:
return x + y
sig = inspect.signature(foo)
print(sig.parameters)
# 輸出結果:
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)])
使用parameter方法,獲取更詳細的信息:
import inspect
def foo(x: int , y: int, z=20) -> int:
return x + y + z
sig = inspect.signature(foo)
par = sig.parameters
print(par.items())
print(par["x"].name, "|-|", par["x"].annotation, "|-|", par["x"].default, "|-|", par["x"].empty, "|-|", par["x"].kind)
print(par["y"].name, "|-|", par["y"].annotation, "|-|", par["y"].default, "|-|", par["y"].empty, "|-|", par["y"].kind)
print(par["z"].name, "|-|", par["z"].annotation, "|-|", par["z"].default, "|-|", par["z"].empty, "|-|", par["z"].kind)
#輸出結果:
odict_items([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('z', <Parameter "z=20">)])
x |-| <class 'int'> |-| <class 'inspect._empty'> |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
y |-| <class 'int'> |-| <class 'inspect._empty'> |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
z |-| <class 'inspect._empty'> |-| 20 |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
parameter輸出結果的信息保存在元組中,只讀;
name:獲取參數名字;
annotation:獲取參數的注解,有可能沒有定義,則為_empty;
default:返回參數的缺省值,如其中z參數的20即為缺省值;
empty:特殊的類,用來標記default屬性或者注釋annotation屬性的空值;
kind:實參如何綁定到形參。以下是形參的類型:
-
POSITIONAL_ONLY,值必須是位置參數提供的(Python中並沒有絕對的位置參數,未實現)。
-
POSITIONAL_OR_KEYWORD,值可以作為關鍵字或者未知參數提供。
-
VAR_POSITIONAL,可變位置參數,對應*args。
-
KEYWORD_ONLY,keyword-only參數,對應*args之后出現的非可變關鍵字參數。
-
VAR_KEYWORD,可變關鍵字參數,對應**kwargs。
業務代碼
了解以上方法后,編寫一個函數裝飾器用來檢測傳參是否規范。
- 首先在函數運行之前,獲取到函數簽名、及簽名的parameter信息將parameter信息。
- kv結構的value轉換為列表保存。
- 使用for循環
檢查每個對應的parameter內參數(實際傳的參數)注解是否不為空
並且同時是否不是已知類型
(isinstance函數可以檢查傳的值是否是已知類型,返回True或False,簽名中參數的annotation類型為已知的)。 - 匹配通過,則說明傳遞的參數和注解要求類型一致,否則則拋出TypeError類型的自定義錯誤信息。
import inspect
import functools
def parameter_check(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
sig = inspect.signature(fn)
parameter = sig.parameters
value = list(parameter.values())
for i, par in enumerate(args):
if value[i].annotation is not value[i].empty and not isinstance(par, value[i].annotation):
raise TypeError("Parameter type error")
for k, v in kwargs.items():
if parameter[k].annotation is not parameter[k].empty and not isinstance(v, parameter[k].annotation):
raise TypeError("Parameter type error")
ret = fn(*args, **kwargs)
return ret
return wrapper
@parameter_check
def foo(x: int, y: int, z=20) -> int:
return x + y + z
print(foo(1, 50, 29))
# 輸出結果:
80
# 更改傳參,查看效果:
print(foo("hhh", 50, 29))
# 輸出結果:
Traceback (most recent call last):
File "E:/project/python/test.py", line 29, in <module>
print(foo("hhh", 50, 29))
File "E:/project/python/test.py", line 13, in wrapper
raise TypeError("Parameter type error")
TypeError: Parameter type error
總結
通過對函數注解的了解,能夠更加規范代碼,提高效率,避免出錯。在實際應用中亦可方便的展開使用。