如果列表 arg 仅包含 None,则 Python 键入返回 None 的函数

发布于 2025-01-12 18:33:45 字数 584 浏览 3 评论 0原文

我正在使用一个有点像这样的函数(作为示例,超级简化):

def foo(*stuff: None | int):
    stuff_not_none = [x for x in stuff if x is not None]
    if len(stuff_not_none) is 0:
        return None
    return sum(stuff_not_none)

如果我使用以下方式调用该函数:

  • foo(*[1, 2, 3]),我' d 希望将返回类型推断为 int
  • foo(*[None, None]),我希望将返回类型推断为 None
  • foo(*[1, None]),梦会被推断为 int,但如果 None | 则可以。 int

我尝试过泛型/重载,但我无法解决这个难题。我怎样才能实现这个目标?

I'm working with a function that is a bit like this (super simplified, as an example):

def foo(*stuff: None | int):
    stuff_not_none = [x for x in stuff if x is not None]
    if len(stuff_not_none) is 0:
        return None
    return sum(stuff_not_none)

If I call the function using:

  • foo(*[1, 2, 3]), I'd want the return type to be inferred to int.
  • foo(*[None, None]), I'd want the return type to be inferred to None.
  • foo(*[1, None]), the dream would be inferred to int, but ok if None | int.

I've tried with generics / overloads, but I couldn't figure out this puzzle. How can I achieve this?

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

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

发布评论

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

评论(1

偏闹i 2025-01-19 18:33:45

解决方案:

from typing import overload

@overload
def foo(*stuff: None) -> None: ...  # type: ignore[misc]

@overload
def foo(*stuff: int | None) -> int: ...

def foo(*stuff: int | None) -> int | None:
    stuff_not_none = [x for x in stuff if x is not None]
    if len(stuff_not_none) is 0:
        return None
    return sum(stuff_not_none)
    
reveal_type(foo(None, None))  # revealed type is None
reveal_type(foo(1, 2, 3))  # revealed type is int
reveal_type(foo(None, 2, None, 4))  # revealed type is int
foo('a', 'b')  # error: no matching overload

Mypy 讨厌这种事情,因为重载重叠。但您会发现,如果您在正确的位置添加 type:ignore 注释,它完全能够推断出正确的类型。 (我是一个 typeshed 维护者,我们一直在 typeshed 做这种事情。)

请注意,重载的顺序非常重要:类型检查器总是首先尝试第一个重载,然后,只有当不匹配时,他们才会尝试第二次过载。这就是当我们传入 intNone 的混合时获得 int 显示类型的方式:第一个重载不匹配,由于 int 的存在,因此类型检查器被迫尝试第二次重载。

Mypy Playground 演示: https://mypy-play.net/?mypy=latest&python=3.10&gist=ff07808e0a314208fdfa6291dcf9f717

The solution:

from typing import overload

@overload
def foo(*stuff: None) -> None: ...  # type: ignore[misc]

@overload
def foo(*stuff: int | None) -> int: ...

def foo(*stuff: int | None) -> int | None:
    stuff_not_none = [x for x in stuff if x is not None]
    if len(stuff_not_none) is 0:
        return None
    return sum(stuff_not_none)
    
reveal_type(foo(None, None))  # revealed type is None
reveal_type(foo(1, 2, 3))  # revealed type is int
reveal_type(foo(None, 2, None, 4))  # revealed type is int
foo('a', 'b')  # error: no matching overload

Mypy hates this kind of thing, because the overloads overlap. But you'll find that if you add a type: ignore comment in the right place, it's perfectly able to infer the correct types anyway. (I'm a typeshed maintainer, and we do this kind of thing at typeshed all the time.)

Note that the order of the overloads is very important: type checkers will always try the first overload first, and then, only if that doesn't match, will they try the second overload. This is how we get the int revealed type when we pass in a mixture of ints and Nones: the first overload doesn't match, because of the presence of ints, so the type checker is forced to try the second overload.

Mypy playground demo: https://mypy-play.net/?mypy=latest&python=3.10&gist=ff07808e0a314208fdfa6291dcf9f717

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文