Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
638 views
in Technique[技术] by (71.8m points)

python - Mypy complains about incompatible default argument value with ellipsis

I'm trying to find a way to distinguish whether an argument has been passed to the method or not. For instance, I have the following function:

@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

It works like a charm, but when I remove # type: ignore comment mypy complains with the following error:

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] = ...

I've tried a solution with dummy "sentinel" object like:

DO_NOTHING = object()

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

...but in my opinion it is a little bit too verbose.

Is there a way to do it better in a less verbose way?


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It seems that @overload from typing module does exactly what I want. The full example:

@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"),
    ]

I hope someone will find it useful.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...