def fun(a:int, b=1, *c, d, e=2, **f) -> str: pass這裡主要是說幾點與python2中不同的點。 1)分號後面表示參數的annotation,這個annotation可以是任意的python對象,不一定要type類型的,比如 a:"This is parameter a!" 這樣也是可以的。 2)在可變位置參數列表c和可變關鍵字參數字典f中間的參數叫做 指定關鍵字參數(keyword only parameter),這個關鍵字的意思是你在函數調用時只能用關鍵字形式調用。比如調用下面的這個例子,調用fun(1,2)是會報trace的,因為沒有指定keyword-only參數,如果調用fun(1,c=2)則不會出錯。注意,如果要定義keyword-only參數,那麼必須在其前面加一個可變的位置參數。
# _*_ coding: utf-8 import functools import inspect from itertools import chain def typesafe(func): """ Verify that the function is called with the right arguments types and that it returns a value of the right type, accordings to its annotations. """ spec = inspect.getfullargspec(func) annotations = spec.annotations for name, annotation in annotations.items(): if not isinstance(annotation, type): raise TypeError("The annotation for '%s' is not a type." % name) error = "Wrong type for %s: expected %s, got %s." # Deal with default parameters defaults = spec.defaults or () defaults_zip = zip(spec.args[-len(defaults):], defaults) kwonlydefaults = spec.kwonlydefaults or {} for name, value in chain(defaults_zip, kwonlydefaults.items()): if name in annotations and not isinstance(value, annotations[name]): raise TypeError(error % ('default value of %s' % name, annotations[name].__name__, type(value).__name__)) @functools.wraps(func) def wrapper(*args, **kwargs): # Populate a dictionary of explicit arguments passed positionally explicit_args = dict(zip(spec.args, args)) keyword_args = kwargs.copy() # Add all explicit arguments passed by keyword for name in chain(spec.args, spec.kwonlyargs): if name in kwargs: explicit_args[name] = keyword_args.pop(name) # Deal with explict arguments for name, arg in explicit_args.items(): if name in annotations and not isinstance(arg, annotations[name]): raise TypeError(error % (name, annotations[name].__name__, type(arg).__name__)) # Deal with variable positional arguments if spec.varargs and spec.varargs in annotations: annotation = annotations[spec.varargs] for i, arg in enumerate(args[len(spec.args):]): if not isinstance(arg, annotation): raise TypeError(error % ('variable argument %s' % (i+1), annotation.__name__, type(arg).__name__)) # Deal with variable keyword argument if spec.varkw and spec.varkw in annotations: annotation = annotations[spec.varkw] for name, arg in keyword_args.items(): if not isinstance(arg, annotation): raise TypeError(error % (name, annotation.__name__, type(arg).__name__)) # Deal with return value r = func(*args, **kwargs) if 'return' in annotations and not isinstance(r, annotations['return']): raise TypeError(error % ('the return value', annotations['return'].__name__, type(r).__name__)) return r return wrapper
對於上面的代碼:
19-27 行比較好理解,就是驗證函數定義時,默認參數和annotation是否匹配,如果不匹配的就返回定義錯誤。 32 行spec.args和args的長度不一定會一樣長,如果args的長度比較長,說明多出來的是屬於可變位置參數,在46行到52行處理;如果spec.args的位置比較長,說明沒有可變位置參數,多出來的已經通過默認值指定了,已經在19-27行處理了。 34-37 行主要是考慮到,1)有一些確定的參數在函數調用時也有可能使用參數名進行調用;2)指定關鍵字參數必須使用參數名進行調用。 這樣處理後,keyword_args中就只存儲可變關鍵字參數了,而explicit_args裡存儲的是包括確定型參數以及指定關鍵字參數。 39-44 行處理explicit_args裡面的參數 46-53 行處理可變位置參數 55-62 行處理的是可變關鍵字參數 64-69 行處理返回值 當然這個函數對類型的要求太高了,而並沒有像C++那樣有默認的類型轉換。以下是自己寫的一個帶有默認類型轉換的代碼,如有錯誤望指出。# _*_ coding: utf-8 import functools import inspect from itertools import chain def precessArg(value, annotation): try: return annotation(value) except ValueError as e: print('value:', value) raise TypeError('Expected: %s, got: %s' % (annotation.__name__, type(value).__name__)) def typesafe(func): """ Verify that the function is called with the right arguments types and that it returns a value of the right type, accordings to its annotations. """ spec = inspect.getfullargspec(func) annotations = spec.annotations for name, annotation in annotations.items(): if not isinstance(annotation, type): raise TypeError("The annotation for '%s' is not a type." % name) error = "Wrong type for %s: expected %s, got %s." # Deal with default parameters defaults = spec.defaults and list(spec.defaults) or [] defaults_zip = zip(spec.args[-len(defaults):], defaults) i = 0 for name, value in defaults_zip: if name in annotations: defaults[i] = precessArg(value, annotations[name]) i += 1 func.__defaults__ = tuple(defaults) kwonlydefaults = spec.kwonlydefaults or {} for name, value in kwonlydefaults.items(): if name in annotations: kwonlydefaults[name] = precessArg(value, annotations[name]) func.__kwdefaults__ = kwonlydefaults @functools.wraps(func) def wrapper(*args, **kwargs): keyword_args = kwargs.copy() new_args = args and list(args) or [] new_kwargs = kwargs.copy() # Deal with explicit argument passed positionally i = 0 for name, arg in zip(spec.args, args): if name in annotations: new_args[i] = precessArg(arg, annotations[name]) i += 1 # Add all explicit arguments passed by keyword for name in chain(spec.args, spec.kwonlyargs): poped_name = None if name in kwargs: poped_name = keyword_args.pop(name) if poped_name is not None and name in annotations: new_kwargs[name] = precessArg(poped_name, annotations[name]) # Deal with variable positional arguments if spec.varargs and spec.varargs in annotations: annotation = annotations[spec.varargs] for i, arg in enumerate(args[len(spec.args):]): new_args[i] = precessArg(arg, annotation) # Deal with variable keyword argument if spec.varkw and spec.varkw in annotations: annotation = annotations[spec.varkw] for name, arg in keyword_args.items(): new_kwargs[name] = precessArg(arg, annotation) # Deal with return value r = func(*new_args, **new_kwargs) if 'return' in annotations: r = precessArg(r, annotations['return']) return r return wrapper if __name__ == '__main__': print("Begin test.") print("Test case 1:") try: @typesafe def testfun1(a:'This is a para.'): print('called OK!') except TypeError as e: print("TypeError: %s" % e) print("Test case 2:") try: @typesafe def testfun2(a:int,b:str = 'defaule'): print('called OK!') testfun2('str',1) except TypeError as e: print("TypeError: %s" % e) print("test case 3:") try: @typesafe def testfun3(a:int, b:int = 'str'): print('called OK') except TypeError as e: print('TypeError: %s' % e) print("Test case 4:") try: @typesafe def testfun4(a:int = '123', b:int = 1.2): print('called OK.') print(a, b) testfun4() except TypeError as e: print('TypeError: %s' % e) @typesafe def testfun5(a:int, b, c:int = 1, d = 2, *e:int, f:int, g, h:int = 3, i = 4, **j:int) -> str : print('called OK.') print(a, b, c, d, e, f, g, h, i, j) return 'OK' print("Test case 5:") try: testfun5(1.2, 'whatever', f = 2.3, g = 'whatever') except TypeError as e: print('TypeError: %s' % e) print("Test case 6:") try: testfun5(1.2, 'whatever', 2.2, 3.2, 'e1', f = '123', g = 'whatever') except TypeError as e: print('TypeError: %s' % e) print("Test case 7:") try: testfun5(1.2, 'whatever', 2.2, 3.2, 12, f = '123', g = 'whatever') except TypeError as e: print('TypeError: %s' % e) print("Test case 8:") try: testfun5(1.2, 'whatever', 2.2, 3.2, 12, f = '123', g = 'whatever', key1 = 'key1') except TypeError as e: print('TypeError: %s' % e) print("Test case 9:") try: testfun5(1.2, 'whatever', 2.2, 3.2, 12, f = '123', g = 'whatever', key1 = '111') except TypeError as e: print('TypeError: %s' % e) print('Test case 10:') @typesafe def testfun10(a) -> int: print('called OK.') return 'OK' try: testfun10(1) except TypeError as e: print('TypeError: %s' % e)