【问题标题】:How to strongly type the "value" attribute to be str or a custom type?如何将“值”属性强类型为 str 或自定义类型?
【发布时间】:2021-06-18 09:22:29
【问题描述】:

在严格类型检查模式下使用 Pylance (ms-python.vscode-pylance) VS Code 扩展时,我在以下代码的自定义 Enum 值上收到类型错误:

def println_ctrl_sequence(message: str, ctrlSequence: Union[ANSICtrlSequence, str]):
    """
    This function is use with  terminals to print the message
    with colors specified by a, ANSI control sequence that 
    can be either a str or a console.ANSICtrlSequence object.
    """
    if type(ctrlSequence) == ANSICtrlSequence:
        ctrlSequenceStr: str = ctrlSequence.value
    else:
        ctrlSequenceStr = ctrlSequence
    
    print("%s%s%s" % (
        ctrlSequenceStr,
        message,
        ANSICtrlSequence.RESET.value
    ))

ctrlSequenceStr: str = ctrlSequence.value 行上检测到类型错误,因为ctrlSequence.value 被检测为Any | Unknown 类型。所以我的目标是强烈键入我的扩展Enumvalue 属性:

# python enum : https://docs.python.org/3/library/enum.html 
from enum import Enum

class ANSICtrlSequence(Enum):

    # basic control sequences
    RESET = "\033[m" 

    # full control sequences
    PASSED = "\033[1;4;38;5;76m" 
    FAILED = "\033[1;5;38;5;197m" 

我已经尝试过例如按照here in "String-based enum in Python" Q&A 指定的方式执行ANSICtrlSequence(str, Enum) 之类的操作,但没有成功。

我已经阅读了enum.pyi 的课程,我可以理解为什么 value 的类型是这样的:

class Enum(metaclass=EnumMeta):
    name: str
    value: Any
    ...

我找不到在documentationStackOverflow 的任何位置键入我的值属性为str 的方法。那么有可能吗?有没有办法覆盖继承属性的类型?或者我是否需要使用例如可能是 StrEnum 的 IntEnum 的等价物来扩展 Enum 类?也许我需要编写自己的强类型 Enum 类?有什么我错过的吗?

【问题讨论】:

    标签: python visual-studio-code enums python-typing pylance


    【解决方案1】:

    问题似乎并非完全来自Enum.value,而是来自str 作为ctrlSequence 的一种可能类型。 Pylance 似乎会检查 Union 中包含的所有类型是否具有 .value 属性,而对于 str,它当然没有 .value 所以 Pylance 不知道期望什么类型(那是“未知”)。

    我们可以在不使用Enum 的情况下重现类似“未知”错误:

    x = 5
    print(x.value)
    

    在您的情况下,在定义 Enum 时遵循 string-based enum 解决方案并从 str 继承仍然是必要的,因为这向类型检查器(此处为 Pylance)表明您的枚举 .valuestr-type。

    所以,你肯定需要这个来强类型你的枚举:

    class ANSICtrlSequence(str, Enum):
        RESET = "\033[m" 
        PASSED = "\033[1;4;38;5;76m" 
        FAILED = "\033[1;5;38;5;197m" 
    

    但是,它仍然显示为

    “值”的类型部分未知
    ^ “值”的类型是“Any | Unknown*”

    因为在Union[ANSICtrlSequence, str] 中,ANSICtrlSequence.value 类型为Anystr.value 类型为未知。 str 的这个问题在您将联合的顺序反转为 Union[str, ANSICtrlSequence] 时很明显,然后变为

    “值”的类型是“未知 | 任何”

    ...表示“未知”与str 相关联。基本上,我的观点是你不应该专注于输入枚举的.value 属性,因为问题在于包含str。如果您删除 Union 并仅使用 ANSICtrlSequence,则错误实际上会消失:

    class ANSICtrlSequence(str, Enum):
        RESET = "\033[m" 
        PASSED = "\033[1;4;38;5;76m" 
        FAILED = "\033[1;5;38;5;197m" 
    
    def println_ctrl_sequence(message: str, ctrlSequence: ANSICtrlSequence):
        # Pylance does not complain here
        ctrlSequenceStr: str = ctrlSequence.value
    

    ...这表明您的Enum 首先没有任何问题。

    但我明白为什么代码中有一个 Union。不幸的是,Pylance 没有。它不明白,当代码到达ctrlSequence.value 时,代码已经检查了ctrlSequenceEnum

    有趣的是,改变检查类型的方式是有效的。代替type(obj),使用isinstance(obj, classinfo)

    class ANSICtrlSequence(str, Enum):
        RESET = "\033[m" 
        PASSED = "\033[1;4;38;5;76m" 
        FAILED = "\033[1;5;38;5;197m" 
    
    def println_ctrl_sequence(message: str, ctrlSequence: Union[ANSICtrlSequence, str]):
        if isinstance(ctrlSequence, ANSICtrlSequence):
            ctrlSequenceStr: str = ctrlSequence.value
        else:
            ctrlSequenceStr = ctrlSequence
    

    ...满足 Pylance 并修复错误 :)

    我希望这不是“它适用于我的环境”的情况,但在检查对象的类型时,我几乎总是使用isinstance 而不是type,我不在我的代码中,使用枚举或使用枚举的联合不会出现任何 Pylance 错误。我不知道 Pylance 是如何工作的,但这里有一个关于该主题的相关问答:What are the differences between type() and isinstance()?

    如果你真的需要使用type(ctrlSequence),那么你可以使用typing.cast,其中:

    将值转换为类型。

    这将返回不变的值。对于类型检查器,这表明返回值具有指定的类型,但在运行时我们故意不检查任何内容(我们希望它尽可能快)。

    from typing import Union, cast
    
    class ANSICtrlSequence(str, Enum):
        RESET = "\033[m" 
        PASSED = "\033[1;4;38;5;76m" 
        FAILED = "\033[1;5;38;5;197m" 
    
    def println_ctrl_sequence(message: str, ctrlSequence: Union[ANSICtrlSequence, str]):
        if type(ctrlSequence) == ANSICtrlSequence:
            ctrlSequence = cast(ANSICtrlSequence, ctrlSequence)
            ctrlSequenceStr: str = ctrlSequence.value
        else:
            ctrlSequenceStr = ctrlSequence
    

    ...再次满足 Pylance 并修复错误 :) cast 强制类型检查器(此处为 Pylance)ctrlSequence 是您的 Enum 类型,而 .value 确实是一个字符串。

    【讨论】:

    • 好的,太棒了。带有isinstance() 的解决方案最终消除了Pylance 检测到的“假”错误。谢谢你的时间;)
    猜你喜欢
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-26
    • 1970-01-01
    • 2021-10-09
    相关资源
    最近更新 更多