我有一个大致如下的函数:

import datetime
from typing import Union

class Sentinel(object): pass
sentinel = Sentinel()

def func(
    dt: datetime.datetime,
    as_tz: Union[datetime.tzinfo, None, Sentinel] = sentinel,
) -> str:

    if as_tz is not sentinel:
        # Never reached if as_tz has wrong type (Sentinel)
        dt = dt.astimezone(as_tz)
    # ...
    # do other meaningful stuff
    # ...
    return "foo"

这里使用sentinel值是因为None已经是.astimezone()的有效参数,所以其目的是正确识别用户根本不想调用.astimezone()的情况。
然而,mypy抱怨这种模式:
错误:“datetime”的“astimezone”的参数1具有不兼容的类型
“Union[tzinfo,None,Sentinel];应为”可选[tzinfo]“
这似乎是因为datetime stub使用了:
def astimezone(self, tz: Optional[_tzinfo] = ...) -> datetime: ...

但是,有没有办法让mypy知道,sentinel值永远不会因为.astimezone()检查而传递给if?或者这只是需要一个# type: ignore而没有更干净的方法?
另一个例子:
from typing import Optional
import requests


def func(session: Optional[requests.Session] = None):
    new_session_made = session is None
    if new_session_made:
        session = requests.Session()
    try:
        session.request("GET", "https://a.b.c.d.com/foo")
        # ...
    finally:
        if new_session_made:
            session.close()

第二个和第一个一样,是“运行时安全的”(因为缺少更好的术语):调用AttributeErrorNone.request()None.close()将无法访问或计算。然而,mypy仍然抱怨:
mypytest.py:9: error: Item "None" of "Optional[Session]" has no attribute "request"
mypytest.py:13: error: Item "None" of "Optional[Session]" has no attribute "close"

我应该做些不同的事情吗?

最佳答案

您可以使用显式cast

    from typing import cast
    ...
    if as_tz is not sentinel:
        # Never reached if as_tz has wrong type (Sentinel)
        as_tz = cast(datetime.tzinfo, as_tz)
        dt = dt.astimezone(as_tz)


    new_session_made = session is None
    session = cast(requests.Session, session)

您可以交替使用assert(尽管这是一个实际的运行时检查,而cast更明确地说是一个no-op):
        assert isinstance(as_tz, datetime.tzinfo)
        dt = dt.astimezone(as_tz)


    new_session_made = session is None
    assert session is not None

关于python - 用mypy处理条件逻辑+哨兵值,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57959664/

10-12 21:01