在Python 3中實作類別型檢查器的簡單方法

來源:互聯網
上載者:User
樣本函數

為了開發類型檢查器,我們需要一個簡單的函數對其進行實驗。歐幾裡得演算法就是一個完美的例子:

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

在上面的樣本中,參數 a 和 b 以及傳回值應該是 int 類型的。預期的類型將會以函數註解的形式來表達,函數註解是 Python 3 的一個新特性。接下來,類型檢查機制將會以一個裝飾器的形式實現,註解版本的第一行代碼是:

def gcd(a: int, b: int) -> int:

使用“gcd.__annotations__”可以獲得一個包含註解的字典:

>>> gcd.__annotations__{'return': , 'b': , 'a': }>>> gcd.__annotations__['a']

需要注意的是,傳回值的註解儲存在鍵“return”下。這是有可能的,因為“return”是一個關鍵字,所以不能用作一個有效參數名。
檢查傳回值類型

傳回值註解儲存在字典“__annotations__”中的“return”鍵下。我們將使用這個值來檢查傳回值(假設註解存在)。我們將參數傳遞給原始函數,如果存在註解,我們將通過註解中的值來驗證其類型:

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”替換函數gcd的傳回值來測試上面的代碼:

 Traceback (most recent call last): File "typechecker.py", line 9, in   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

由上面的結果可知,確實檢查了傳回值的類型。
檢查參數類型

函數的參數存在於關聯代碼對象的“co_varnames”屬性中,在我們的例子中是“gcd.__code__.co_varnames”。元組包含了所有局部變數的名稱,並且該元組以參數開始,參數數量儲存在“co_nlocals”中。我們需要遍曆包括索引在內的所有變數,並從參數“args”中擷取參數值,最後對其進行類型檢查。

得到了下面的代碼:

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

在上面的迴圈中,i是數組args中參數的以0起始的索引,arg是包含其值的字串。可以利用“f.__code__.co_varnames[i]”讀取到參數的名稱。類型檢查代碼與傳回值類型檢查完全一樣(包括錯誤訊息的異常)。

為了對關鍵字參數進行類型檢查,我們需要遍曆參數kwargs。此時的類型檢查幾乎與第一個迴圈中相同:

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

將類型檢查代碼寫成一個函數將會使代碼更加清晰。為了簡化代碼,我們修改錯誤資訊,而當傳回值是無效的類型時,將會使用到這些錯誤資訊。我們也可以利用 functools 模組中的 wraps 方法,將封裝函數的一些屬性複製到 wrapper 中(這使得 wrapper 看起來更像原來的函數):

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

結論

註解是 Python 3 中的一個新元素,本文例子中的使用方法很普通,你也可以想象很多特定領域的應用。雖然上面的實現代碼並不能滿足實際產品要求,但它的目的本來就是用作概念驗證。可以對其進行以下改善:

  • 處理額外的參數( args 中意想不到的項目)
  • 預設值類型檢查
  • 支援多個類型
  • 支援模板類型(例如,int 型列表)
  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.