限制对象属性类型的函数(Python, mypy)
Function for restricting the type of attributes in an object (Python, mypy)
示例设置:
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
)?
问题是,就类型检查器而言,您的 bool
没有说明您的 .a
的类型。例如,您可以编写
class A:
...
def check_a(self) -> bool:
return True
但是,您可能知道,mypy 可以在 if x is not None:
内排除 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
。
在较早的 Python 版本中,使用 , which can be imported from typing
in Python 3.10, and is available from the PyPI typing_extensions
package 可以通过两种方式完成此操作。请注意,typing_extensions
已经是 Mypy 的依赖项,因此如果您正在使用 Mypy,您可能已经拥有它。
第一个选项是将您的 check_a
方法更改为 staticmethod
,它接受一个可能是 int
或 None
的变量作为输入,并验证它是否是 int
。 (抱歉,我更改了您的一些变量的名称,因为我发现 class 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)
第二个选项是使用 来断言 class Foo
的一个实例(或 class 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 操场上尝试这两个选项 here。
示例设置:
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
)?
问题是,就类型检查器而言,您的 bool
没有说明您的 .a
的类型。例如,您可以编写
class A:
...
def check_a(self) -> bool:
return True
但是,您可能知道,mypy 可以在 if x is not None:
内排除 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
。
在较早的 Python 版本中,使用 typing
in Python 3.10, and is available from the PyPI typing_extensions
package 可以通过两种方式完成此操作。请注意,typing_extensions
已经是 Mypy 的依赖项,因此如果您正在使用 Mypy,您可能已经拥有它。
第一个选项是将您的 check_a
方法更改为 staticmethod
,它接受一个可能是 int
或 None
的变量作为输入,并验证它是否是 int
。 (抱歉,我更改了您的一些变量的名称,因为我发现 class 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)
第二个选项是使用 Foo
的一个实例(或 class 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 操场上尝试这两个选项 here。