MyPy - Работа с возможными типами возврата None - PullRequest
1 голос
/ 25 февраля 2020

Я, наверное, к своему стыду, только начал включать проверку типов в моем python коде. Большая часть проверки типов прямолинейна, но я немного растерялся из-за того, что pythoni c способ иметь дело с функциями, которые могут возвращать None

Например,

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

Но это дает точно такую ​​же ошибку.

Что лучше способ справиться с этим?

Ответы [ 2 ]

1 голос
/ 26 февраля 2020

Ваш исходный код не прошел 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
0 голосов
/ 28 февраля 2020

Вы получаете ошибку, потому что в строке:

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

mypy не может сказать, что второй вызов будет таким же, как второй ... поэтому он не может сделать вывод, что Второй звонок не будет None. Кроме того, несколько глупо вызывать эту функцию дважды (представьте, если бы эта функция выполняла много работы и занимала много времени, было бы нежелательно использовать шаблон, который вы использовали). Таким образом, должны быть более эффективные способы express.

Я считаю, что самым чистым и простым решением является использование вспомогательной переменной для значения, возвращаемого 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

Следующее может выглядеть более Pythoni c (дано в комментарии hoefling):

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

Это здорово, но имейте в виду, что это не совсем семантически эквивалентно тому, что вы делаете: значение (os.cpu_count() or 1) будет 1, если os.cpu_count() тоже равно 0. Это, вероятно, не проблема с os.cpu_count(), поскольку он не должен возвращать 0, но помните об этом семантичном c разнице, когда вы используете такие «горячие клавиши» (такие ярлыки всегда выглядят круто, но вы должны понимать, их реальное значение, прежде чем использовать их).

...