如何根据参数的值更改函数的暗示返回类型

发布于 2025-01-09 17:11:00 字数 985 浏览 0 评论 0原文

我有一个函数,它通常返回一个它搜索的对象并执行一些其他操作。如果找不到匹配项,它会引发异常。通常,我不在乎它是否找到匹配项,但频率不够高,以至于我会考虑完全删除异常。因此,我做出的妥协是创建一个默认为 True 的参数 raise_on_failure。如果为 False,则返回 None 而不是引用。

def searchAndOperate(f, raise_on_failure: bool = True) -> Optional[Foo]:
    # Search
    result = ...
    if result is None:
        if raise_on_failure: raise ValueError("No results")
        else: return None
    # Operate
    ...
    # Then return the result
    return result

然而,这给我的类型提示带来了一些问题。我想这样做,以便如果 raise_on_failureTrue,该函数保证返回 Foo,这样它就只是 如果 raise_on_failureFalse,则可选[Foo]

我该怎么做?类似于以下代码片段的东西将是最理想的,但我也对其他想法持开放态度。

def searchAndOperate(
    f,
    raise_on_failure: bool = True
) -> Foo if raise_on_failure else Optional[Foo]:
    ...

I have a function where it usually returns an object that it searches for and performs some other actions. It raises an exception if it fails to find a match. Frequently, I don't care if it finds a match or not, but not frequently enough that I'd consider removing the exception entirely. As such, the compromise I've made is to create a parameter raise_on_failure which defaults to True. If it is False, then None is returned rather than the reference.

def searchAndOperate(f, raise_on_failure: bool = True) -> Optional[Foo]:
    # Search
    result = ...
    if result is None:
        if raise_on_failure: raise ValueError("No results")
        else: return None
    # Operate
    ...
    # Then return the result
    return result

However, this has caused some issues with my type hinting. I want to make it so that if raise_on_failure is True, the function is guaranteed to return a Foo, such that it is only Optional[Foo] if raise_on_failure is False.

How can I do this? Something akin to the following snippet would be the most desirable, but I'm open to other ideas too.

def searchAndOperate(
    f,
    raise_on_failure: bool = True
) -> Foo if raise_on_failure else Optional[Foo]:
    ...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

诗笺 2025-01-16 17:11:00

其他人已经提到,将函数 searchAndOperate 重构为两个单独的函数可能会更好。

然而,为了完整起见,实际上可以使用 @overload 装饰器:

@overload
def searchAndOperate(f, raise_on_failure: Literal[True] = True) -> Foo: ...
@overload
def searchAndOperate(f, raise_on_failure: Literal[False]) -> Optional[Foo]: ...
@overload
def searchAndOperate(f, raise_on_failure: bool = True) -> Optional[Foo]: ...
def searchAndOperate(f, raise_on_failure = True):
    # Search
    result = ...
    if result is None:
        if raise_on_failure: raise ValueError("No results")
        else: return None
    # Operate
    ...
    # Then return the result
    return result

请注意,每个组合都有一个重载,再加上一个通用后备。对于更全面的函数签名,重载列表可能会变得很长。像 mypy 这样的静态类型检查器现在推断以下返回类型:

a = searchAndOperate(f)  # Foo
b = searchAndOperate(f, raise_on_failure=True)  # Foo
c = searchAndOperate(f, raise_on_failure=False)  # Foo | None
d = searchAndOperate(f, raise_on_failure=(10 > 2))  # Foo | None
e = searchAndOperate(f, raise_on_failure=boolarg)  # Foo | None

当不使用关键字参数或文字参数 TrueFalse 时,类型检查器正确推断两个专门的返回类型之一过载。当传递布尔表达式或变量时,类型检查器只能推断泛型重载(因为它无法在不运行代码的情况下知道实际值)。

Others already mentioned that it might be better to refactor the function searchAndOperate into two separate functions.

Just for completeness however, it's actually possible to create type annotations for the original behaviour by using the @overload decorator:

@overload
def searchAndOperate(f, raise_on_failure: Literal[True] = True) -> Foo: ...
@overload
def searchAndOperate(f, raise_on_failure: Literal[False]) -> Optional[Foo]: ...
@overload
def searchAndOperate(f, raise_on_failure: bool = True) -> Optional[Foo]: ...
def searchAndOperate(f, raise_on_failure = True):
    # Search
    result = ...
    if result is None:
        if raise_on_failure: raise ValueError("No results")
        else: return None
    # Operate
    ...
    # Then return the result
    return result

Note that there's an overload for each combination plus one generic fallback. For more comprehensive function signatures that overload list can get quite long. A static type checker like mypy now infers the following return types:

