【问题标题】:MyPy - Dealing with possible None return typesMyPy - 处理可能的 None 返回类型
【发布时间】:2020-06-09 04:05:47
【问题描述】:

可能让我感到羞耻的是,我刚刚开始在我的 python 代码中包含类型检查。大多数类型检查都是直截了当的,但我对处理可以返回 None 的函数的 pythonic 方式有点迷茫

例如

threads = os.cpu_count() * 1.2

这会引发 MyPy 错误

Mypy: Unsupported operand types for * ("None" and "float")

所以为了摆脱这种情况,我将代码更改为

default = (os.cpu_count() if os.cpu_count() is not None else 1.0) * 1.2

但这给出了完全相同的错误。

处理这个问题的最佳方法是什么?

【问题讨论】:

  • default = (os.cpu_count() or 1) * 1.2?
  • 反模式的人真的不喜欢这个答案,但 MyPy 似乎很高兴

标签: python python-3.x types mypy


【解决方案1】:

您的原始代码没有通过 mypy,因为您对同一个函数有两个不同的调用,每个调用都返回不同的 Optional[int]。缩小一个类型不会影响另一个类型。考虑这种极端情况:

import random

def maybe_an_int() -> Optional[int]:
    if random.random() > 0.9:
        return 1
    return None

default = (maybe_an_int() if maybe_an_int() is not None else 1.0) * 1.2

如果第一次调用不是 None,我们再调用一次。但显然,由于返回类型是随机的,我们无法预测第二次调用的返回类型是什么。

许多代码库有不同的处理方式。一种选择是这样的:

from typing import TypeVar

T = TypeVar('T')

def default_optional(value: Optional[T], default: T) -> T:
    if value is None:
        return default
    return value

【讨论】:

    【解决方案2】:

    您收到错误是因为在以下行中:

    default = (os.cpu_count() if os.cpu_count() is not None else 1.0) * 1.2
    

    mypy 无法判断第二次调用与第二次调用相同...因此无法推断第二次调用不会是None。另外,调用这个函数两次有点傻(想象一下,如果这个函数做了很多工作并且花费了很长时间,那么使用你使用的模式是不可取的)。所以必须有更好的方式来表达这一点。

    我认为最干净和最直接的解决方法是对os.cpu_count() 返回的值使用辅助变量:

    nb_cpus = os.cpu_count()  # type: Optional[int]
    default = (nb_cpus if nb_cpus is not None else 1) * 1.2  # type: float
    

    以下可能看起来更像 Pythonic(在 hoefling 的评论中给出):

    default = (os.cpu_count() or 1) * 1.2
    

    这很好,但请注意,它在语义上并不完全等同于您正在做的事情:如果 os.cpu_count() 也是 0(os.cpu_count() or 1) 的值将是 1。这可能不是os.cpu_count() 的问题,因为它不应该返回0,但是当你使用这样的“快捷方式”时请记住语义上的差异(这样的快捷方式总是看起来很酷,但你必须了解它们的真正含义在你使用它们之前)。

    【讨论】:

      猜你喜欢
      • 2019-02-15
      • 2019-08-21
      • 2020-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多