在Python 3中實(shí)現(xiàn)類型檢查器的簡單方法
示例函數(shù)
為了開發(fā)類型檢查器,我們需要一個(gè)簡單的函數(shù)對其進(jìn)行實(shí)驗(yàn)。歐幾里得算法就是一個(gè)完美的例子:
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 以及返回值應(yīng)該是 int 類型的。預(yù)期的類型將會(huì)以函數(shù)注解的形式來表達(dá),函數(shù)注解是 Python 3 的一個(gè)新特性。接下來,類型檢查機(jī)制將會(huì)以一個(gè)裝飾器的形式實(shí)現(xiàn),注解版本的第一行代碼是:
def gcd(a: int, b: int) -> int:
使用“gcd.__annotations__”可以獲得一個(gè)包含注解的字典:
>>> gcd.__annotations__ {'return': <class 'int'>, 'b': <class 'int'>, 'a': <class 'int'>} >>> gcd.__annotations__['a'] <class 'int'>
需要注意的是,返回值的注解存儲(chǔ)在鍵“return”下。這是有可能的,因?yàn)椤皉eturn”是一個(gè)關(guān)鍵字,所以不能用作一個(gè)有效的參數(shù)名。
檢查返回值類型
返回值注解存儲(chǔ)在字典“__annotations__”中的“return”鍵下。我們將使用這個(gè)值來檢查返回值(假設(shè)注解存在)。我們將參數(shù)傳遞給原始函數(shù),如果存在注解,我們將通過注解中的值來驗(yàn)證其類型:
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ù)的參數(shù)存在于關(guān)聯(lián)代碼對象的“co_varnames”屬性中,在我們的例子中是“gcd.__code__.co_varnames”。元組包含了所有局部變量的名稱,并且該元組以參數(shù)開始,參數(shù)數(shù)量存儲(chǔ)在“co_nlocals”中。我們需要遍歷包括索引在內(nèi)的所有變量,并從參數(shù)“args”中獲取參數(shù)值,最后對其進(jìn)行類型檢查。
得到了下面的代碼:
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是包含其值的字符串??梢岳谩癴.__code__.co_varnames[i]”讀取到參數(shù)的名稱。類型檢查代碼與返回值類型檢查完全一樣(包括錯(cuò)誤消息的異常)。
為了對關(guān)鍵字參數(shù)進(jìn)行類型檢查,我們需要遍歷參數(shù)kwargs。此時(shí)的類型檢查幾乎與第一個(gè)循環(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
將類型檢查代碼寫成一個(gè)函數(shù)將會(huì)使代碼更加清晰。為了簡化代碼,我們修改錯(cuò)誤信息,而當(dāng)返回值是無效的類型時(shí),將會(huì)使用到這些錯(cuò)誤信息。我們也可以利用 functools 模塊中的 wraps 方法,將包裝函數(shù)的一些屬性復(fù)制到 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 中的一個(gè)新元素,本文例子中的使用方法很普通,你也可以想象很多特定領(lǐng)域的應(yīng)用。雖然上面的實(shí)現(xiàn)代碼并不能滿足實(shí)際產(chǎn)品要求,但它的目的本來就是用作概念驗(yàn)證??梢詫ζ溥M(jìn)行以下改善:
- 處理額外的參數(shù)( args 中意想不到的項(xiàng)目)
- 默認(rèn)值類型檢查
- 支持多個(gè)類型
- 支持模板類型(例如,int 型列表)
相關(guān)文章
django foreignkey外鍵使用的例子 相當(dāng)于left join
今天小編就為大家分享一篇django foreignkey外鍵使用的例子 相當(dāng)于left join,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08Python?異之如何同時(shí)運(yùn)行多個(gè)協(xié)程詳解
這篇文章主要為大家介紹了Python?異之如何同時(shí)運(yùn)行多個(gè)協(xié)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03Django admin model 漢化顯示文字的實(shí)現(xiàn)方法
今天小編就為大家分享一篇Django admin model 漢化顯示文字的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08python求加權(quán)平均值的實(shí)例(附純python寫法)
今天小編就為大家分享一篇python求加權(quán)平均值的實(shí)例(附純python寫法),具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08pandas之?dāng)?shù)據(jù)修改與基本運(yùn)算方式
這篇文章主要介紹了pandas之?dāng)?shù)據(jù)修改與基本運(yùn)算方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02python heic后綴圖片文件轉(zhuǎn)換成jpg格式的操作
這篇文章主要介紹了python heic后綴圖片文件轉(zhuǎn)換成jpg格式的操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03