我正在运行myypy预提交钩子来检查任何可能的类型问题,它一直给我这个错误Argument 2 to "join" has incompatible type "Optional[str]"; expected "str"
以下代码:
else:
renamed_paths_dict: CustomConnectorRenameDict = {
"old_path": os.path.join(
self.temp_dir, change["file_path"]
),
"new_path": os.path.join(
self.temp_dir,
change["new_file_path"], -> this is the line mypy is talking about
),
}
change["new_file_path"]
可以是字符串或None
,但在这个特定的else块中,它永远不会是None
。
如何解决这个问题?
感谢请允许我以这样一种方式重写你的问题:给出一个适当的最小可重复的例子,扔掉所有不相关的东西(与实际问题无关),只保留要点。
如果字典中的值是str | None
类型,但我确定其中一个绝对是str
(而不是None
),我如何告诉静态类型检查器?下面的代码使用mypy
产生一个错误:
import os
temp_dir = "tmp"
paths: dict[str, str | None] = {}
...
paths["new_file_path"] = "foo"
...
new_path = os.path.join(temp_dir, paths["new_file_path"])
错误:
参数2 "join"有不兼容的类型"Optional[str]";期望的"str" [arg-type]
答案
您告诉类型检查器期望与键"new_file_path"
对应的值是str
:
...
paths["new_file_path"] = "foo"
...
assert paths["new_file_path"] is not None
new_path = os.path.join(temp_dir, paths["new_file_path"])
另外:
...
assert isinstance(paths["new_file_path"], str)
new_path = os.path.join(temp_dir, paths["new_file_path"])
如果你不想写额外的类型保护,你总是可以使用type: ignore
,但你应该总是尝试通过使用正确的错误代码来沉默,使那些尽可能窄:
new_path = os.path.join(temp_dir, paths["new_file_path"]) # type: ignore[arg-type]
但我不会走那条路。断言还有一个额外的好处,如果您在某个地方犯了错误,而new_file_path
的值恰好是None
,则可以立即给出一个干净且明显的错误。
paths["new_file_path"] or "some string"
短路的路线。这甚至更危险,因为它可能会在您的代码中引入无声的bug,因为您说您期望new_file_path
值是字符串。如果你犯了一个错误,代码会给你一个tmp/some string
的路径,而不会引发一个错误。
p
感谢@SUTerliakov指出关于特定字典值的断言并不完全安全。如果您想要真正精确和安全,您应该为此使用一个中间变量:
...
new_file_path = paths["new_file_path"]
assert new_file_path is not None # isinstance(new_file_path, str)
new_path = os.path.join(temp_dir, new_file_path)
为了完整起见,您还可以这样使用typing.cast
:
from typing import cast
...
new_path = os.path.join(temp_dir, cast(str, paths["new_file_path"]))
但是这基本上和一个合适的和特定的type: ignore
有相同的效果,所以我仍然推荐assert
。
你有多个选择:
- 通过添加注释
# type: ignore
来忽略来自myypy的错误:
else:
renamed_paths_dict: CustomConnectorRenameDict = {
"old_path": os.path.join(
self.temp_dir, change["file_path"]
),
"new_path": os.path.join(
self.temp_dir,
change["new_file_path"], # type: ignore
),
}
- 给变量一个默认值:
else:
renamed_paths_dict: CustomConnectorRenameDict = {
"old_path": os.path.join(
self.temp_dir, change["file_path"]
),
"new_path": os.path.join(
self.temp_dir,
change["new_file_path"] or "",
),
}
- 在
else
语句的开头添加一个断言(如果你使用它,将会带来一个强盗的警告):
else:
assert change["new_file_path"] is not None
renamed_paths_dict: CustomConnectorRenameDict = {
"old_path": os.path.join(
self.temp_dir, change["file_path"]
),
"new_path": os.path.join(
self.temp_dir,
change["new_file_path"],
),
}