如何使用可选导入输入提示?

How to type hint with an optional import?

当使用可选的导入时,包只在一个函数中导​​入,因为我希望它成为我的包的可选依赖项,有没有办法输入提示作为属于此可选依赖项的 类 之一的函数的 return 类型?

举一个简单的例子,把pandas作为一个可选的依赖:

def my_func() -> pd.DataFrame:                                                  
    import pandas as pd                                                         
    return pd.DataFrame()                                                       

df = my_func()

在这种情况下,由于 import 语句在 my_func 内,因此这段代码将毫不奇怪地引发:

NameError: name 'pd' is not defined

如果使用字符串文字类型提示,:

def my_func() -> 'pd.DataFrame':                                                
    import pandas as pd                                                         
    return pd.DataFrame()                                                       

df = my_func()

模块现在可以正常执行,但是mypy会报错:

error: Name 'pd' is not defined

如何使模块成功执行并保留静态类型检查功能,同时使此导入成为可选的?

尝试将您的导入粘贴到文件顶部的 if typing.TYPE_CHECKING 语句中。此变量在运行时始终为 false,但出于类型提示的目的而被视为始终为 true。

例如:

# Lets us avoid needing to use forward references everywhere
# for Python 3.7+
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    import pandas as pd

def my_func() -> pd.DataFrame:  
    import pandas as pd                                                 
    return pd.DataFrame()

您也可以 if False:,但我认为这会让别人更难分辨发生了什么。

需要注意的是,这确实意味着虽然 pandas 将是运行时的可选依赖项,但出于类型检查的目的,它仍然是强制性依赖项。

您可以探索使用的另一个选项是 mypy 的 --always-true and --always-false 标志。这会让您更细粒度地控制代码的哪些部分需要进行类型检查。例如,您可以这样做:

try:
    import pandas as pd
    PANDAS_EXISTS = True
except ImportError:
    PANDAS_EXISTS = False

if PANDAS_EXISTS:
    def my_func() -> pd.DataFrame:                                                   
        return pd.DataFrame()

...然后执行 mypy --always-true=PANDAS_EXISTS your_code.py 进行类型检查,假设 pandas 已导入,mypy --always-false=PANDAS_EXISTS your_code.py 进行类型检查,假设它丢失。

这可以帮助您发现您不小心使用了一个需要 pandas 的函数,而这个函数本不应该需要它——尽管需要注意的是 (a) 这是一个 mypy-唯一的解决方案和 (b) 具有仅有时存在于您的库中的功能可能会让最终用户感到困惑。

这是我暂时使用的解决方案,虽然我还没有尝试过 MyPy,但它似乎在 PyCharm 的 type-checker 中有效。

from typing import TypeVar, TYPE_CHECKING

PANDAS_CONFIRMED = False
if TYPE_CHECKING:
    try:
        import pandas as pd
        PANDAS_CONFIRMED = True
    except ImportError:
        pass 

if PANDAS_CONFIRMED:
    DataFrameType = pd.DataFrame
else:
    DataFrameType = TypeVar('DataFrameType')

def my_func() -> DataFrameType:  
    import pandas as pd                                                 
    return pd.DataFrame()

这具有始终定义函数的优势(因此,如果有人运行调用 my_func 的代码,他们将得到一个信息丰富的 ImportError,而不是误导性的 AttributeError)。即使未安装 pandas,这也总是提供某种 type-hint,而不会在运行时过早地尝试导入 pandas。 if-else 结构使 PyCharm 将 DataFrameType 的某些实例视为 Union[DataFrame, DataFrameType] 但它仍然提供数据帧的 well-suited linting 信息,在某些情况下,就像 my_func 的输出一样,它以某种方式推断出 DataFrameType 实例将始终是 DataFrame。