|
2 | 2 | from __future__ import annotations |
3 | 3 |
|
4 | 4 | import contextlib |
| 5 | +import re |
5 | 6 | import shutil |
6 | 7 | import warnings |
7 | | -from collections.abc import Callable, Mapping, Sized |
| 8 | +from abc import ABC, abstractmethod |
| 9 | +from collections.abc import Callable, Iterable, Mapping, Sequence, Sized |
8 | 10 | from functools import wraps |
9 | 11 | from pathlib import Path |
10 | | -from typing import TYPE_CHECKING, Any, Literal, TypeVar, overload |
| 12 | +from typing import ( |
| 13 | + TYPE_CHECKING, |
| 14 | + Any, |
| 15 | + Literal, |
| 16 | + TypeVar, |
| 17 | + overload, |
| 18 | +) |
11 | 19 | from typing_extensions import ParamSpec |
12 | 20 |
|
13 | 21 | import numpy as np |
@@ -470,3 +478,57 @@ def update(self, length: int) -> None: |
470 | 478 | self._progress_bar.update(length) |
471 | 479 | if self._progress_bar.total <= self._progress_bar.n: |
472 | 480 | self._progress_bar.close() |
| 481 | + |
| 482 | + |
| 483 | +class ReprMixin(ABC): |
| 484 | + """A mixin class that provides a customizable string representation for OpenML objects. |
| 485 | +
|
| 486 | + This mixin standardizes the __repr__ output format across OpenML classes. |
| 487 | + Classes inheriting from this mixin should implement the |
| 488 | + _get_repr_body_fields method to specify which fields to display. |
| 489 | + """ |
| 490 | + |
| 491 | + def __repr__(self) -> str: |
| 492 | + body_fields = self._get_repr_body_fields() |
| 493 | + return self._apply_repr_template(body_fields) |
| 494 | + |
| 495 | + @abstractmethod |
| 496 | + def _get_repr_body_fields(self) -> Sequence[tuple[str, str | int | list[str] | None]]: |
| 497 | + """Collect all information to display in the __repr__ body. |
| 498 | +
|
| 499 | + Returns |
| 500 | + ------- |
| 501 | + body_fields : List[Tuple[str, Union[str, int, List[str]]]] |
| 502 | + A list of (name, value) pairs to display in the body of the __repr__. |
| 503 | + E.g.: [('metric', 'accuracy'), ('dataset', 'iris')] |
| 504 | + If value is a List of str, then each item of the list will appear in a separate row. |
| 505 | + """ |
| 506 | + # Should be implemented in the base class. |
| 507 | + |
| 508 | + def _apply_repr_template( |
| 509 | + self, |
| 510 | + body_fields: Iterable[tuple[str, str | int | list[str] | None]], |
| 511 | + ) -> str: |
| 512 | + """Generates the header and formats the body for string representation of the object. |
| 513 | +
|
| 514 | + Parameters |
| 515 | + ---------- |
| 516 | + body_fields: List[Tuple[str, str]] |
| 517 | + A list of (name, value) pairs to display in the body of the __repr__. |
| 518 | + """ |
| 519 | + # We add spaces between capitals, e.g. ClassificationTask -> Classification Task |
| 520 | + name_with_spaces = re.sub( |
| 521 | + r"(\w)([A-Z])", |
| 522 | + r"\1 \2", |
| 523 | + self.__class__.__name__[len("OpenML") :], |
| 524 | + ) |
| 525 | + header_text = f"OpenML {name_with_spaces}" |
| 526 | + header = f"{header_text}\n{'=' * len(header_text)}\n" |
| 527 | + |
| 528 | + _body_fields: list[tuple[str, str | int | list[str]]] = [ |
| 529 | + (k, "None" if v is None else v) for k, v in body_fields |
| 530 | + ] |
| 531 | + longest_field_name_length = max(len(name) for name, _ in _body_fields) |
| 532 | + field_line_format = f"{{:.<{longest_field_name_length}}}: {{}}" |
| 533 | + body = "\n".join(field_line_format.format(name, value) for name, value in _body_fields) |
| 534 | + return header + body |
0 commit comments