【问题标题】:lint usages of functions with @deprecated decorator使用 @deprecated 装饰器对函数的 lint 用法
【发布时间】:2020-10-29 14:51:28
【问题描述】:

是否有一个 linter 可以检测使用 deprecated 包中的 @deprecated 装饰器标记为已弃用的函数的使用情况?

例如在

from deprecated import deprecated

def realfun():
    print('Hey there')

@deprecated(reason="this is a test")
def myfun():
    realfun()

if __name__ == "__main__":
    myfun()

以PYTHONWARNINGS="default::DeprecationWarning" python testfile.py 运行时我会收到运行时警告,但pylint、mypy 和flake8 似乎(至少我如何运行它们)对调用myfun 感到满意。

【问题讨论】:

  • 这只是一个添加警告的随机包。如果我要创建一个名为 hey 的包,我的 linter 甚至都不知道它。你需要做什么:1)要么找到 linter 如何建议解决弃用(可能是另一个包或评论。2)要么创建一个自定义规则,比如说 pylint 并将其添加到你的 .pylintrc。检查custom checkers

标签: python static-analysis deprecation-warning


【解决方案1】:

正如其他人所提到的,您必须编写一个自定义的pylint 检查器。其工作方式是定义一个将 pylint 的 BaseChecker 子类化的类,并定义要发出什么警告以及何时发出警告。

下面的代码正是这样做的。它有点针对deprecated 包的特定关键字参数量身定制,但只要有人使用名为deprecated 的装饰器来标记函数或类,它通常应该可以工作,并且会发出pylint 警告W0001(随意更改它,例如更改为错误)。它还应该提供信息丰富的消息。

要使用它,请将代码添加到名为(例如)deprecated_checker.py 的文件中,然后将包含 deprecated_checker.py 的文件夹添加到 PYTHONPATH 或将源添加到 pylint/checkers 文件夹。然后,您可以通过运行 pylint 和选项 --load-plugins=deprecated_checker 来进行 lint。

有关编写自己的跳棋的更多信息,look here。

from astroid.nodes import Call, ClassDef, FunctionDef, Name
from pylint.checkers import BaseChecker
from pylint.interfaces import IAstroidChecker


class DeprecatedChecker(BaseChecker):
    __implements__ = IAstroidChecker

    name = "no-deprecated"
    priority = -1
    msgs = {
        "W0001": (
            "%s %s is deprecated since version %s; reason: %s.",
            "deprecated",
            "Functions that have been marked via annotations as deprecated should not be used.",
        )
    }

    def __init__(self, linter=None):
        super().__init__(linter)

    def visit_decorators(self, node):
        # Check if there are decorators
        if node.nodes is None:
            return

        # Figure out whether its a class or function
        # that is deprecated, and get relevant info
        if isinstance(node.parent, ClassDef):
            parent_type = "Class"
        elif isinstance(node.parent, FunctionDef):
            parent_type = "Function"
        parent_name = node.parent.name

        # Check each decorator to see if its deprecating
        for decorator in node.get_children():
            if isinstance(decorator, Call):
                if decorator.func.name == "deprecated":
                    version = "(not specified)"
                    reason = "not specified"
                    if decorator.keywords is not None:
                        for kw in decorator.keywords:
                            if kw.arg == "version":
                                version = f'"{kw.value.value}"'
                            if kw.arg == "reason":
                                reason = f'"{kw.value.value}"'
                    self.add_message(
                        "deprecated",
                        node=node.parent,
                        args=(parent_type, parent_name, version, reason),
                    )
            elif isinstance(decorator, Name):
                if decorator.name == "deprecated":
                    self.add_message(
                        "deprecated",
                        node=node.parent,
                        args=(
                            parent_type,
                            parent_name,
                            "(not specified)",
                            "not specified",
                        ),
                    )

def register(linter):
    linter.register_checker(DeprecatedChecker(linter))

如果你已经完成了所有这些,然后检查存根文件tmp.py

from deprecated import deprecated


@deprecated
def fn_stmt():
    pass

@deprecated(version="0.1.0")
def fn_version():
    pass

@deprecated(reason="I'm mean")
def fn_reason():
    pass

@deprecated(version="0.1.0", reason="I'm mean")
def fn_both():
    pass

@deprecated
class ClassStmt:
    pass

@deprecated(version="0.1.0")
class ClassVersion:
    pass

@deprecated(reason="I'm mean")
class ClassReason:
    pass

@deprecated(version="0.1.0", reason="I'm mean")
class ClassBoth:
    pass

使用命令pylint --load-plugins=deprecated_checker --disable=all --enable=deprecated tmp.py 会得到你

************* Module tmp
tmp.py:5:0: W0001: Function fn_stmt is deprecated since version (not specified); reason: (not specified). (deprecated)
tmp.py:9:0: W0001: Function fn_version is deprecated since version 0.1.0; reason: (not specified). (deprecated)
tmp.py:13:0: W0001: Function fn_reason is deprecated since version (not specified); reason: I'm mean. (deprecated)
tmp.py:17:0: W0001: Function fn_both is deprecated since version 0.1.0; reason: I'm mean. (deprecated)
tmp.py:20:0: W0001: Class ClassStmt is deprecated since version (not specified); reason: (not specified). (deprecated)
tmp.py:24:0: W0001: Class ClassVersion is deprecated since version 0.1.0; reason: (not specified). (deprecated)
tmp.py:28:0: W0001: Class ClassReason is deprecated since version (not specified); reason: I'm mean. (deprecated)
tmp.py:32:0: W0001: Class ClassBoth is deprecated since version 0.1.0; reason: I'm mean. (deprecated)

-------------------------------------------------------------------
Your code has been rated at 5.29/10 (previous run: -2.35/10, +7.65)

【讨论】:

    【解决方案2】:

    您应该查看 How to warn about class (name) deprecation 以将您自己的规则添加到 lint。没有用于标记弃用的标准库方法,因此不会内置到工具中。最接近的是 warnings 库中的特定类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-16
      • 2010-11-20
      • 2016-07-09
      • 2018-10-16
      • 1970-01-01
      • 1970-01-01
      • 2021-10-01
      相关资源
      最近更新 更多