如何为pluggy hook规范注释类型?

2024-09-27 21:31:59 发布

您现在位置:Python中文网/ 问答频道 /正文

我想将类型注释添加到我的pluggy hook规范中,以便可以对hook实现进行类型检查。使用pluggy documentation中的简化示例:

import pluggy  # type: ignore

hookspec = pluggy.HookspecMarker("myproject")
hookimpl = pluggy.HookimplMarker("myproject")


class MySpec(object):
    """A hook specification namespace."""

    @hookspec
    def myhook(self, arg1, arg2):
        """My special little hook that you can customize."""


class Plugin_1(object):
    """A hook implementation namespace."""

    @hookimpl
    def myhook(self, arg1, arg2):
        print("inside Plugin_1.myhook()")
        return arg1 + arg2 + "a" # intentional error


# create a manager and add the spec
pm = pluggy.PluginManager("myproject")
pm.add_hookspecs(MySpec)
# register plugins
pm.register(Plugin_1())
# call our `myhook` hook
# intentional incompatible type for parameter arg2
results = pm.hook.myhook(arg1=1, arg2="1")
print(results)

我相信正确有效的诠释应该是:

def myhook(self, arg1: int, arg2: int) -> int: ...

我试着把这个注释添加到hookspec中。如我所料,这不起作用。我相信这是因为pluggy实现的间接寻址是动态的。必须运行代码,以便PluginManageradd_hookspecs()方法可以定义可用的钩子。你知道吗

我看到pm.hookpluggy.hooks._HookRelay类型,pm.hook.myhookpluggy.hooks._HookCaller的一个实例,它有一个__call__()方法。你知道吗

我尝试使用stubgen为pluggy创建一组.pyi文件,然后以两种不同的方式将注释添加到pluggy.hooks._HookCaller

class _HookCaller:
    def __init__(self, trace: Any) -> None: ...
    def myhook(self, arg1: int, arg2: int) -> int: ...
    def __call__(self, arg1: int, arg2: int) -> int: ...

当我执行MYPYPATH=./stubs mypy --verboes example.py时,我可以看到hooks.pyi正在被解析,但是没有检测到参数类型不匹配。即使我从import pluggy中删除# type: ignore注释,这种行为也是一致的。你知道吗

问题:

  1. 是否可以将.pyi钩子的类型注释定义为外部.pyi文件?你知道吗
  2. 如果是这样,那么.pyi文件将包含什么?我应该将其存储在哪里,以便在运行类型检查时mypy将其拾取?你知道吗
  3. 是否可以进行注释,以便钩子实现者和钩子调用者都获得有用的类型提示?你知道吗

Tags: self类型deftypehook钩子inthooks
1条回答
网友
1楼 · 发布于 2024-09-27 21:31:59

第一个问题是@hookspec删除了myhook方法的类型提示:

from typing import TypeVar, Callable, Any, cast

# Improvement suggested by @oremanj on python/typing gitter
F = TypeVar("F", bound=Callable[..., Any])
hookspec = cast(Callable[[F], F], pluggy.HookspecMarker("myproject"))

这种解决方法否定了对外部.pyi文件的要求。只需使用现有的钩子规范来定义类型提示。这解决了Q1和Q2:您不需要.pyi文件。只需使用typing.cast()给mypy一个提示,它无法从静态分析中学习:

# Add cast so that mypy knows that pm.hook
# is actually a MySpec instance. Without this
# hint there really is no way for mypy to know
# this.
pm.hook = cast(MySpec, pm.hook)

这可以通过添加注释来检查:

# Uncomment these when running through mypy to see
# how mypy regards the type
reveal_type(pm.hook)
reveal_type(pm.hook.myhook)
reveal_type(MySpec.myhook)

通过mypy运行以下命令:

plug.py:24: error: Unsupported operand types for + ("int" and "str")
plug.py:42: error: Revealed type is 'plug.MySpec'
plug.py:43: error: Revealed type is 'def (arg1: builtins.int, arg2: builtins.int) -> builtins.int'
plug.py:44: error: Revealed type is 'def (self: plug.MySpec, arg1: builtins.int, arg2: builtins.int) -> builtins.int'
plug.py:47: error: Argument "arg2" to "myhook" of "MySpec" has incompatible type "str"; expected "int"

现在mypy捕获钩子调用者和钩子实现(Q3)的类型问题!你知道吗

完整代码:

import pluggy  # type: ignore
from typing import TypeVar, Callable, Any, cast

# Improvement suggested by @oremanj on python/typing gitter
F = TypeVar("F", bound=Callable[..., Any])
hookspec = cast(Callable[[F], F], pluggy.HookspecMarker("myproject"))
hookimpl = pluggy.HookimplMarker("myproject")


class MySpec(object):
    """A hook specification namespace."""

    @hookspec
    def myhook(self, arg1: int, arg2: int) -> int:
        """My special little hook that you can customize."""


class Plugin_1(object):
    """A hook implementation namespace."""

    @hookimpl
    def myhook(self, arg1: int, arg2: int) -> int:
        print("inside Plugin_1.myhook()")
        return arg1 + arg2 + 'a'


# create a manager and add the spec
pm = pluggy.PluginManager("myproject")
pm.add_hookspecs(MySpec)

# register plugins
pm.register(Plugin_1())

# Add cast so that mypy knows that pm.hook
# is actually a MySpec instance. Without this
# hint there really is no way for mypy to know
# this.
pm.hook = cast(MySpec, pm.hook)

# Uncomment these when running through mypy to see
# how mypy regards the type
# reveal_type(pm.hook)
# reveal_type(pm.hook.myhook)
# reveal_type(MySpec.myhook)

# this will now be caught by mypy
results = pm.hook.myhook(arg1=1, arg2="1")
print(results)

相关问题 更多 >

    热门问题