【问题标题】:How do I avoid type errors when internal function returns 'Union' that could be 'None'?当内部函数返回可能为“None”的“Union”时,如何避免类型错误?
【发布时间】:2021-03-01 22:38:02
【问题描述】:

我在 Python 中使用 Unions(当然还有 Optionals)时遇到了一些奇怪的问题——也就是说,静态类型检查器似乎针对联合体的所有成员而不是联合体的成员测试属性(即它似乎过于严格?)。例如,考虑以下情况:

import pandas as pd

def test_dummy() -> pd.DataFrame:
   df = pd.DataFrame()
   df = df.fillna(df)
   return df

这会创建一个类型警告,如pd.fillna(..., inplace: Bool = False, ...) -> Optional[pd.DataFrame](如果inplace=True 则返回None)。我怀疑理论上静态类型检查器应该实现函数的返回取决于参数(编写代码时应该知道),但这有点超出了重点。

我有以下问题:

  1. 解决此问题的最佳方法是什么?我可以想到两种解决方案:

    i) 什么都不做——这会在我的代码中产生丑陋的曲线

    ii) castfillna 返回到pd.DataFrame;我的理解是这是静态类型检查器的一个信息性步骤,因此不应引起任何担忧或问题?

  2. 让我们考虑一下我正在编写一个函数f,与此类似,它的返回类型会根据函数调用输入而有所不同,这应该在运行前就可以确定。为避免日后出现此类错误;编写此函数的最佳方法是什么?像@typing.overload 这样的事情会更好吗?

【问题讨论】:

    标签: python pandas type-hinting python-typing


    【解决方案1】:

    pandas.DataFrame.fillna 方法定义为返回DataFrameNone

    如果函数有可能返回None,则应使用Optional 类型提示记录这一点。试图通过使用强制转换或注释来忽略以下警告来隐藏函数可能返回 None 的事实是错误的,例如:

    return df  # type: ignore
    

    如果函数可以返回None,请使用Optional

    import numpy as np
    import pandas as pd
    from typing import Optional
    
    
    def test_dummy() -> Optional[pd.DataFrame]:
        df = pd.DataFrame([np.nan, 2, np.nan, 0])
        df = df.fillna(value=0)
        return df
    

    函数保证不返回None,有这些选项

    如果你可以保证一个函数不会返回None,但它不能被类型检查器静态推断,那么有三种选择。

    选项 1:使用断言表明 DataFrame 不是 None

    这是mypy documentation推荐的方法。

    def test_dummy() -> pd.DataFrame:
        df = pd.DataFrame([np.nan, 2, np.nan, 0])
        df = df.fillna(value=0)
        assert df is not None 
        return df
    

    选项 2:使用演员表

    from typing import cast
    
    def test_dummy() -> pd.DataFrame:
        df = pd.DataFrame([np.nan, 2, np.nan, 0])
        df = cast(pd.DataFrame, df.fillna(value=0))
        return df
    

    选项 3:告诉 mypy 忽略警告(不推荐)

    from typing import cast
    
    def test_dummy() -> pd.DataFrame:
        df = pd.DataFrame([np.nan, 2, np.nan, 0])
        df = df.fillna(value=0)
        return df  # type: ignore
    

    【讨论】:

    • 感谢您的时间和回答,但我不确定它是否解决了我正在查看的问题。我知道为什么 MyPy 在使用 fillna 时会产生类型错误;这是因为fillna 的返回类型是Optional[pd.DataFrame]。 MyPy 检查其余代码对两种类型(pd.DataFrameNone)返回的变量的使用,因此错误实际上发生在test_dummy 函数的return,而不是fillna --它无法将test_dummy-> pd.DataFramefillna-> Optional[pd.DataFrame] 匹配。
    • 我认为这是Unions 不共享属性的一般问题,并认为最初提出的问题是针对该问题的。
    • 哦。我是个白痴,这是我假设的例子中的一个错字;我很抱歉。该示例应该是 df.fillna。是的,这会产生类型警告,这就是我要问的问题。我什至不认为有 pandas.fillna 函数!
    • @deetsb 查看显示使用Optional 的更新答案,或者使用断言告诉mypy DataFrame 绝对不会是None
    • 再次感谢您的回答!如果我是正确的,选项 1 实际上仍会传播打字错误;它只是延迟了它。例如,如果你这样做(使用你对test_dummy 的定义)'df_1 = test_dummy()',然后使用'df_1=df_1.fillna()',它会抛出一个打字错误(Item @987654354 @ of 'Optional[Any]` 没有属性 groupby)。您的assert 解决方案很酷;我没想到!这与使用cast 非常相似(尽管可能更安全);我只是想知道是否有办法告诉 MyPy 这是确定性的,而不只是推翻它。
    【解决方案2】:

    底层函数应该真正定义为重载——我可能建议给 pandas 打一个补丁

    这是looks like right now的类型:

        def fillna(
            self: FrameOrSeries,
            value=None,
            method=None,
            axis=None,
            inplace: bool_t = False,
            limit=None,
            downcast=None,
        ) -> Optional[FrameOrSeries]: ...
    

    实际上,更好的表示方法是使用@overload -- 当inplace = True 时函数返回None

        @overload
        def fillna(
            self: FrameOrSeries,
            value=None,
            method=None,
            axis=None,
            inplace: Literal[True] = False,
            limit=None,
            downcast=None,
        ) -> None: ...
    
    
        @overload
        def fillna(
            self: FrameOrSeries,
            value=None,
            method=None,
            axis=None,
            inplace: Literal[False] = False,
            limit=None,
            downcast=None,
        ) -> FrameOrSeries: ...
    
    
        def fillna(
            self: FrameOrSeries,
            value=None,
            method=None,
            axis=None,
            inplace: bool_t = False,
            limit=None,
            downcast=None,
        ) -> Optional[FrameOrSeries]:
            # actual implementation
    

    但假设您无法更改底层库,您有多种方法可以解压联合。我专门为re.match 制作了a video about this,但我会在这里重申,因为它基本上是同样的问题(Optional[T]

    选项 1:表示预期返回类型的断言

    断言告诉类型检查器一些它不知道的事情:类型比它知道的要窄。 mypy 将信任此断言,并且类型将被假定为 pd.DataFrame

    def test_dummy() -> pd.DataFrame:
       df = pd.DataFrame()
       ret = df.fillna(df)
       assert ret is not None
       return ret
    

    选项 2:演员表

    明确告诉类型检查器该类型是您所期望的,“抛弃”None-ness

    from typing import cast
    
    def test_dummy() -> pd.DataFrame:
       df = pd.DataFrame()
       ret = cast(pd.DataFrame, df.fillna(df))
       return ret
    

    类型:忽略

    (imo)hacky 解决方案是告诉类型检查器忽略不兼容性,我不建议使用这种方法,但它可以作为快速修复有用

    def test_dummy() -> pd.DataFrame:
       df = pd.DataFrame()
       ret = df.fillna(df)
       return ret  # type: ignore
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-16
      • 2020-06-26
      • 2020-06-09
      相关资源
      最近更新 更多