Pylint 2.12.1 与 2.11.1 不同的行为

Pylint 2.12.1 different behaviour from 2.11.1

我们正在从 Pylint 2.11.1 升级到 2.12.1(最近发布),我们在检查旧版本通过的代码时看到奇怪的行为。具体来说,我们有以下方法(没有代码,但除此之外正是我们拥有的代码,因此毫无疑问存在剪切'n'粘贴错误):

async def run_callback(callback: common_types.AnyCallback) -> None:
    """Run the callback, handles sync and async functions.

    This WILL block the event loop if a sync function is called this way.
    IF a sync callback needs to be called, it should be wrapped in an
    async function and then called with run in executor. This cannot be
    done at this level because run_in_executor is a separate thread.
    Most async stuff is not thread safe and vice versa, so this is the
    minimal abstraction which won't introduce race conditions, the
    developer needs to handle it by manually doing a run_in_executor.

    Example:
        def sync_thing():
            pass

        async def async_thing():
            pass

        from cmnlibpy import utils

        await util.run_callback(sync_thing)
        await util.run_callback(async_thing)

    Args:
        callback:
            sync or async function which will be called
    """

虽然这对 2.11.1 来说是完全可以接受的,但较新的版本会拒绝参数,尽管它 文档字符串中:

************* Module blah.blah.blah
blah/blah/blah.py:20:0: W9015: "callback" missing in parameter documentation (missing-param-doc)

我试过在文档字符串中移动它、重命名它以及进行各种其他操作都无济于事。如果您担心类型,它是在同一包中 common_types.py 的导入项中定义的:

from typing import Union, Awaitable, Callable

SyncCallback = Callable[[], None]
AsyncCallback = Callable[[], Awaitable[None]]
AnyCallback = Union[SyncCallback, AsyncCallback]

为什么新的 PyLint 会抱怨这个?它是否已决定弃用 Google 样式的参数规范?


进一步调查:当参数和描述在一行时,问题似乎没有表现出来,例如:

callback: description goes here.

extensions/_check_docs_utils.py 中参数行的 GoogleDocstring 正则表达式的差异似乎表明它应该是一个冒号与同一行的描述,或者没有冒号与下一行的描述:

- \s*  (\w+)                                                          # identifier
- \s*  :
- \s*  (?:({GoogleDocstring.re_multiple_type})(?:,\s+optional)?)?     # optional type declaration
- \n                                                                  # description starts on a new line
- \s* (.*)                                                            # description

+ \s*  (\*{{0,2}}\w+)(\s?(:|\n))                                      # identifier with potential asterisks
+ \s*  (?:({GoogleDocstring.re_multiple_type})(?:,\s+optional)?\n)?   # optional type declaration
+ \s* (.*)                                                            # optional description

但是,即使我将 : 留在行尾,多行版本似乎也不起作用。


应 PyLint 开发人员的要求,我打开了一个错误报告(详情请参阅 https://github.com/PyCQA/pylint/issues/5452)。

事实证明,处理跨多行的参数规范存在问题。

代码使用的正则表达式始终提供三个组成部分(名称、类型和描述),但代码有些奇怪,导致既没有设置类型也没有设置描述的情况,这意味着没有记录参数.

负责的代码在 extensions/_check_docs_utils.py:

re_only_desc = re.search(":\n", entry)
if re_only_desc:
    param_name = match.group(1)
    param_desc = match.group(2)
    param_type = None
else:
    param_name = match.group(1)
    param_type = match.group(2)
    param_desc = match.group(3)

我不确定为什么 :\n 的存在被用来决定类型不可用,但这似乎不正确。它的存在意味着描述在下一行(与缺失类型无关)但是你使用 \s 的事实意味着换行符被视为与任何其他白色 space 在节所以它是无关紧要的。

因为用于此的正则表达式捕获所有三组,无论类型是否存在,如果不存在,则第二组(参数类型)设置为 None,第三组仍然描述(它不会移动到第 2 组)。所以 if 的第一部分将类型和描述都设置为 None,停止添加变量。

如果代码改为(不需要if):

param_name = match.group(1)
param_type = match.group(2)
param_desc = match.group(3)

然后下面的第一个测试用例仍然有效,第二个开始工作:

Args:
    first_param: description
    second_param:
        description

bug report and pull request 上的完整详细信息,维护人员很高兴与他们合作完成修复,希望它会在 2.12.2 中出现。

作为一种解决方法,您可以确保整个参数规范都在一行中,例如:

""" Doc string goes here.

    Args:
        param: The parameter.
"""