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

问题描述 投票:0回答:2

设置示例:

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")

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

是否可以以某种方式让 mypy 注意到这一点,以便使用该函数检查变量

self.a
的类型,而无需在检查中明确引用它? (使用
if a.a_check
代替
if a.a is not None
)?

python python-typing mypy
2个回答
5
投票

可以通过两种方式使用 新的

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

第一个选项是将

check_a
方法更改为
staticmethod
,它接受可能是
int
None
的变量作为输入,并验证它是否是
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)

第二个选项是使用 structural subtyping 来断言

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)

您可以在 Mypy Playground here 上尝试这两个选项。


0
投票

问题是,就类型检查器而言,您的

bool
没有说明您的
.a
的类型。例如,你可以写

class A:
    ...

    def check_a(self) -> bool:
        return True

但是,正如您可能知道的那样,mypy 可以排除

None
中的
if x is not None:
。您的问题是您已将
if x is not None:
拆分为函数定义和调用站点,因此 mypy 无法使用它来推断该值不是
None

解决此问题的方法是将您想要执行的操作传递给

A
,在这种情况下,您将得到类似于其他语言中所谓的
foreach
的内容。在
Optional
的上下文中(
A
是可变
Optional
的包装器),这可以将函数应用于值 (如果它们存在)。

class A: ... def foreach(self, f: Callable[[int], None]) -> None: if self.a is not None: f(self.a) a = A() a.foreach(lambda x: print(x + 1))
注意我这里没有 

check_a

© www.soinside.com 2019 - 2024. All rights reserved.