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:
...: '''
...: :param x: int
...: :param y: int
...: :return: int
...: '''
...: return x+y
...:
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:
...: return x+y
...:
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:
...: return x+y
...:
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'>
True
4 z <class 'inspect._empty'> KEYWORD_ONLY <class 'inspect._empty'>
True
5 t <class 'inspect._empty'> KEYWORD_ONLY 10
False
6 kwargs <class 'inspect._empty'> VAR_KEYWORD <class 'inspect._empty'>
True
例(參數檢查):
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(): #關鍵字參數傳參處理
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)
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
例:
from functools import wraps
import inspect
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i,p in enumerate(args):
if isinstance(p,values[i].annotation):
print('==')
for k,v in kwargs.items():
if isinstance(v,params[k].annotation):
print('===')
return fn(*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=7):
return x + y
#add(4,8)
#add(x=4,y=8)
#add(4,y=2)
add(4)
####################
from functools import wraps
import inspect
def check(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
values = list(params.values())
for i,p in enumerate(args):
# 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)
for k,v in kwargs.items():
# 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)
return fn(*args,**kwargs)
return wrapper
@check
def add(x:int,y:int=7):
return x + y
#add(4,8)
#add(x=4,y=8)
#add(4,y=2)
#add(4)
add('mag','edu')
注:
param.empty與inspect._empty一樣;