a = searchAndOperate(f)  # Foo
b = searchAndOperate(f, raise_on_failure=True)  # Foo
c = searchAndOperate(f, raise_on_failure=False)  # Foo | None
d = searchAndOperate(f, raise_on_failure=(10 > 2))  # Foo | None
e = searchAndOperate(f, raise_on_failure=boolarg)  # Foo | None

When using no keyword argument or the literal arguments True or False, the type checker correctly infers one of the two specialised overloads. When passing a boolean expression or variable the type checker can infer the generic overload only (since it cannot know the actual value without running the code).

美人如玉 2025-01-16 17:11:00

从概念上讲,我认为根据参数而变化的类型提示没有意义。

类型提示 Optional[Foo] 已经封装了 searchAndOperate 能够返回的所有内容:任何调用它的人都知道它将返回 Foo。尝试根据 raise_on_failure 的值“拆分”返回类型会将函数的行为与其实现耦合起来。您应该做的是重构函数本身,使其行为明确。对当前类型提示的不满意源于 searchAndOperate 尝试同时执行太多操作这一事实。

例如,searchAndOperate 可以分为单职责函数 searchoperate。或者您可以完全放弃 raise_on_failure 参数,并让调用者尝试/排除函数调用。如果它成为您经常做的事情,您可以将其包装为一个单独的函数,例如:

def searchAndOperate(f) -> Foo:
    """Searches and returns Foo. Raises a ValueError if no result is found."""
    result = ...
    if result is None:
        raise ValueError("No result")
    return result

def trySearchAndOperate(f) -> Optional[Foo]:
    """Searches and returns Foo. Returns None if no result is found."""
    try:
        result = searchAndOperate(f)
    except ValueError:
        return None

这样,调用者就已经知道在没有找到结果时会发生什么。您可能认为这有点重复,但这是一种常见模式:'abc'.index(s)'abc'.find(s) 之间的唯一区别> 的区别是,如果在 'abc' 中找不到 s,第一个会引发异常,而第二个则返回默认值 -1在这种情况下。

Conceptually, I do not think that type hints that change depending on a parameter makes sense.

The type hint Optional[Foo] already encapsulates everything that searchAndOperate is able to return: anyone who calls it knows that it will return either Foo or None. Trying to "split" the return types based to the value of raise_on_failure couples the function's behavior to its implementation. What you should be doing is refactoring the function itself so that its behavior is non-ambiguous. The dissatisfaction with your current type hint stems from the fact that searchAndOperate tries to do too many things at once.

For example, searchAndOperate may be split into single-responsibility functions search and operate. Or you could just ditch the raise_on_failure parameter entirely, and leave to the caller to try/except the function call. If it becomes something you do a lot, you could wrap it as a separate function, something like:

def searchAndOperate(f) -> Foo:
    """Searches and returns Foo. Raises a ValueError if no result is found."""
    result = ...
    if result is None:
        raise ValueError("No result")
    return result

def trySearchAndOperate(f) -> Optional[Foo]:
    """Searches and returns Foo. Returns None if no result is found."""
    try:
        result = searchAndOperate(f)
    except ValueError:
        return None

This way, the caller already knows what to expect when no results are found. You might think that this is somewhat repetitive, but it is a common pattern: the only difference between 'abc'.index(s) and 'abc'.find(s) is that the first raises an exception if s isn't found in 'abc', and the second returns a default value of -1 in that case.

笔落惊风雨 2025-01-16 17:11:00

我不确定这是否可能。

返回类型提示应该告诉您函数返回的内容,无需查看参数的值

您本质上在此函数中实现了两种不同的行为:

  1. 返回 Foo 或崩溃
  2. 返回 FooNone

如果您想将其编码为输入提示,您必须考虑这两种情况。这意味着函数Optional[Foo]

如果您不希望这样,我建议将此函数拆分为两个不同的函数;一种是加注,另一种是加点:

def search_and_operate(f) -> Optional[Foo]:
    result = ... # some code that either results in a Foo object or None
    return result

def search_and_operate_or_raise(f) -> Foo:
    result = search_and_operate(f)
    if result is None:
        raise ValueError("No result")
    return result

I am not sure this is possible.

The return type hint is supposed to tell you, without looking at the values of the arguments, what the function returns.

You are essentially implementing two different behaviors in this function:

  1. Return a Foo or crash
  2. Return a Foo or None

If you want to encode this in a type hint, you have to consider both cases. That means that the function is Optional[Foo].

If you want it not to be, I would suggest splitting this function into two different functions; one that raises and one that does dot:

def search_and_operate(f) -> Optional[Foo]:
    result = ... # some code that either results in a Foo object or None
    return result

def search_and_operate_or_raise(f) -> Foo:
    result = search_and_operate(f)
    if result is None:
        raise ValueError("No result")
    return result
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文