在包级别使用类型注释

时间:2016-06-22 12:58:35

标签: python django python-3.x type-hinting

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

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

我目前的想法是在包级别包含类型注释:在setup.py(或其他地方)。我定义了一个规则"每次使用变量request时,它都是django.http.HttpRequest" 类型。对于名称为request的变量指向不同类型的边缘情况,应该进行显式注释,但这些注释很少。

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

2 个答案:

答案 0 :(得分:5)

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

  

存根文件是包含类型提示的文件,仅供类型检查程序使用,而不是在运行时使用。

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

它们基本上由带注释的函数签名组成,其主体包含单个省略号...

你可以解决这个问题的一种方法是使用帮助函数来写出你的.pyi,或者至少写出它的大部分内容。可能有很多方法可以做到这一点,哪个是最好的可能是另一个问题。

作为一种方法的示例,我将写出一种方法来注释inspect模块中具有名为object的参数的每个函数,并且不以下划线开头。我正在键入这个特定的模块,因为我也将它用于识别功能和抓取他们的签名。

inspect模块中,我将使用getmembersisfunctionsignature函数。

# 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))  

这个逻辑可以扩展到method s,其他参数类型等等。 此外,重要提示.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文件并提供您需要的功能。

答案 1 :(得分:0)

您可以使用自定义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

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

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

并将特定注释放在my_view()的定义中。