在Python 3中實現(xiàn)類型檢查器的簡單方法
示例函數(shù)
為了開發(fā)類型檢查器,我們需要一個簡單的函數(shù)對其進行實驗。歐幾里得算法就是一個完美的例子:
def gcd(a, b):
'''Return the greatest common divisor of a and b.'''
a = abs(a)
b = abs(b)
if a < b:
a, b = b, a
while b != 0:
a, b = b, a % b
return a
在上面的示例中,參數(shù) a 和 b 以及返回值應該是 int 類型的。預期的類型將會以函數(shù)注解的形式來表達,函數(shù)注解是 Python 3 的一個新特性。接下來,類型檢查機制將會以一個裝飾器的形式實現(xiàn),注解版本的第一行代碼是:
def gcd(a: int, b: int) -> int:
使用“gcd.__annotations__”可以獲得一個包含注解的字典:
>>> gcd.__annotations__
{'return': <class 'int'>, 'b': <class 'int'>, 'a': <class 'int'>}
>>> gcd.__annotations__['a']
<class 'int'>
需要注意的是,返回值的注解存儲在鍵“return”下。這是有可能的,因為“return”是一個關(guān)鍵字,所以不能用作一個有效的參數(shù)名。
檢查返回值類型
返回值注解存儲在字典“__annotations__”中的“return”鍵下。我們將使用這個值來檢查返回值(假設(shè)注解存在)。我們將參數(shù)傳遞給原始函數(shù),如果存在注解,我們將通過注解中的值來驗證其類型:
def typecheck(f):
def wrapper(*args, **kwargs):
result = f(*args, **kwargs)
return_type = f.__annotations__.get('return', None)
if return_type and not isinstance(result, return_type):
raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
return result
return wrapper
我們可以用“a”替換函數(shù)gcd的返回值來測試上面的代碼:
Traceback (most recent call last):
File "typechecker.py", line 9, in <module>
gcd(1, 2)
File "typechecker.py", line 5, in wrapper
raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
RuntimeError: gcd should return int
由上面的結(jié)果可知,確實檢查了返回值的類型。
檢查參數(shù)類型
函數(shù)的參數(shù)存在于關(guān)聯(lián)代碼對象的“co_varnames”屬性中,在我們的例子中是“gcd.__code__.co_varnames”。元組包含了所有局部變量的名稱,并且該元組以參數(shù)開始,參數(shù)數(shù)量存儲在“co_nlocals”中。我們需要遍歷包括索引在內(nèi)的所有變量,并從參數(shù)“args”中獲取參數(shù)值,最后對其進行類型檢查。
得到了下面的代碼:
def typecheck(f):
def wrapper(*args, **kwargs):
for i, arg in enumerate(args[:f.__code__.co_nlocals]):
name = f.__code__.co_varnames[i]
expected_type = f.__annotations__.get(name, None)
if expected_type and not isinstance(arg, expected_type):
raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
result = f(*args, **kwargs)
return_type = f.__annotations__.get('return', None)
if return_type and not isinstance(result, return_type):
raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
return result
return wrapper
在上面的循環(huán)中,i是數(shù)組args中參數(shù)的以0起始的索引,arg是包含其值的字符串。可以利用“f.__code__.co_varnames[i]”讀取到參數(shù)的名稱。類型檢查代碼與返回值類型檢查完全一樣(包括錯誤消息的異常)。
為了對關(guān)鍵字參數(shù)進行類型檢查,我們需要遍歷參數(shù)kwargs。此時的類型檢查幾乎與第一個循環(huán)中相同:
for name, arg in kwargs.items():
expected_type = f.__annotations__.get(name, None)
if expected_type and not isinstance(arg, expected_type):
raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
得到的裝飾器代碼如下:
def typecheck(f):
def wrapper(*args, **kwargs):
for i, arg in enumerate(args[:f.__code__.co_nlocals]):
name = f.__code__.co_varnames[i]
expected_type = f.__annotations__.get(name, None)
if expected_type and not isinstance(arg, expected_type):
raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
for name, arg in kwargs.items():
expected_type = f.__annotations__.get(name, None)
if expected_type and not isinstance(arg, expected_type):
raise RuntimeError("{} should be of type {}; {} specified".format(name, expected_type.__name__, type(arg).__name__))
result = f(*args, **kwargs)
return_type = f.__annotations__.get('return', None)
if return_type and not isinstance(result, return_type):
raise RuntimeError("{} should return {}".format(f.__name__, return_type.__name__))
return result
return wrapper
將類型檢查代碼寫成一個函數(shù)將會使代碼更加清晰。為了簡化代碼,我們修改錯誤信息,而當返回值是無效的類型時,將會使用到這些錯誤信息。我們也可以利用 functools 模塊中的 wraps 方法,將包裝函數(shù)的一些屬性復制到 wrapper 中(這使得 wrapper 看起來更像原來的函數(shù)):
def typecheck(f):
def do_typecheck(name, arg):
expected_type = f.__annotations__.get(name, None)
if expected_type and not isinstance(arg, expected_type):
raise RuntimeError("{} should be of type {} instead of {}".format(name, expected_type.__name__, type(arg).__name__))
@functools.wraps(f)
def wrapper(*args, **kwargs):
for i, arg in enumerate(args[:f.__code__.co_nlocals]):
do_typecheck(f.__code__.co_varnames[i], arg)
for name, arg in kwargs.items():
do_typecheck(name, arg)
result = f(*args, **kwargs)
do_typecheck('return', result)
return result
return wrapper
結(jié)論
注解是 Python 3 中的一個新元素,本文例子中的使用方法很普通,你也可以想象很多特定領(lǐng)域的應用。雖然上面的實現(xiàn)代碼并不能滿足實際產(chǎn)品要求,但它的目的本來就是用作概念驗證??梢詫ζ溥M行以下改善:
- 處理額外的參數(shù)( args 中意想不到的項目)
- 默認值類型檢查
- 支持多個類型
- 支持模板類型(例如,int 型列表)
相關(guān)文章
matplotlib之多邊形選區(qū)(PolygonSelector)的使用
這篇文章主要介紹了matplotlib之多邊形選區(qū)(PolygonSelector)的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
詳解使用Pytorch Geometric實現(xiàn)GraphSAGE模型
這篇文章主要為大家介紹了詳解使用Pytorch Geometric實現(xiàn)GraphSAGE模型示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
從基礎(chǔ)到進階帶你玩轉(zhuǎn)Python中的JSON
JSON是一種輕量級的數(shù)據(jù)交換格式,在Python中處理JSON數(shù)據(jù)是日常開發(fā)中的常見任務之一,本文將詳細介紹如何在Python中處理JSON對象,需要的可以參考下2024-12-12
關(guān)于Python中compile() 函數(shù)簡單實用示例詳解
這篇文章主要介紹了關(guān)于compile() 函數(shù)簡單實用示例,compile() 函數(shù)將一個字符串編譯為字節(jié)代碼,compile將代碼編譯為代碼對象,應用在代碼中可以提高效率,本文通過示例代碼給大家介紹的非常詳細,需要的朋友可以參考下2023-05-05
Numpy實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)(CNN)的示例
這篇文章主要介紹了Numpy實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)(CNN)的示例,幫助大家更好的理解和使用Numpy,感興趣的朋友可以了解下2020-10-10
python用函數(shù)創(chuàng)造字典的實例講解
在本篇文章里小編給大家整理的是一篇關(guān)于python用函數(shù)創(chuàng)造字典的實例講解內(nèi)容,有需要的朋友們可以學習參考下。2021-06-06
用python給csv里的數(shù)據(jù)排序的具體代碼
在本文里小編給大家分享的是關(guān)于用python給csv里的數(shù)據(jù)排序的具體代碼內(nèi)容,需要的朋友們可以學習下。2020-07-07

