From 91beee1221089b4917f16bd6d42364f2b5f8a7c2 Mon Sep 17 00:00:00 2001 From: Mauko Quiroga Date: Sat, 5 Oct 2024 14:32:55 +0200 Subject: [PATCH] docs(enums): add docs to EnumArray (#1271) --- openfisca_core/indexed_enums/enum_array.py | 77 ++++++++++++++++++---- setup.cfg | 14 +++- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/openfisca_core/indexed_enums/enum_array.py b/openfisca_core/indexed_enums/enum_array.py index a1479d5b8..dc63071da 100644 --- a/openfisca_core/indexed_enums/enum_array.py +++ b/openfisca_core/indexed_enums/enum_array.py @@ -9,41 +9,84 @@ class EnumArray(t.EnumArray): - """NumPy array subclass representing an array of enum items. + """Subclass of :any:`numpy.ndarray` representing an array of ``Enum``. + + ``Enum`` arrays are encoded as :any:`int` arrays to improve performance. + + Note: + Subclassing :any:`numpy.ndarray` is a little tricky™. To read more + about the :meth:`.__new__` and :meth:`.__array_finalize__` methods + below, see `Subclassing ndarray`_. + + .. _Subclassing ndarray: + https://numpy.org/doc/stable/user/basics.subclassing.html - EnumArrays are encoded as ``int`` arrays to improve performance """ - # Subclassing ndarray is a little tricky. - # To read more about the two following methods, see: - # https://docs.scipy.org/doc/numpy-1.13.0/user/basics.subclassing.html#slightly-more-realistic-example-attribute-added-to-existing-array. def __new__( cls, input_array: t.Array[t.DTypeEnum], possible_values: None | type[t.Enum] = None, ) -> Self: + """See comment above.""" obj = numpy.asarray(input_array).view(cls) obj.possible_values = possible_values return obj - # See previous comment def __array_finalize__(self, obj: numpy.int32 | None) -> None: + """See comment above.""" if obj is None: return self.possible_values = getattr(obj, "possible_values", None) def __eq__(self, other: object) -> bool: - # When comparing to an item of self.possible_values, use the item index - # to speed up the comparison. + """Compare equality with the item index. + + When comparing to an item of :attr:`.possible_values`, use the item + index to speed up the comparison. + + Whenever possible, use :any:`numpy.ndarray.view` so that the result is + a classic :any:`numpy.ndarray`, not an :obj:`.EnumArray`. + + Args: + other: Another object to compare to. + + Returns: + bool: When ??? + numpy.ndarray[numpy.bool_]: When ??? + + Note: + This breaks the `Liskov substitution principle`_. + + .. _Liskov substitution principle: + https://en.wikipedia.org/wiki/Liskov_substitution_principle + + """ + if other.__class__.__name__ is self.possible_values.__name__: - # Use view(ndarray) so that the result is a classic ndarray, not an - # EnumArray. return self.view(numpy.ndarray) == other.index return self.view(numpy.ndarray) == other def __ne__(self, other: object) -> bool: + """Inequality… + + Args: + other: Another object to compare to. + + Returns: + bool: When ??? + numpy.ndarray[numpy.bool_]: When ??? + + Note: + This breaks the `Liskov substitution principle`_. + + .. _Liskov substitution principle: + https://en.wikipedia.org/wiki/Liskov_substitution_principle + + """ + return numpy.logical_not(self == other) def _forbidden_operation(self, other: Any) -> NoReturn: @@ -65,7 +108,10 @@ def _forbidden_operation(self, other: Any) -> NoReturn: __or__ = _forbidden_operation def decode(self) -> numpy.object_: - """Return the array of enum items corresponding to self. + """Decode itself to a normal array. + + Returns: + numpy.ndarray[t.Enum]: The enum items of the ``EnumArray``. For instance: @@ -76,14 +122,19 @@ def decode(self) -> numpy.object_: Decoded value: enum item + """ + return numpy.select( [self == item.index for item in self.possible_values], list(self.possible_values), ) def decode_to_str(self) -> numpy.str_: - """Return the array of string identifiers corresponding to self. + """Decode itself to an array of strings. + + Returns: + numpy.ndarray[numpy.str_]: The string values of the ``EnumArray``. For instance: @@ -92,7 +143,9 @@ def decode_to_str(self) -> numpy.str_: >>> 2 # Encoded value >>> enum_array.decode_to_str()[0] 'free_lodger' # String identifier + """ + return numpy.select( [self == item.index for item in self.possible_values], [item.name for item in self.possible_values], diff --git a/setup.cfg b/setup.cfg index 9b8ce699b..fd18e5ab3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,19 @@ convention = google docstring_style = google extend-ignore = D -ignore = B019, E203, E501, F405, E701, E704, RST212, RST213, RST301, RST306, W503 +ignore = + B019, + E203, + E501, + F405, + E701, + E704, + RST210, + RST212, + RST213, + RST301, + RST306, + W503 in-place = true include-in-doctest = openfisca_core/commons