Skip to content

Commit 0769ff5

Browse files
authored
[ENH] Added ReprMixin to share __repr__ formatting (#1595)
* Reference Issue: Fixes #1591 * New Tests Added: No * Documentation Updated: Yes (docstring) * Change Log Entry: Adds `ReprMixin` in `utils`
1 parent d421b9e commit 0769ff5

1 file changed

Lines changed: 64 additions & 2 deletions

File tree

openml/utils.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22
from __future__ import annotations
33

44
import contextlib
5+
import re
56
import shutil
67
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
810
from functools import wraps
911
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+
)
1119
from typing_extensions import ParamSpec
1220

1321
import numpy as np
@@ -470,3 +478,57 @@ def update(self, length: int) -> None:
470478
self._progress_bar.update(length)
471479
if self._progress_bar.total <= self._progress_bar.n:
472480
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

Comments
 (0)