From a9aafb4cfcba322ed30c9fb43692488c4772b314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Wed, 3 Jun 2026 00:18:37 +0200 Subject: [PATCH 1/5] get_tag hybridmethod --- skbase/base/_base.py | 15 ++++++++++++- skbase/utils/_hybridmethod.py | 16 ++++++++++++++ skbase/utils/dependencies/_import.py | 1 - skbase/utils/tests/test_hybridmethod.py | 29 +++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 skbase/utils/_hybridmethod.py create mode 100644 skbase/utils/tests/test_hybridmethod.py diff --git a/skbase/base/_base.py b/skbase/base/_base.py index c6a0d16a..ebb89ef8 100644 --- a/skbase/base/_base.py +++ b/skbase/base/_base.py @@ -64,6 +64,7 @@ class name: BaseEstimator from skbase.base._clone_base import _check_clone, _clone from skbase.base._pretty_printing._object_html_repr import _object_html_repr from skbase.base._tagmanager import _FlagManager +from skbase.utils._hybridmethod import hybridmethod __author__: List[str] = ["fkiraly", "mloning", "RNKuhns", "tpvasconcelos"] __all__: List[str] = ["BaseEstimator", "BaseObject"] @@ -204,7 +205,7 @@ def _get_clone_plugins(cls): Returns ------- - list of str + list of BaseCloner descendants, default = None List of clone plugins for descendants. Each plugin must inherit from ``BaseCloner`` in ``skbase.base._clone_plugins``, and implement @@ -567,6 +568,7 @@ def get_class_tag(cls, tag_name, tag_value_default=None): flag_attr_name="_tags", ) + @hybridmethod def get_tags(self): """Get tags from instance, with tag level inheritance and overrides. @@ -599,8 +601,13 @@ def get_tags(self): class attribute via nested inheritance and then any overrides and new tags from ``_tags_dynamic`` object attribute. """ + if isinstance(self, type): + # if called on class, return class tags + return self.get_class_tags() + # if called on instance, return instance tags with overrides return self._get_flags(flag_attr_name="_tags") + @hybridmethod def get_tag(self, tag_name, tag_value_default=None, raise_error=True): """Get tag value from instance, with tag level inheritance and overrides. @@ -645,6 +652,12 @@ def get_tag(self, tag_name, tag_value_default=None, raise_error=True): The ``ValueError`` is then raised if ``tag_name`` is not in ``self.get_tags().keys()``. """ + if isinstance(self, type): + # if called on class, return class tag + return self.get_class_tag( + tag_name=tag_name, + tag_value_default=tag_value_default, + ) return self._get_flag( flag_name=tag_name, flag_value_default=tag_value_default, diff --git a/skbase/utils/_hybridmethod.py b/skbase/utils/_hybridmethod.py new file mode 100644 index 00000000..82ad64cf --- /dev/null +++ b/skbase/utils/_hybridmethod.py @@ -0,0 +1,16 @@ +"""Decorator for methods that can be called both on the class and on instances.""" + + +class hybridmethod: + """Decorator for methods that can be called both on the class and on instances. + + The decorated method will receive the class as the first argument when called + on the class, and the instance when called on an instance. + """ + def __init__(self, func): + self.func = func + + def __get__(self, obj, cls): + def wrapper(*args, **kwargs): + return self.func(obj if obj is not None else cls, *args, **kwargs) + return wrapper diff --git a/skbase/utils/dependencies/_import.py b/skbase/utils/dependencies/_import.py index 9ba41f2b..1275ce34 100644 --- a/skbase/utils/dependencies/_import.py +++ b/skbase/utils/dependencies/_import.py @@ -23,7 +23,6 @@ def _safe_import(import_path, pkg_name=None, condition=True, return_object="Magi Example: ``clone = _safe_import("sklearn.clone", pkg_name="scikit-learn")``. - Parameters ---------- import_path : str diff --git a/skbase/utils/tests/test_hybridmethod.py b/skbase/utils/tests/test_hybridmethod.py new file mode 100644 index 00000000..9185af47 --- /dev/null +++ b/skbase/utils/tests/test_hybridmethod.py @@ -0,0 +1,29 @@ +"""Tests for hybridmethod decorator.""" + +from inspect import isclass + +from skbase.utils._hybridmethod import hybridmethod + + +class HybridmethodTestclass: + + def __init__(self): + self.ref_to_self = self + + @hybridmethod + def method(self_or_cls): + + if isclass(self_or_cls): + assert self_or_cls is self_or_cls.ref_to_self + else: + assert self_or_cls is self_or_cls.ref_to_self + assert isinstance(self_or_cls, self_or_cls.__class__) + + +HybridmethodTestclass.ref_to_self = HybridmethodTestclass + + +def test_hybridmethod(): + """Test that hybridmethod works as expected.""" + HybridmethodTestclass.method() + HybridmethodTestclass().method() From d53ac5388c3dddd5f13a9e38b29d767eb07ae50d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:21:18 +0000 Subject: [PATCH 2/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- skbase/utils/_hybridmethod.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skbase/utils/_hybridmethod.py b/skbase/utils/_hybridmethod.py index 82ad64cf..37ebad9b 100644 --- a/skbase/utils/_hybridmethod.py +++ b/skbase/utils/_hybridmethod.py @@ -7,10 +7,12 @@ class hybridmethod: The decorated method will receive the class as the first argument when called on the class, and the instance when called on an instance. """ + def __init__(self, func): self.func = func def __get__(self, obj, cls): def wrapper(*args, **kwargs): return self.func(obj if obj is not None else cls, *args, **kwargs) + return wrapper From 7f709a4b137f960212f6f690eb46d65dbe61bb3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Wed, 3 Jun 2026 00:24:26 +0200 Subject: [PATCH 3/5] Update _hybridmethod.py --- skbase/utils/_hybridmethod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skbase/utils/_hybridmethod.py b/skbase/utils/_hybridmethod.py index 82ad64cf..59700586 100644 --- a/skbase/utils/_hybridmethod.py +++ b/skbase/utils/_hybridmethod.py @@ -11,6 +11,7 @@ def __init__(self, func): self.func = func def __get__(self, obj, cls): + """Get method that can be called on both class and instance.""" def wrapper(*args, **kwargs): return self.func(obj if obj is not None else cls, *args, **kwargs) return wrapper From 651798e5b00673b0e9b7e19758fec38d3503002b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franz=20Kir=C3=A1ly?= Date: Wed, 3 Jun 2026 00:24:59 +0200 Subject: [PATCH 4/5] Update test_hybridmethod.py --- skbase/utils/tests/test_hybridmethod.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skbase/utils/tests/test_hybridmethod.py b/skbase/utils/tests/test_hybridmethod.py index 9185af47..3932457c 100644 --- a/skbase/utils/tests/test_hybridmethod.py +++ b/skbase/utils/tests/test_hybridmethod.py @@ -11,13 +11,13 @@ def __init__(self): self.ref_to_self = self @hybridmethod - def method(self_or_cls): + def method(self): - if isclass(self_or_cls): - assert self_or_cls is self_or_cls.ref_to_self + if isclass(self): + assert self is self.ref_to_self else: - assert self_or_cls is self_or_cls.ref_to_self - assert isinstance(self_or_cls, self_or_cls.__class__) + assert self is self.ref_to_self + assert isinstance(self, self.__class__) HybridmethodTestclass.ref_to_self = HybridmethodTestclass From 954d2c2d2a22db4b5573ba94e32739ee47755e7d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 22:25:35 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- skbase/utils/_hybridmethod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skbase/utils/_hybridmethod.py b/skbase/utils/_hybridmethod.py index 3dd1069f..10f3c79f 100644 --- a/skbase/utils/_hybridmethod.py +++ b/skbase/utils/_hybridmethod.py @@ -13,6 +13,7 @@ def __init__(self, func): def __get__(self, obj, cls): """Get method that can be called on both class and instance.""" + def wrapper(*args, **kwargs): return self.func(obj if obj is not None else cls, *args, **kwargs)