mypy、compose 方法和重复代码的问题
Issues with mypy, compose method and duplicated code
我已经尝试寻找这个问题的答案,因为我不可能是第一个偶然发现这个问题的人,但我的 google-fu 让我非常失望。
是否可以让 mypy
理解某些 functions/methods 只能从另一个方法中调用?
让我们以下面的代码为例(E:为清楚起见 - 这是一个简化的、可运行的现实生活问题版本,涉及一个有意为空字段的 Django 模型实例):
from typing import Optional
class Foo:
def __init__(self, value: Optional[int] = None) -> None:
self.value = value
def compose_method(foo: Foo) -> None:
if not foo.value:
raise RuntimeError("Oh no!")
__method_1(foo)
__method_2(foo)
def __method_1(foo: Foo) -> None:
foo.value + 1
def __method_2(foo: Foo) -> None:
foo.value + 2
运行 mypy
在这个文件上不出所料地导致:
test_mypy.py:18: error: Unsupported operand types for + ("None" and "int")
test_mypy.py:18: note: Left operand is of type "Optional[int]"
test_mypy.py:22: error: Unsupported operand types for + ("None" and "int")
test_mypy.py:22: note: Left operand is of type "Optional[int]"
这显然是正确的,但它没有考虑到这些方法仅打算从 compose_method
中调用这一事实 - 这已经检查了 value != None
。我不想复制代码并在每个方法中放置 if not foo.value:
条件,其中 foo.value
只是为了满足工具的要求。同时,# type: ignore
也不太适合我。
是否有一些最好的 practice/way 来处理社区认为好的和正确的?或者我只是注定要复制代码(当然,检查可以放在一个单独的方法中并在 compose_method
包含的每个方法中调用)或使用 # type: ignore
注释?
你可以这样写 __method_1
和 __method_2
只从 compose_method
中调用,但是,据 mypy 所知,有人会导入你的文件并直接调用这些方法。请记住 Python 并没有真正的私人项目的概念。
你可以做的是通过使用 typing.cast
.[= 告诉 mypy 本质上,“我保证 value
不是 None
”来消除错误。 21=]
def __method_1(foo: Foo) -> None:
foo.value = typing.cast(int, foo.value) + 1
此处轻微 性能下降。由于这不使用 +=
,Python 编译器不会发出 INPLACE_ADD
字节码。我原本想改为 typing.cast(int, foo.value) += 1
。但是,通过函数调用,它不再是 L 值,因此成为无效语法。
正因为如此,最好的办法可能就是 # type: ignore
。
如果使用assert
,优点是当python以-O
选项启动时assert
消失。
from typing import Optional
class Foo:
def __init__(self, value: Optional[int] = None) -> None:
self.value = value
def compose_method(foo: Foo) -> None:
if not foo.value:
raise RuntimeError("Oh no!")
__method_1(foo)
__method_2(foo)
def __method_1(foo: Foo) -> None:
assert isinstance(foo.value, int)
foo.value + 1
def __method_2(foo: Foo) -> None:
assert isinstance(foo.value, int)
foo.value + 2
我已经尝试寻找这个问题的答案,因为我不可能是第一个偶然发现这个问题的人,但我的 google-fu 让我非常失望。
是否可以让 mypy
理解某些 functions/methods 只能从另一个方法中调用?
让我们以下面的代码为例(E:为清楚起见 - 这是一个简化的、可运行的现实生活问题版本,涉及一个有意为空字段的 Django 模型实例):
from typing import Optional
class Foo:
def __init__(self, value: Optional[int] = None) -> None:
self.value = value
def compose_method(foo: Foo) -> None:
if not foo.value:
raise RuntimeError("Oh no!")
__method_1(foo)
__method_2(foo)
def __method_1(foo: Foo) -> None:
foo.value + 1
def __method_2(foo: Foo) -> None:
foo.value + 2
运行 mypy
在这个文件上不出所料地导致:
test_mypy.py:18: error: Unsupported operand types for + ("None" and "int")
test_mypy.py:18: note: Left operand is of type "Optional[int]"
test_mypy.py:22: error: Unsupported operand types for + ("None" and "int")
test_mypy.py:22: note: Left operand is of type "Optional[int]"
这显然是正确的,但它没有考虑到这些方法仅打算从 compose_method
中调用这一事实 - 这已经检查了 value != None
。我不想复制代码并在每个方法中放置 if not foo.value:
条件,其中 foo.value
只是为了满足工具的要求。同时,# type: ignore
也不太适合我。
是否有一些最好的 practice/way 来处理社区认为好的和正确的?或者我只是注定要复制代码(当然,检查可以放在一个单独的方法中并在 compose_method
包含的每个方法中调用)或使用 # type: ignore
注释?
你可以这样写 __method_1
和 __method_2
只从 compose_method
中调用,但是,据 mypy 所知,有人会导入你的文件并直接调用这些方法。请记住 Python 并没有真正的私人项目的概念。
你可以做的是通过使用 typing.cast
.[= 告诉 mypy 本质上,“我保证 value
不是 None
”来消除错误。 21=]
def __method_1(foo: Foo) -> None:
foo.value = typing.cast(int, foo.value) + 1
此处轻微 性能下降。由于这不使用 +=
,Python 编译器不会发出 INPLACE_ADD
字节码。我原本想改为 typing.cast(int, foo.value) += 1
。但是,通过函数调用,它不再是 L 值,因此成为无效语法。
正因为如此,最好的办法可能就是 # type: ignore
。
如果使用assert
,优点是当python以-O
选项启动时assert
消失。
from typing import Optional
class Foo:
def __init__(self, value: Optional[int] = None) -> None:
self.value = value
def compose_method(foo: Foo) -> None:
if not foo.value:
raise RuntimeError("Oh no!")
__method_1(foo)
__method_2(foo)
def __method_1(foo: Foo) -> None:
assert isinstance(foo.value, int)
foo.value + 1
def __method_2(foo: Foo) -> None:
assert isinstance(foo.value, int)
foo.value + 2