如何根据参数值更改函数的提示 return 类型

How can I change the hinted return type of a function depending on the value of a parameter

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

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,则函数保证 return 一个 Foo,这样如果 [=] 只有 Optional[Foo] 13=] 是 False.

我该怎么做?类似于以下代码片段的内容是最理想的,但我也愿意接受其他想法。

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

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

类型提示 Optional[Foo] 已经封装了 searchAndOperate 能够 return 的所有内容:任何调用它的人都知道它会 return FooNone。尝试根据 raise_on_failure 的值“拆分”return 类型会将函数的行为与其实现耦合起来。您应该做的是 重构函数本身 以便它的行为是 non-ambiguous。对您当前类型提示的不满源于 searchAndOperate 试图一次做太多事情。

例如,searchAndOperate 可以拆分为 single-responsibility 函数 searchoperate。或者您可以完全放弃 raise_on_failure 参数,而将函数调用留给调用者 try/except 。如果它成为你经常做的事情,你可以将它包装成一个单独的函数,比如:

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) 之间的唯一区别是如果 s'abc',第二个 return 在这种情况下是默认值 -1

我不确定这是否可行。

return 类型提示应该告诉您,无需查看参数的值,函数 returns.

您实际上是在这个函数中实现两种不同的行为:

  1. Return一个Foo或崩溃
  2. Return一个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