如何注释转发给另一个函数的参数类型?

问题描述 投票:0回答:2

假设我们有一个简单的函数,它调用

open()
但具有固定的参数:

def open_for_writing(*args, **kwargs):
    kwargs['mode'] = 'w'
    return open(*args, **kwargs)

如果我现在尝试调用

open_for_writing(some_fake_arg = 123)
,没有类型检查器(例如 mypy)可以判断这是一个不正确的调用:它缺少所需的
file
参数,并且添加了另一个不属于
open 一部分的参数
签名。

如何告诉类型检查器

*args
**kwargs
必须是
open
参数规范的子集?我意识到 Python 3.10 有新的
ParamSpec
类型,但它似乎不适用于这里,因为你无法获得像
ParamSpec
这样的具体函数的
open

python python-3.x mypy python-typing
2个回答
7
投票

我认为这是不可能的。但是,您可以编写一个装饰器,它将包含要检查的参数(在您的情况下为 open )的函数作为输入并返回装饰函数,即在您的情况下为 open_for_writing 。当然,这仅适用于 python 3.10 或使用 Typing_extensions,因为它使用 ParamSpec

from typing import TypeVar, ParamSpec, Callable, Optional

T = TypeVar('T')
P = ParamSpec('P')


def take_annotation_from(this: Callable[P, Optional[T]]) -> Callable[[Callable], Callable[P, Optional[T]]]:
    def decorator(real_function: Callable) -> Callable[P, Optional[T]]:
        def new_function(*args: P.args, **kwargs: P.kwargs) -> Optional[T]:
            return real_function(*args, **kwargs)

        return new_function
    return decorator

@take_annotation_from(open)
def open_for_writing(*args, **kwargs):
    kwargs['mode'] = 'w'
    return open(*args, **kwargs)


open_for_writing(some_fake_arg=123)
open_for_writing(file='')

here所示,mypy 现在抱怨得到一个未知的参数。


0
投票

@SimonHawe 的答案是正确的。然而,它可以被简化和修改一点以避免

--strict
咆哮。

  1. 使用
    Any
    作为目标函数的
    *args
    **kwargs
    和返回类型。无论如何,它们都会被我们的装饰器覆盖。
  2. 使用
    cast()
    ,目标函数可以按原样返回,因此不需要
    wraps()

(游乐场链接:mypyPyright

from collections.abc import Callable
from typing import Any, cast, ParamSpec, TypeVar

T = TypeVar('T')
P = ParamSpec('P')

def copy_signature_from(_origin: Callable[P, T]) -> Callable[[Callable[..., Any]], Callable[P, T]]:
    def decorator(target: Callable[..., Any]) -> Callable[P, T]:
        return cast(Callable[P, T], target)
    
    return decorator

但是,Pyright 有一个小警告:如果原点函数是

@overload
的,它将发出错误。我认为这是一个错误并且已报告它

from typing import overload, Literal

@overload
def g(v: Literal[1]) -> int:
    ...
    
@overload
def g(v: Literal[2]) -> str:
    ...

def g(v: Literal[1, 2]) -> int | str:
    ...

@copy_signature_from(g)
def g_wrapper(*args: Any, **kwargs: Any) -> Any:
    # mypy    => fine
    # yright => error: "g_wrapper" is marked as overload, but no implementation is provided
    ...

IDE支持

截至撰写本文时(2024 年 2 月),这在 PyCharm 中不太有效,但 VSCode/Pylance 将具有良好的参数自动完成功能:

© www.soinside.com 2019 - 2024. All rights reserved.