在包级别使用类型注释

Using type annotations at package level

我想使用注解在 IDE 中实现更好的自动完成,也许以后在自动化测试中进行类型测试。但是如何在不引入大量额外代码的情况下为许多方法添加类型注释呢?

例如,假设您有一个使用 Django 框架的大型代码库,其中包括大约 200 个带有 request 参数的方法;添加类型注释 200 次会使代码膨胀。

我目前的想法是在包级别包含类型注释:在 setup.py(或其他地方)。我定义了一个规则 "every time the variable request gets used, it is of type django.http.HttpRequest"。对于名称为 request 的变量指向不同类型的边缘情况,应该进行显式注释,但这种情况很少。

如何实现?还有其他方法可以实现总体目标吗?

您可以使用自定义 middleware(未测试):

from django.http import HttpRequest

class RequireHttpRequest():
    def process_view(request, view_func, view_args, view_kwargs):
        require_http_request = view_kwargs.get('require_http_request', True)
        if require_http_request and type(h) != HttpRequest:
            raise SomeException
        return None

默认情况下,此中间件会影响所有 URL。对于少数不想检查request类型的url,在urls.py对应行添加参数:

url(r'^foo/$', views.your_view, require_http_request=False)

并将具体注解放在my_view()的定义中。

如果我的理解是正确的,这听起来像是 存根文件 的用例,如 PEP 484 中所述:

Stub files are files containing type hints that are only for use by the type checker, not at runtime.

存根文件几乎可以满足您将代码与类型注释分开的要求,从而避免复杂类型提示的膨胀和混淆效果。它们有一个前缀 .pyi 并且,如果由 IDE 实现的类型检查器想要符合 PEP 484,则必须始终由类型检查器检查它们是否存在。

它们基本上由带注释的函数签名和包含单个省略号的正文组成 ...


解决此问题的一种方法是使用辅助函数来写出您的 .pyi,或者至少是其中的大部分。可能有很多方法可以做到这一点,哪种方法最好可能完全是另一个问题。

作为执行此操作的示例,我将编写一种方法来注释 inspect 模块中具有名为 object 且不以以下参数开头的每个函数一个下划线。我正在输入这个特定的模块,因为我还将使用它来识别函数并获取它们的签名。

inspect 模块中,我将使用 getmembers, isfunction and signature 函数。

# gets members of inspect module {member_name: member_type} dict.
members = getmembers(inspect)  

# loop through members
for name, type in members:

    # grab functions that don't start with an underscore  
    if isfunction(type) and not name.startswith('_'):

        # grab its signature and
        # check if it has a parameter named object
        sig = signature(type)  
        if 'object' in sig.parameters:

            # add the annotation to the object parameter
            param = sig.parameter['object']
            s = sig.replace(parameters = [param.replace(annotation="object")])

            # here you normally write to .pyi file
            print('def {0} {1}: ...'.format(name, s))  

这个逻辑可以扩展到methods,其他参数类型等等。 另外,重要说明.pyi 文件应与您正在注释的模块同名,因此在本例中应为 inspect.pyi

目前,这只是打印出所有具有名为 object 的参数的函数以及我们提供的 'annotation' (object):

def findsource(object:'object'): ...
def formatannotationrelativeto(object:'object'): ...
def getabsfile(object:'object'): ...
def getcomments(object:'object'): ...
def getdoc(object:'object'): ...
def getfile(object:'object'): ...
def getmembers(object:'object'): ...
def getmodule(object:'object'): ...
def getsource(object:'object'): ...
# .. and so on..

大多数生产就绪检查器将阅读此 .pyi 文件并提供您需要的功能。