Python函數注解


以下內容基於Python 3x

涉及的知識前提

  • 建議理解Python裝飾器后學習此內容

函數注解概述

函數注解可以針對函數的參數、返回值添加元數據,其為注解。

python是動態語言,變量賦值時不會強制聲明類型,且能隨時重新賦值。無法像靜態編譯型語言一樣,在編譯時發現基本問題。

函數的參數要求,沒有詳細的doc string或更新沒跟上,以至后續的使用者不能夠清晰明白設計者要求的參數類型。以上行為導致的出錯率變高,難以使用等問題。

函數注解可以對參數的要求類型進行注解,對函數返回值進行注解;其只是對做一個輔助性的說明,並不強制進行類型檢查

一些第三方工具(PyCharm)會對已定義的函數注解進行檢查標記提示等,在編寫代碼時同樣也可以通過編寫一個裝飾器,來進行一些檢查。

實際應用

inspect模塊

注解信息保存在函數的__annotations__屬性中,函數的參數檢查,一定是在函數實際運行之前,檢查傳遞進來的形參與原有的注解類型是否匹配。

__annotations__屬性是一個字典,對於位置參數的判斷,較為復雜,可直接使用inspect模塊獲取函數簽名信息。

# 注解x和y參數要求為int類型,返回結果為int類型
# Syntax:

def foo(x: int, y: int) -> int:
    return x + y

查看其__annotations__屬性信息:

def foo(x: int, y: int) -> int:
    return x + y


print(type(foo.__annotations__))
print(foo.__annotations__)

# 輸出結果:
<class 'dict'>
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

使用inspect模塊獲取簽名信息:

inspect.signature返回的是一個OrderedDict(有序字典)

import inspect


def foo(x: int, y: int) -> int:
    return x + y


sig = inspect.signature(foo)
print(sig.parameters)

# 輸出結果:
OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">)])

使用parameter方法,獲取更詳細的信息:

import inspect


def foo(x: int , y: int, z=20) -> int:
    return x + y + z


sig = inspect.signature(foo)
par = sig.parameters
print(par.items())
print(par["x"].name, "|-|", par["x"].annotation, "|-|", par["x"].default, "|-|", par["x"].empty, "|-|", par["x"].kind)
print(par["y"].name, "|-|", par["y"].annotation, "|-|", par["y"].default, "|-|", par["y"].empty, "|-|", par["y"].kind)
print(par["z"].name, "|-|", par["z"].annotation, "|-|", par["z"].default, "|-|", par["z"].empty, "|-|", par["z"].kind)

#輸出結果:
odict_items([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('z', <Parameter "z=20">)])
x |-| <class 'int'> |-| <class 'inspect._empty'> |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
y |-| <class 'int'> |-| <class 'inspect._empty'> |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD
z |-| <class 'inspect._empty'> |-| 20 |-| <class 'inspect._empty'> |-| POSITIONAL_OR_KEYWORD

parameter輸出結果的信息保存在元組中,只讀;

name:獲取參數名字;

annotation:獲取參數的注解,有可能沒有定義,則為_empty;

default:返回參數的缺省值,如其中z參數的20即為缺省值;

empty:特殊的類,用來標記default屬性或者注釋annotation屬性的空值;

kind:實參如何綁定到形參。以下是形參的類型:

  • POSITIONAL_ONLY,值必須是位置參數提供的(Python中並沒有絕對的位置參數,未實現)。

  • POSITIONAL_OR_KEYWORD,值可以作為關鍵字或者未知參數提供。

  • VAR_POSITIONAL,可變位置參數,對應*args。

  • KEYWORD_ONLY,keyword-only參數,對應*args之后出現的非可變關鍵字參數。

  • VAR_KEYWORD,可變關鍵字參數,對應**kwargs。

業務代碼

了解以上方法后,編寫一個函數裝飾器用來檢測傳參是否規范。

  • 首先在函數運行之前,獲取到函數簽名、及簽名的parameter信息將parameter信息。
  • kv結構的value轉換為列表保存。
  • 使用for循環檢查每個對應的parameter內參數(實際傳的參數)注解是否不為空並且同時是否不是已知類型(isinstance函數可以檢查傳的值是否是已知類型,返回True或False,簽名中參數的annotation類型為已知的)。
  • 匹配通過,則說明傳遞的參數和注解要求類型一致,否則則拋出TypeError類型的自定義錯誤信息。
import inspect
import functools


def parameter_check(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        sig = inspect.signature(fn)
        parameter = sig.parameters
        value = list(parameter.values())
        for i, par in enumerate(args):
            if value[i].annotation is not value[i].empty and not isinstance(par, value[i].annotation):
                raise TypeError("Parameter type error")

        for k, v in kwargs.items():
            if parameter[k].annotation is not parameter[k].empty and not isinstance(v, parameter[k].annotation):
                raise TypeError("Parameter type error")

        ret = fn(*args, **kwargs)
        return ret
    return wrapper


@parameter_check
def foo(x: int, y: int, z=20) -> int:
    return x + y + z


print(foo(1, 50, 29))

# 輸出結果:
80

# 更改傳參,查看效果:
print(foo("hhh", 50, 29))

# 輸出結果:
Traceback (most recent call last):
  File "E:/project/python/test.py", line 29, in <module>
    print(foo("hhh", 50, 29))
  File "E:/project/python/test.py", line 13, in wrapper
    raise TypeError("Parameter type error")
TypeError: Parameter type error

總結

通過對函數注解的了解,能夠更加規范代碼,提高效率,避免出錯。在實際應用中亦可方便的展開使用。

參考鏈接:PEP 3107 — Function Annotations


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM