【问题标题】:Mypy complains about incompatible default argument value with ellipsisMypy 抱怨默认参数值与省略号不兼容
【发布时间】:2021-01-11 12:32:53
【问题描述】:

我试图找到一种方法来区分参数是否已传递给方法。 例如,我有以下功能:

@dataclass
class Record:
    id: int
    name: str
    completed_at: Optional[date] = None


records = [
    Record(id=1, name="Foo", completed_at=date(2021, 1, 10)),
    Record(id=2, name="Bar", completed_at=date(2021, 1, 11)),
]

def update_record(
    id: int,
    name: Optional[str] = None,
    completed_at: Optional[date] = ...,  # type: ignore
):
    record = next(record for record in records if record.id == id)

    if name is not None:
        record.name = name

    if completed_at is not ...:
        record.completed_at = completed_at

它就像一个魅力,但是当我删除 # type: ignore 评论时 mypy 抱怨以下错误:

error: Incompatible default for argument
"completed_at" (default has type "ellipsis", argument has type
"Optional[date]")  [assignment]
    ... int, name: Optional[str] = None, completed_at: Optional[date] = ...

我尝试过使用虚拟“哨兵”对象的解决方案,例如:

DO_NOTHING = object()

def update_record(id, completed_at: Union[DO_NOTHING, None, date] = DO_NOTHING):
    pass

...但在我看来它有点过于冗长了。

有没有办法以不那么冗长的方式做得更好?

【问题讨论】:

  • 为什么在这里使用省略号作为默认值,而不是像 name 字段中的 None?你可以这样做(如果 None 是一个有意义的参数值,确实必须这样做),但你必须将类型声明为 Union[date, ellipsis] - 你当前尝试使用的 Optional[date] 是等价的到Union[date, None],它根本不允许省略号。
  • @jasonharper 基本上我正在寻找一种从 JavaScript 中“模拟”undefined 的方法。例如,我想像这样调用update_record 方法:python update_record(1, name="Foobar") # Do nothing with `completed_at` field update_record(2, completed_at=None) # Set `completed_at` to None Btw。 completed_at: Union[ellipsis, None, date] = ... 的技巧很好用,但这次我有 Incompatible types in assignment (expression has type "Union[ellipsis, None, date]", variable has type "Optional[date]")
  • "...但在我看来它有点过于冗长了。"您认为哪些内容不那么冗长?键入/MyPy 的全部意义在于拒绝与指定类型不匹配的值。拥有无声的“无”/undef 类型意味着 the type system is not sound - 试图主动强制一个输入违背了键入/MyPy 的目的。

标签: python mypy python-typing


【解决方案1】:

看来打字模块中的@overload 完全符合我的要求。 完整示例:

@dataclass
class Record:
    id: int
    name: str
    completed_at: Optional[date] = None


records = [
    Record(id=1, name="Foo", completed_at=date(2021, 1, 10)),
    Record(id=2, name="Bar", completed_at=date(2021, 1, 11)),
]


@overload
def update_record(id: int):
    ...


@overload
def update_record(
    id: int, name: Optional[str] = None, completed_at: Optional[date] = None
):
    ...


def update_record(id: int, *args, **kwargs):
    record = next(record for record in records if record.id == id)

    for field, value in kwargs.items():
        setattr(record, field, value)


def test_update():
    repository = Repository(records)
    repository.update(1, name="Foobar")  # Do nothing with `completed_at` field
    repository.update(2, completed_at=None)  # Set `completed_at` to None
    # repository.update(2, completed_at="2021-01-12")  # Typing error

    assert records == [
        Record(id=1, name="Foobar", completed_at=date(2021, 1, 10)),
        Record(id=2, name="Bar"),
    ]

我希望有人会觉得它有用。

【讨论】:

    猜你喜欢
    • 2017-12-03
    • 2022-01-10
    • 1970-01-01
    • 2011-09-06
    • 2021-09-29
    • 2023-02-10
    • 1970-01-01
    • 1970-01-01
    • 2019-06-18
    相关资源
    最近更新 更多