Source code for openfisca_core.indexed_enums.enum
from __future__ import annotations
from collections.abc import Sequence
import numpy
from . import types as t
from ._enum_type import EnumType
from ._errors import EnumEncodingError, EnumMemberNotFoundError
from ._guards import (
_is_enum_array,
_is_enum_array_like,
_is_int_array,
_is_int_array_like,
_is_str_array,
_is_str_array_like,
)
from ._utils import _enum_to_index, _int_to_index, _str_to_index
from .enum_array import EnumArray
[docs]
class Enum(t.Enum, metaclass=EnumType):
"""Enum based on `enum34 <https://pypi.python.org/pypi/enum34/>`_.
Its items have an :class:`int` index, useful and performant when running
:mod:`~openfisca_core.simulations` on large :mod:`~openfisca_core.populations`.
Examples:
>>> from openfisca_core import indexed_enums as enum
>>> class Housing(enum.Enum):
... OWNER = "Owner"
... TENANT = "Tenant"
... FREE_LODGER = "Free lodger"
... HOMELESS = "Homeless"
>>> repr(Housing)
"<enum 'Housing'>"
>>> repr(Housing.TENANT)
'Housing.TENANT'
>>> str(Housing.TENANT)
'Housing.TENANT'
>>> dict([(Housing.TENANT, Housing.TENANT.value)])
{Housing.TENANT: 'Tenant'}
>>> list(Housing)
[Housing.OWNER, Housing.TENANT, Housing.FREE_LODGER, Housing.HOMELESS]
>>> Housing["TENANT"]
Housing.TENANT
>>> Housing("Tenant")
Housing.TENANT
>>> Housing.TENANT in Housing
True
>>> len(Housing)
4
>>> Housing.TENANT == Housing.TENANT
True
>>> Housing.TENANT != Housing.TENANT
False
>>> Housing.TENANT.index
1
>>> Housing.TENANT.name
'TENANT'
>>> Housing.TENANT.value
'Tenant'
"""
#: The :attr:`index` of the :class:`.Enum` member.
index: int
def __init__(self, *__args: object, **__kwargs: object) -> None:
"""Tweak :class:`enum.Enum` to add an :attr:`.index` to each enum item.
When the enum is initialised, ``_member_names_`` contains the names of
the already initialized items, so its length is the index of this item.
Args:
*__args: Positional arguments.
**__kwargs: Keyword arguments.
Examples:
>>> import numpy
>>> from openfisca_core import indexed_enums as enum
>>> Housing = enum.Enum("Housing", "owner tenant")
>>> Housing.tenant.index
1
>>> class Housing(enum.Enum):
... OWNER = "Owner"
... TENANT = "Tenant"
>>> Housing.TENANT.index
1
>>> array = numpy.array([[1, 2], [3, 4]])
>>> array[Housing.TENANT.index]
array([3, 4])
Note:
``_member_names_`` is undocumented in upstream :class:`enum.Enum`.
"""
self.index = len(self._member_names_)
def __repr__(self) -> str:
return f"{self.__class__.__name__}.{self.name}"
def __hash__(self) -> int:
return object.__hash__(self.__class__.__name__ + self.name)
[docs]
def __eq__(self, other: object) -> bool:
if (
isinstance(other, Enum)
and self.__class__.__name__ == other.__class__.__name__
):
return self.index == other.index
return NotImplemented
[docs]
def __ne__(self, other: object) -> bool:
if (
isinstance(other, Enum)
and self.__class__.__name__ == other.__class__.__name__
):
return self.index != other.index
return NotImplemented
[docs]
@classmethod
def encode(cls, array: t.VarArray | t.ArrayLike[object]) -> t.EnumArray:
"""Encode an encodable array into an :class:`.EnumArray`.
Args:
array: :class:`~numpy.ndarray` to encode.
Returns:
EnumArray: An :class:`.EnumArray` with the encoded input values.
Examples:
>>> import numpy
>>> from openfisca_core import indexed_enums as enum
>>> class Housing(enum.Enum):
... OWNER = "Owner"
... TENANT = "Tenant"
# EnumArray
>>> array = numpy.array([1])
>>> enum_array = enum.EnumArray(array, Housing)
>>> Housing.encode(enum_array)
EnumArray([Housing.TENANT])
# Array of Enum
>>> array = numpy.array([Housing.TENANT])
>>> enum_array = Housing.encode(array)
>>> enum_array == Housing.TENANT
array([ True])
# Array of integers
>>> array = numpy.array([1])
>>> enum_array = Housing.encode(array)
>>> enum_array == Housing.TENANT
array([ True])
# Array of strings
>>> array = numpy.array(["TENANT"])
>>> enum_array = Housing.encode(array)
>>> enum_array == Housing.TENANT
array([ True])
# Array of bytes
>>> array = numpy.array([b"TENANT"])
>>> enum_array = Housing.encode(array)
Traceback (most recent call last):
EnumEncodingError: Failed to encode "[b'TENANT']" of type 'bytes...
.. seealso::
:meth:`.EnumArray.decode` for decoding.
"""
if isinstance(array, EnumArray):
return array
if len(array) == 0:
return EnumArray(numpy.asarray(array, t.EnumDType), cls)
if isinstance(array, Sequence):
return cls._encode_array_like(array)
return cls._encode_array(array)
@classmethod
def _encode_array(cls, value: t.VarArray) -> t.EnumArray:
if _is_int_array(value):
indices = _int_to_index(cls, value)
elif _is_str_array(value): # type: ignore[unreachable]
indices = _str_to_index(cls, value)
elif _is_enum_array(value) and cls.__name__ is value[0].__class__.__name__:
indices = _enum_to_index(value)
else:
raise EnumEncodingError(cls, value)
if indices.size != len(value):
raise EnumMemberNotFoundError(cls)
return EnumArray(indices, cls)
@classmethod
def _encode_array_like(cls, value: t.ArrayLike[object]) -> t.EnumArray:
if _is_int_array_like(value):
indices = _int_to_index(cls, value)
elif _is_str_array_like(value): # type: ignore[unreachable]
indices = _str_to_index(cls, value)
elif _is_enum_array_like(value):
indices = _enum_to_index(value)
else:
raise EnumEncodingError(cls, value)
if indices.size != len(value):
raise EnumMemberNotFoundError(cls)
return EnumArray(indices, cls)
__all__ = ["Enum"]