“至少一个参数属于 X 类型”的类型注释

use*_*284 6 python overloading type-hinting mypy python-typing

我试图使用重载来使可变参数函数的返回类型以某种方式依赖于其参数的类型。具体来说,当且仅当其任何参数的类型为 X 时,我希望返回类型为X。

考虑以下最小示例:

from typing import overload

class Safe:
    pass

class Dangerous:
    pass

@overload
def combine(*args: Safe) -> Safe: ...

@overload
def combine(*args: Safe | Dangerous) -> Safe | Dangerous: ...

def combine(*args: Safe | Dangerous) -> Safe | Dangerous:
    if all(isinstance(arg, Safe) for arg in args):
        return Safe()
    else:
        return Dangerous()

reveal_type(combine())
reveal_type(combine(Safe()))
reveal_type(combine(Dangerous()))
reveal_type(combine(Safe(), Safe()))
reveal_type(combine(Safe(), Dangerous()))
Run Code Online (Sandbox Code Playgroud)

这输出

example.py:21: note: Revealed type is "example.Safe"
example.py:22: note: Revealed type is "example.Safe"
example.py:23: note: Revealed type is "Union[example.Safe, example.Dangerous]"
example.py:24: note: Revealed type is "example.Safe"
example.py:25: note: Revealed type is "Union[example.Safe, example.Dangerous]"
Success: no issues found in 1 source file
Run Code Online (Sandbox Code Playgroud)

我想进行设置,以便推断类型 和combine(Dangerous())combine(Safe(), Dangerous())例如,而Dangerous不是Safe | Dangerous。将第二个重载的返回类型更改为仅Dangerous产生错误:

example.py:10: error: Overloaded function signatures 1 and 2 overlap with incompatible return types  [misc]
example.py:21: note: Revealed type is "example.Safe"
example.py:22: note: Revealed type is "example.Safe"
example.py:23: note: Revealed type is "example.Dangerous"
example.py:24: note: Revealed type is "example.Safe"
example.py:25: note: Revealed type is "example.Dangerous"
Found 1 error in 1 file (checked 1 source file)
Run Code Online (Sandbox Code Playgroud)

因此,我似乎需要一种方法来注释第二个重载,以明确声明它的至少一个参数是Dangerous。有没有办法做到这一点?

我发现参数序列所需的类型是Sequence[Safe | Dangerous] - Sequence[Safe],但我认为尚不支持类型减法。

Ara*_*Fey 2

使用 PyRight,您的解决方案确实有效。mypy 也可以通过简单的方式被迫接受它# type: ignore

@overload
def combine(*args: Safe) -> Safe: ...  # type: ignore

@overload
def combine(*args: Safe | Dangerous) -> Dangerous: ...

def combine(*args: Safe | Dangerous) -> Safe | Dangerous:
    if all(isinstance(arg, Safe) for arg in args):
        return Safe()
    else:
        return Dangerous()


reveal_type(combine())  # Safe
reveal_type(combine(Safe()))  # Safe
reveal_type(combine(Dangerous()))  # Dangerous
reveal_type(combine(Safe(), Safe()))  # Safe
reveal_type(combine(Safe(), Dangerous()))  # Dangerous
Run Code Online (Sandbox Code Playgroud)

但请注意,如果输入类型不完全正确,这可能会导致静态类型和动态类型之间的差异:

arg: Safe | Dangerous = random.choice([Safe(), Dangerous()])
result = combine(arg)
reveal_type(result)  # Dangerous
print(type(result))  # Can be Safe or Dangerous
Run Code Online (Sandbox Code Playgroud)

  • `mypy` (正确)禁止这样做的原因如下:`x: tuple[Safe | 危险,...] = (安全(),); Reveal_type(combine(*x))`(猜猜是什么)。这是[游乐场链接](https://mypy-play.net/?mypy=master&python=3.10&flags=strict&gist=01868d5182b71871c71be7d80ea6fc51)。将这个“小心”添加到答案中会很棒。 (2认同)
  • 是的,但这就是为什么过载并不严格安全的原因。如果子类型给出的结果与超类型不兼容,则违反了 LSP。对于“T”的任何子类型,“combine”产生的结果应该与“T”本身的“combine”结果兼容。向上转型永远不应该产生与严格类型不兼容的结果(例如,因为它破坏了推理)。 (2认同)