用于限制对象中属性类型的函数(Python、mypy)

Lev*_*145 5 python type-hinting mypy python-typing

设置示例:

from typing import Optional


class A(object):
    def __init__(self):
        self.a: Optional[int] = None
        
    def check_a(self) -> bool:
        return self.a is not None
        
a = A()
if a.check_a():
    print(a.a + 1)  # error: Unsupported operand types for + ("None" and "int")
Run Code Online (Sandbox Code Playgroud)

check_a方法检查变量的类型a,但mypy没有看到这一点并写入错误。TypeGuard不会有帮助,因为它可以创建一个函数来检查类型,而不是一个函数来检查对象变量的类型

是否有可能以某种方式让 mypy 注意到这一点,以便使用该函数检查变量的类型,self.a而无需在检查中显式引用它?(使用if a.a_check而不是if a.a is not None)?

Ale*_*ood 5

有两种方法可以使用TypeGuard功能来完成此操作,该新功能可以从 Python 3.10 中导入typing,并且可以从早期 Python 版本中的PyPItyping_extensions包中获取。请注意,它typing_extensions已经是 Mypy 的依赖项,因此如果您正在使用 Mypy,您可能已经拥有它。

第一个选项是将您的check_a方法更改为 a staticmethod,该方法接受可能是intNone作为输入的变量,并验证它是否是int. (抱歉,我更改了一些变量的名称,因为我发现一个A具有a属性的类非常令人困惑。)

from typing import TypeGuard, Optional

class Foo1:
    def __init__(self, bar: Optional[int] = None) -> None:
        self.bar = bar
    
    @staticmethod
    def check_bar(bar: Optional[int]) -> TypeGuard[int]:
        return bar is not None
        

f1 = Foo1()
if f1.check_bar(f1.bar):
    print(f1.bar + 1)
Run Code Online (Sandbox Code Playgroud)

第二种选择是使用结构子类型来断言类的实例Foo(或原始问题中的类A)在某个时间点具有某些属性。这需要改变测试方法,使其成为classmethod,并且设置起来稍微复杂一些,但是一旦设置好,就会产生更好的检查。

from typing import TypeGuard, Optional, Protocol, TypeVar

class HasIntBar(Protocol):
    bar: int


F = TypeVar('F', bound='Foo2')


class Foo2:
    def __init__(self, bar: Optional[int] = None) -> None:
        self.bar = bar
    
    @classmethod
    def check_bar(cls: type[F], instance: F) -> TypeGuard[HasIntBar]:
        return instance.bar is not None
        

f2 = Foo2()
if Foo2.check_bar(f2): # could also write this as `if f2.check_bar(f2)`
    print(f2.bar + 1)
Run Code Online (Sandbox Code Playgroud)

您可以在此处的Mypy Playground 上尝试这两个选项。