编辑 2024 年 10 月 18 日:
下面显示了该问题的一个更为简单的重现。
mypy_arg_type.py
:
import numpy as np
from numpy.typing import NDArray
import random
def winner(_: NDArray[np.bytes_]) -> bytes | None:
return b"." if bool(random.randint(0, 1)) else None
board = np.full((2, 2), ".", "|S1")
for w in np.apply_along_axis(winner, 0, board):
print(w)
>> python mypy_arg_type.py
b'.'
None
>> mypy mypy_arg_type.py
mypy_arg_type.py:9: error: Argument 1 to "apply_along_axis" has incompatible type "Callable[[ndarray[Any, dtype[bytes_]]], bytes | None]"; expected "Callable[[ndarray[Any, dtype[Any]]], _SupportsArray[dtype[Never]] | _NestedSequence[_SupportsArray[dtype[Never]]]]" [arg-type]
mypy_arg_type.py:9: note: This is likely because "winner" has named arguments: "_". Consider marking them positional-only
Found 1 error in 1 file (checked 1 source file)
原始问题:
我正在研究一个问题,即根据棋盘上棋子的位置来确定四子连珠A
游戏的获胜者。棋盘尺寸为 6x7,每列标有从到 的字母G
。如果有获胜者,则在一行、一列、对角线或反对角线上有 4 个相同颜色的棋子。
例子:
输入:["A_Red", "B_Yellow", "A_Red", "B_Yellow", "A_Red", "B_Yellow", "G_Red", "B_Yellow"]
木板:
R Y . . . . R
R Y . . . . .
R Y . . . . .
. Y . . . . .
. . . . . . .
. . . . . . .
优胜者:Yellow
以下代码决定获胜者。
import itertools
import numpy as np
from numpy.typing import NDArray
def who_is_winner(pieces: list[str]) -> str:
def parse_board() -> NDArray[np.bytes_]:
m, n = 6, 7
indices = [0] * n
# https://numpy.org/doc/stable/user/basics.strings.html#fixed-width-data-types
# One-byte encoding, the byteorder is ‘|’ (not applicable)
board = np.full((m, n), ".", "|S1")
for p in pieces:
col = ord(p[0]) - ord("A")
board[indices[col], col] = p[2]
indices[col] += 1
return board
def winner(arr: NDArray[np.bytes_]) -> np.bytes_ | None:
i = len(arr)
xs = next(
(xs for j in range(i - 3) if (xs := set(arr[j : j + 4])) < {b"R", b"Y"}),
{None},
)
return xs.pop()
def axis(x: int) -> np.bytes_ | None:
# https://numpy.org/doc/2.0/reference/generated/numpy.apply_along_axis.html#numpy-apply-along-axis
# Axis 0 is column-wise, 1 is row-wise.
return next(
(w for w in np.apply_along_axis(winner, x, board) if w is not None), None
)
def diag(d: int) -> np.bytes_ | None:
# https://numpy.org/doc/stable/reference/generated/numpy.diagonal.html#numpy-diagonal
# Diagonal number is w.r.t. the main diagonal.
b = board if bool(d) else np.fliplr(board)
return next(
(w for d in range(-3, 4) if (w := winner(b.diagonal(d))) is not None), None
)
board = parse_board()
match next(
(
w
for f, i in itertools.product((axis, diag), (0, 1))
if (w := f(i)) is not None
),
None,
):
case b"Y":
return "Yellow"
case b"R":
return "Red"
case _:
return "Draw"
但是,这会产生如下的 mypy 违规:
error: Argument 1 to "apply_along_axis" has incompatible type "Callable[[ndarray[Any, dtype[bytes_]]], bytes_ | None]"; expected "Callable[[ndarray[Any, dtype[Any]]], _SupportsArray[dtype[bytes_]] | _NestedSequence[_SupportsArray[dtype[bytes_]]]]" [arg-type]
note: This is likely because "winner" has named arguments: "arr". Consider marking them positional-only
根据apply_along_axis的文档,它应该返回一个值,这与上面的代码一致。
如何修复此违规?使函数winner
仅定位于某一位置并没有什么区别,只是建议消失了。
我正在使用 Python 3.12.5 和 mypy 1.11.2。
通过研究 的重载签名
apply_along_axis
,我得出结论,它没有定义为返回None
,从而导致 mypy 违规。但是没有真正的理由不返回None
,我已经为此开了一个mypy 票。我们将看看它是否会被踢到 numpy。过载 1:
过载2:
我修改了该函数
winner
,使其返回一个空字节字符串(b""
)而不是None
,并将所有返回类型替换为np.bytes_
。bytes
这样就解决了问题。为自定义函数添加类型注释:当 Mypy 不知道输入/输出的类型时,它经常会感到困惑。通过明确注释自定义函数,您可以为 Mypy 提供一些帮助。
例如:
对引起警告的行使用 # type: ignore:如果您无法消除警告,并且您确信代码按预期工作,您可以通过在该行中添加 # type: ignore 来告诉 Mypy 忽略它。它不适合长期使用,但它是一个快速修复:
考虑使用 Mypy 的 NumPy 插件:Mypy 付出了很多努力来提高对 NumPy 等库的理解。您可能希望研究提供更好支持的第三方插件或类型存根,尽管它们仍在开发中。