定义签名以创建漂亮的api
autosig的Python项目详细描述
动机
当我看到一个伟大的api时,我总是观察到一种高度的一致性:在语法层面上类似的命名和排序参数;在语义层面上类似的默认值、允许值的范围等。在查看代码时,我们看不到这些规则非常明确地表示出来。
假设我们开始开发一个具有三个入口点的库, map , reduce 和 filter :
from collections import Iterable def map(function, iterable): assert callable(function) assert isinstance(iterable, Iterable) return (function(x) for x in iterable) def reduce(function, iterable): total = next(iterable) for x in iterable: total = function(total, x) return total def filter(iterable, fun): if not isinstance(iterable, Iterable): iterable = [iterable] if isinstance(fun, set): fun = lambda x: x in fun return (x for x in iterable if fun(x))
但这几乎不是精心制作的。参数的顺序和命名不一致。一个函数立即检查其参数。下一个没有。第三个尝试某些转换来尝试使用不可iterable或函数的参数。构建严格或容忍的api是有原因的,但是在同一个api中混合使用这两个api是不太可能的,除非是故意的(例如,为每个函数提供一个严格和容忍的版本)。在这个小api中解决这些问题并不困难,但我们最终会得到重复的逻辑,在可预见的将来我们需要保持一致。让我们用 autosig 方法来代替它:
from autosig import param, Signature, autosig, check from collections import Iterable def to_callable(x): return (lambda y: y in x) if isinstance(x, set) else x def to_iterable(x): return x if isinstance(x, Iterable) else [x] API_signature = Signature( function=param(converter=to_callable, validator=check(callable)), iterable=param(converter=to_iterable, validator=check(Iterable))) @autosig(API_signature) def map(function, iterable): return (function(x) for x in iterable) @autosig(API_signature) def reduce(function, iterable): total = next(iterable) for x in iterable: total = function(total, x) return total @autosig(API_signature) def filter(function, iterable): return (x for x in iterable if function(x))
让我们一步一步来。首先我们定义了2个简单的转换 功能。这是独立于autosig的良好的第一步。接下来我们创建 有两个参数的签名对象。这些是用 定义需要对其执行的检查和转换 参数,与使用该签名的函数无关。 check 创建一个函数,该函数使用其参数(可调用或类型)来 验证一个参数(可调用的调用,该参数作为自己的参数 必须返回true;类型必须是参数的类型)。最后,我们重复 o的定义三个api函数,附加刚刚定义的签名 使用decorator,然后跳过所有检查和转换逻辑 直接去参加宴会!
以少量代码为代价,我们获得了很多:
- 明确定义所需的api签名,采用一个简单的原则;
- 该签名与API函数的关联,在加载时检查-没有错误空间;
- 统一应用转换和验证逻辑,无需重复;
autosig 是api设计器的专业工具!如果您想查看使用autosig的真正包,请查看altair食谱。