From 5717403d8e004f55585943fb4bff729d942f6410 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Sun, 15 Oct 2023 20:35:17 +0100 Subject: [PATCH 1/6] fix: set `__class__` if `layout` OR `behavior` change --- src/awkward/highlevel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/awkward/highlevel.py b/src/awkward/highlevel.py index 5e94cca762..e67710611c 100644 --- a/src/awkward/highlevel.py +++ b/src/awkward/highlevel.py @@ -378,6 +378,7 @@ def layout(self, layout): if isinstance(layout, ak.contents.Content): self._layout = layout self._numbaview = None + self.__class__ = get_array_class(layout, self._behavior) else: raise TypeError("layout must be a subclass of ak.contents.Content") @@ -403,8 +404,8 @@ def behavior(self): @behavior.setter def behavior(self, behavior): if behavior is None or isinstance(behavior, Mapping): - self.__class__ = get_array_class(self._layout, behavior) self._behavior = behavior + self.__class__ = get_array_class(self._layout, behavior) else: raise TypeError("behavior must be None or a dict") From 12cf09309fbd5c3d538fe0236ece2622ba73d49d Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Mon, 16 Oct 2023 15:48:28 +0100 Subject: [PATCH 2/6] fix: change `__class__` under various circumstances --- src/awkward/highlevel.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/awkward/highlevel.py b/src/awkward/highlevel.py index e67710611c..f39620264e 100644 --- a/src/awkward/highlevel.py +++ b/src/awkward/highlevel.py @@ -376,9 +376,9 @@ def layout(self): @layout.setter def layout(self, layout): if isinstance(layout, ak.contents.Content): + self.__class__ = get_array_class(layout, self._behavior) self._layout = layout self._numbaview = None - self.__class__ = get_array_class(layout, self._behavior) else: raise TypeError("layout must be a subclass of ak.contents.Content") @@ -404,8 +404,9 @@ def behavior(self): @behavior.setter def behavior(self, behavior): if behavior is None or isinstance(behavior, Mapping): - self._behavior = behavior self.__class__ = get_array_class(self._layout, behavior) + self._behavior = behavior + self._numbaview = None else: raise TypeError("behavior must be None or a dict") @@ -1715,6 +1716,7 @@ def layout(self): @layout.setter def layout(self, layout): if isinstance(layout, ak.record.Record): + self.__class__ = get_record_class(layout, self._behavior) self._layout = layout self._numbaview = None else: @@ -1744,6 +1746,7 @@ def behavior(self, behavior): if behavior is None or isinstance(behavior, Mapping): self.__class__ = get_record_class(self._layout, behavior) self._behavior = behavior + self._numbaview = None else: raise TypeError("behavior must be None or a dict") From 1333da5f15f71f6a06f8582a4e9abb577cc3adfb Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Mon, 16 Oct 2023 15:53:55 +0100 Subject: [PATCH 3/6] fix: set private attributes first --- src/awkward/highlevel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/awkward/highlevel.py b/src/awkward/highlevel.py index f39620264e..b24d8d285a 100644 --- a/src/awkward/highlevel.py +++ b/src/awkward/highlevel.py @@ -376,9 +376,9 @@ def layout(self): @layout.setter def layout(self, layout): if isinstance(layout, ak.contents.Content): - self.__class__ = get_array_class(layout, self._behavior) self._layout = layout self._numbaview = None + self.__class__ = get_array_class(layout, self._behavior) else: raise TypeError("layout must be a subclass of ak.contents.Content") @@ -404,9 +404,9 @@ def behavior(self): @behavior.setter def behavior(self, behavior): if behavior is None or isinstance(behavior, Mapping): - self.__class__ = get_array_class(self._layout, behavior) self._behavior = behavior self._numbaview = None + self.__class__ = get_array_class(self._layout, behavior) else: raise TypeError("behavior must be None or a dict") @@ -1716,9 +1716,9 @@ def layout(self): @layout.setter def layout(self, layout): if isinstance(layout, ak.record.Record): - self.__class__ = get_record_class(layout, self._behavior) self._layout = layout self._numbaview = None + self.__class__ = get_record_class(layout, self._behavior) else: raise TypeError("layout must be a subclass of ak.record.Record") @@ -1744,9 +1744,9 @@ def behavior(self): @behavior.setter def behavior(self, behavior): if behavior is None or isinstance(behavior, Mapping): - self.__class__ = get_record_class(self._layout, behavior) self._behavior = behavior self._numbaview = None + self.__class__ = get_record_class(self._layout, behavior) else: raise TypeError("behavior must be None or a dict") From a17418ba69cc937dbd7849816e475025af814828 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Mon, 16 Oct 2023 15:58:02 +0100 Subject: [PATCH 4/6] refactor: just use `._update_class` --- src/awkward/highlevel.py | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/awkward/highlevel.py b/src/awkward/highlevel.py index b24d8d285a..6381fd53b0 100644 --- a/src/awkward/highlevel.py +++ b/src/awkward/highlevel.py @@ -313,13 +313,18 @@ def __init__( if backend is not None and backend != ak.operations.backend(layout): layout = ak.operations.to_backend(layout, backend, highlevel=False) - self.layout = layout - self.behavior = behavior + if behavior is not None and not isinstance(behavior, Mapping): + raise TypeError("behavior must be None or mapping") + + self._layout = layout + self._behavior = behavior docstr = layout.purelist_parameter("__doc__") if isinstance(docstr, str): self.__doc__ = docstr + self._update_class() + if check_valid: ak.operations.validity_error(self, exception=True) @@ -330,6 +335,10 @@ def __init_subclass__(cls, **kwargs): _histogram_module_ = awkward._connect.hist + def _update_class(self): + self._numbaview = None + self.__class__ = get_array_class(self._layout, self._behavior) + @property def layout(self): """ @@ -377,8 +386,7 @@ def layout(self): def layout(self, layout): if isinstance(layout, ak.contents.Content): self._layout = layout - self._numbaview = None - self.__class__ = get_array_class(layout, self._behavior) + self._update_class() else: raise TypeError("layout must be a subclass of ak.contents.Content") @@ -405,8 +413,7 @@ def behavior(self): def behavior(self, behavior): if behavior is None or isinstance(behavior, Mapping): self._behavior = behavior - self._numbaview = None - self.__class__ = get_array_class(self._layout, behavior) + self._update_class() else: raise TypeError("behavior must be None or a dict") @@ -1661,13 +1668,18 @@ def __init__( if library is not None and library != ak.operations.library(layout): layout = ak.operations.to_library(layout, library, highlevel=False) - self.layout = layout - self.behavior = behavior + if behavior is not None and not isinstance(behavior, Mapping): + raise TypeError("behavior must be None or mapping") + + self._layout = layout + self._behavior = behavior docstr = layout.purelist_parameter("__doc__") if isinstance(docstr, str): self.__doc__ = docstr + self._update_class() + if check_valid: ak.operations.validity_error(self, exception=True) @@ -1676,6 +1688,10 @@ def __init_subclass__(cls, **kwargs): ak.jax.register_behavior_class(cls) + def _update_class(self): + self._numbaview = None + self.__class__ = get_record_class(self._layout, self._behavior) + @property def layout(self): """ @@ -1717,8 +1733,7 @@ def layout(self): def layout(self, layout): if isinstance(layout, ak.record.Record): self._layout = layout - self._numbaview = None - self.__class__ = get_record_class(layout, self._behavior) + self._update_class() else: raise TypeError("layout must be a subclass of ak.record.Record") @@ -1745,8 +1760,7 @@ def behavior(self): def behavior(self, behavior): if behavior is None or isinstance(behavior, Mapping): self._behavior = behavior - self._numbaview = None - self.__class__ = get_record_class(self._layout, behavior) + self._update_class() else: raise TypeError("behavior must be None or a dict") From c527c1f54f0a7d2f9592b1ebb0f8ada2eaea12fa Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Mon, 16 Oct 2023 16:02:19 +0100 Subject: [PATCH 5/6] test: add trivial test --- tests/test_2759_update_class_consistently.py | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/test_2759_update_class_consistently.py diff --git a/tests/test_2759_update_class_consistently.py b/tests/test_2759_update_class_consistently.py new file mode 100644 index 0000000000..8e273b4776 --- /dev/null +++ b/tests/test_2759_update_class_consistently.py @@ -0,0 +1,53 @@ +# BSD 3-Clause License; see https://github.com/scikit-hep/awkward-1.0/blob/main/LICENSE + + +import awkward as ak + + +class ArrayBehavior(ak.Array): + def impl(self): + return True + + +class RecordBehavior(ak.Record): + def impl(self): + return True + + +BEHAVIOR = {("*", "impl"): ArrayBehavior, "impl": RecordBehavior} + + +def test_array_layout(): + array = ak.Array([{"x": 1}, {"y": 3}], behavior=BEHAVIOR) + assert not isinstance(array, ArrayBehavior) + + array.layout = ak.with_name([{"x": 1}, {"y": 3}], "impl", highlevel=False) + assert isinstance(array, ArrayBehavior) + assert array.impl() + + +def test_array_behavior(): + array = ak.Array([{"x": 1}, {"y": 3}], with_name="impl") + assert not isinstance(array, ArrayBehavior) + + array.behavior = BEHAVIOR + assert isinstance(array, ArrayBehavior) + assert array.impl() + + +def test_record_layout(): + record = ak.Record({"x": 1}, behavior=BEHAVIOR) + assert not isinstance(record, RecordBehavior) + + record.layout = ak.with_name({"x": 1}, "impl", highlevel=False) + assert isinstance(record, RecordBehavior) + assert record.impl() + + +def test_record_behavior(): + record = ak.Record({"x": 1}, with_name="impl") + assert not isinstance(record, RecordBehavior) + + record.behavior = BEHAVIOR + assert isinstance(record, RecordBehavior) + assert record.impl() From 870db30058ff7d7bfbfa4f4c082f16a9ce0fcee2 Mon Sep 17 00:00:00 2001 From: Angus Hollands Date: Mon, 16 Oct 2023 17:24:00 +0100 Subject: [PATCH 6/6] fix: correct pickling --- src/awkward/highlevel.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/awkward/highlevel.py b/src/awkward/highlevel.py index 6381fd53b0..e455b49289 100644 --- a/src/awkward/highlevel.py +++ b/src/awkward/highlevel.py @@ -1525,8 +1525,9 @@ def __setstate__(self, state): buffer_key="{form_key}-{attribute}", byteorder="<", ) - self.layout = layout - self.behavior = behavior + self._layout = layout + self._behavior = behavior + self._update_class() def __copy__(self): return Array(self._layout, behavior=self._behavior) @@ -1565,9 +1566,9 @@ def cpp_type(self): if self._cpp_type is None: self._generator = ak._connect.cling.togenerator( - self.layout.form, flatlist_as_rvec=False + self._layout.form, flatlist_as_rvec=False ) - self._lookup = ak._lookup.Lookup(self.layout) + self._lookup = ak._lookup.Lookup(self._layout) self._generator.generate(cppyy.cppdef) self._cpp_type = f"awkward::{self._generator.class_type()}" @@ -2195,8 +2196,9 @@ def __setstate__(self, state): byteorder="<", ) layout = ak.record.Record(layout, at) - self.layout = layout - self.behavior = behavior + self._layout = layout + self._behavior = behavior + self._update_class() def __copy__(self): return Record(self._layout, behavior=self._behavior) @@ -2347,8 +2349,11 @@ class ArrayBuilder(Sized): """ def __init__(self, *, behavior=None, initial=1024, resize=8): + if behavior is not None and not isinstance(behavior, Mapping): + raise TypeError("behavior must be None or mapping") + self._layout = _ext.ArrayBuilder(initial=initial, resize=resize) - self.behavior = behavior + self._behavior = behavior @classmethod def _wrap(cls, layout, behavior=None): @@ -2368,7 +2373,7 @@ def _wrap(cls, layout, behavior=None): assert isinstance(layout, _ext.ArrayBuilder) out = cls.__new__(cls) out._layout = layout - out.behavior = behavior + out._behavior = behavior return out @property