python類型注解
函數定義的弊端:
python是動态語言,變量随時可以被指派,且能指派為不同的類型;
python不是靜态編譯型語言,變量類型是在運作時決定的;
動态語言很靈活,但這種特性也是弊端:
難發現,由于不做任何類型檢查,直到運作時問題才顯現出來,或線上上才能暴露出問題;
難使用,函數的使用者看到函數時,并不知道設計者是如何設計的函數,也不知道應該傳入什麼類型的資料;
例:
in [63]: add(4,5)
out[63]: 9
in [64]: add('hello','world')
out[64]: 'helloworld'
in [65]: add(4,'hello') #強弱類型語言的差別舉例
---------------------------------------------------------------------------
typeerror traceback (most recent call last)
<ipython-input-65-d5d4453c2cd4> in <module>()
----> 1 add(4,'hello')
<ipython-input-62-c1dcfb42218b> in add(x, y)
1 def add(x,y):
----> 2 return x+y
typeerror: unsupported operand type(s) for +: 'int' and 'str'
解決函數定義的弊端:
documentation string;
這隻是一個慣例,不是強制标準,不能要求程式員一定為函數提供說明文檔;
函數定義更新了,文檔未必同步更新;
in [67]: def add(x,y):
...: '''
...: :param x: int
...: :param y: int
...: :return: int
...: return x+y
...:
in [68]: help(add)
help on function add in module __main__:
add(x, y)
:param x: int
:param y: int
:return: int
(end)
function annotation函數注解:
與java注解是兩碼事;
python3.5引入;
對函數的參數進行類型注解;
對函數的傳回值進行類型注解;
隻對函數參數作一個輔助說明,并不對函數參數進行類型檢查;
提供給第三方工具,作代碼分析,發現隐藏的bug;
函數注解的資訊儲存在__annotations__屬性中,如add.__annotations__;
變量注解:
python3.6引入;
業務應用:
函數參數類型檢查;
思路:
函數參數的檢查,一定是在函數外;
函數應該作為參數,傳入到檢查函數中(裝飾器);
檢查函數拿到函數傳入的實際參數,與形參聲明對比;
__annotations__屬性是一個字典,包括函數參數及傳回值的聲明,是普通字典(非有序字典),假設要做位置參數的判斷,無法和此字典中的聲明對應,要使用inspect子產品;
in [71]: def add(x:int,y:int)->int:
in [73]: add.__annotations__ #普通字典,而非有序字典
out[73]: {'return': int, 'x': int, 'y': int}
inspect子產品:
提供擷取對象資訊的函數,可以檢查函數和類、類型檢查;
inspect.signature(callable),擷取簽名,函數簽名包含了一個函數的資訊,包括函數名、函數參數、預設值、傳回值,它的參數類型,它所在的類,和名稱空間及其它資訊;
inspect.isfunction(add),是否是函數,限定隻是函數,函數在類中為method;
inspect.ismethod(add),是否是類的方法;
inspect.isgenerator(add),是否是生成器對象;
inspect.isgeneratorfunction(add),是否是生成器函數;
inspect.isclass(add),是否是類;
inspect.ismodule(inspect),是否是子產品;
inspect.isbuiltin(print),是否是内建對象;
in [74]: import inspect
in [75]: def add(x:int,y:int,*args,**kwargs)->int:
in [76]: add.__annotations__ #是普通字典,順序随機
out[76]: {'return': int, 'x': int, 'y': int}
in [77]: sig=inspect.signature(add)
in [78]: sig #函數簽名,聲明是什麼樣,即函數第一行,定義時的東西
out[78]: <signature (x:int, y:int, *args, **kwargs) -> int>
in [79]: print('params:',sig.parameters) #ordereddict有序字典,解決了調用時傳參的順序問題,可疊代,疊代中的每一個元素為parameter
params: ordereddict([('x', <parameter "x:int">), ('y', <parameter "y:int">), ('args', <parameter "*args">), ('kwargs', <parameter "**kwargs">)])
in [80]: print('return:',sig.return_annotation)
return: <class 'int'>
in [81]: sig.parameters['x']
out[81]: <parameter "x:int">
in [82]: sig.parameters['x'].annotation
out[82]: int
in [83]: sig.parameters['y']
out[83]: <parameter "y:int">
in [84]: sig.parameters['y'].annotation
out[84]: int
in [85]: sig.parameters['args']
out[85]: <parameter "*args">
in [86]: sig.parameters['args'].annotation
out[86]: inspect._empty
in [87]: sig.parameters['kwargs']
out[87]: <parameter "**kwargs">
in [88]: sig.parameters['kwargs'].annotation
out[88]: inspect._empty
parameter對象:
儲存在元組中,是隻讀的;
name,參數的名字;
default,參數的預設值,可能沒有定義;
annotation,參數的注解,可能沒有定義;
empty,特殊的類,用來标記default屬性或annotation屬性的空值,與sig.parameters['x'].annotation是一個東西;
kind,實參如何綁定到形參,就是形參的類型:
positional_only,值必須是位置參數提供,python中未實作此項,僅常量定義了;
positional_or_keyword,值可以作為關鍵字或位置參數提供;
var_positional,可變位置參數,對應*args;
keyword_only,keyword-only參數,對應*或*args之後出現的非可變關鍵字參數;
var_keyword,可變關鍵字參數,對應**kwargs;
positional_or_keyword,var_positional,keyword_only,var_keyword,參數類型(形參)可用此判斷;
實參的資料類型用annotation判斷;
in [93]: def add(x,y:int=7,*args,z,t=10,**kwargs)->int:
in [94]: sig=inspect.signature(add)
in [95]: sig
out[95]: <signature (x, y:int=7, *args, z, t=10, **kwargs) -> int>
in [96]: sig.parameters
out[96]:
mappingproxy({'args': <parameter "*args">,
'kwargs': <parameter "**kwargs">,
't': <parameter "t=10">,
'x': <parameter "x">,
'y': <parameter "y:int=7">,
'z': <parameter "z">})
in [97]: sig.return_annotation
out[97]: int
in [98]: print(sig.return_annotation)
<class 'int'>
in [99]: for i,(name,param) in enumerate(sig.parameters.items()):
...: print(i+1,name,param.annotation,param.kind,param.default)
...: print(param.default is param.empty,end='\n\n')
...:
1 x <class 'inspect._empty'> positional_or_keyword <class 'inspect._empty'>
true
2 y <class 'int'> positional_or_keyword 7
false
3 args <class 'inspect._empty'> var_positional <class 'inspect._empty'>
4 z <class 'inspect._empty'> keyword_only <class 'inspect._empty'>
5 t <class 'inspect._empty'> keyword_only 10
6 kwargs <class 'inspect._empty'> var_keyword <class 'inspect._empty'>
例(參數檢查):
import inspect
from functools import wraps
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
print(args,kwargs) #此處不可以**kwargs,print函數中沒有類似y=7關鍵字參數
sig = inspect.signature(fn)
print(sig)
print('params:',sig.parameters)
print('return:',sig.return_annotation)
print('~~~~~~~~~~~~~~~~~~~~~~~')
# for i,(name,param) in enumerate(sig.parameters.items()):
# print(i+1,name,param.name,param.annotation,param.kind,param.default)
# print(param.default is param.empty,end='\n\n')
# for param in sig.parameters.values():
# print(param.name,param)
# print(param.name,param.annotation,param.kind,param.default)
params = sig.parameters
param_list = list(params.keys())
for i,v in enumerate(args): #位置參數傳參處理
k = param_list[i] #用key找key,技巧
if isinstance(v,params[k].annotation):
print(v,'is',params[k].annotation)
else:
# print(v,'is not',params[k].annotation)
errstr = '{} is not {}'.format(v,params[k].annotation)
print(errstr)
raise typeerror(errstr)
for k,v in kwargs.items(): #關鍵字參數傳參處理
ret = fn(*args,**kwargs)
return ret
return wrapper
@check
def add(x:int,y:int=7)->int:
return x + y
#add(4,8)
#add(x=4,y=8)
#add(4,y=8)
#add('mag','edu')
add(x='mag',y='edu')
#add(4)
#add(4,8,y=8)
注:
mappingproxytype,有序字典被包裝過(虛的,假的);
視圖,一般隻讀;
pycharm裡抽取函數,選中内容-->refactor-->extract-->method
values = list(params.values())
for i,p in enumerate(args):
if isinstance(p,values[i].annotation):
print('==')
for k,v in kwargs.items():
print('===')
return fn(*args,**kwargs)
def add(x:int,y:int=7):
#add(4,y=2)
add(4)
####################
def wrapper(*args,**kwargs):
# if isinstance(p,values[i].annotation):
# print('==')
param = values[i]
if param.annotation is not param.empty and not isinstance(p,param.annotation):
print(p,'!=',values[i].annotation)
# if isinstance(v,params[k].annotation):
# print('===')
if params[k].annotation is not inspect._empty and not isinstance(v,params[k].annotation):
print(v,'!==',params[k].annotation)
add('mag','edu')
param.empty與inspect._empty一樣;