Note: The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
def expects(*types): def func_in(fn): def in_wrap(*args, **kwargs): if len(types) != len(args): raise SyntaxError(f"Expected {len(types)}, got {len(args)}.") for t, v in zip(types, args): if not isinstance(v, t): raise TypeError(f"Value '{v}' not of type '{t}'") return fn(*args, **kwargs) return in_wrap return func_in
def outputs(*types): def func_out(fn): def in_wrap(*args, **kwargs): finalv = fn(*args, **kwargs) if not hasattr(finalv, '__iter__'): if not isinstance(finalv, types): raise TypeError(f"Value '{finalv}' not of type '{types}'") else: for t, v in zip(types, finalv): if not isinstance(v, t): raise TypeError(f"Value '{v}' not of type '{t}'") return finalv return in_wrap return func_out
下一步是提供工具来检查输入是否属于一系列类型,以满足我们遇到的数字重载问题。如果我们可以编写一个合约来检查一个值是否与一系列类型匹配,它将帮助我们编写更好、更可重用的通用 Python 代码。为此,我们需要编写实现与 any 和 all 功能。
def or_contract(*types): def inner_check(*vals): for x in vals: res = [isinstance(x, t) for t in types] if not any(res): return False return True return inner_check
在这里,我们强制逻辑类似于布尔值 or 运算符,其中左侧或右侧的一个值必须满足谓词才能算作真实。为此,我们检查每个值,并在类型列表中运行它,如果没有出现单个匹配,我们认为它是无效的。在我们的例子中,我们把 int 和 float 成一个 or_contract ,这将帮助我们验证传入的值。
更复杂的合同是 and_contract ,这只能通过改变一些事情来实现。
def and_contract(*types): def inner_check(*vals): for x in vals: res = [isinstance(x, t) for t in types] if not all(res): return False return True return inner_check
>>> d = {} >>> isinstance(d, dict) True >>> isinstance(d, defaultdict) False >>> d2 = defaultdict() >>> isinstance(d, defaultdict) False # dict() does not qualify as defaultdict() >>> isinstance(d2, defaultdict) True # qualifies as a defaultdict >>> isinstance(d2, dict) True # also qualifies as a dict, can be and_contract'd
def contract_in(*contracts): if not all([callable(c) for c in contracts]): raise TypeError("All types must be callable contracts") def fn_wrap(fn): def arg_wrap(*args, **kwargs): if len(contracts) != len(args): raise SyntaxError(f"Expected {len(contracts)} inputs, got {len(args)}") for con, val in zip(contracts, args): if not con(val): raise TypeError(f"Expecting a value to satisfy {con.__name__}, got {type(val)}") return fn(*args, **kwargs) return arg_wrap return fn_wrap
def contract_out(*contracts): def func_out(fn): def in_wrap(*args, **kwargs): finalv = fn(*args) if not hasattr(finalv, '__iter__'): for con in contracts: if not con(finalv): raise TypeError(f"Expecting value to satisfy {con.__name__}, got '{type(finalv)}'") else: for con, val in zip(contracts, finalv): if not con(val): raise TypeError(f"Expecting value to satisfy {con.__name__}, got '{type(val)}'") return finalv return in_wrap return func_out