Source code for openfisca_core.indexed_enums
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function, division, absolute_import
import numpy as np
from enum import Enum as BaseEnum
ENUM_ARRAY_DTYPE = np.int16
[docs]class Enum(BaseEnum):
"""
Enum based on `enum34 <https://pypi.python.org/pypi/enum34/>`_, whose items have an index.
"""
# Tweak enums to add an index attribute to each enum item
def __init__(self, name):
# When the enum item is initialized, self._member_names_ contains the names of the previously initialized items, so its length is the index of this item.
self.index = len(self._member_names_)
# Bypass the slow Enum.__eq__
__eq__ = object.__eq__
__hash__ = object.__hash__ # In Python 3, __hash__ must be defined if __eq__ is defined to stay hashable
@classmethod
[docs] def encode(cls, array):
"""
Encode a string numpy array, or an enum item numpy array, into an :any:`EnumArray`. See :any:`EnumArray.decode` for decoding.
:param numpy.ndarray array: Numpy array of string identifiers, or of enum items, to encode.
:returns: An :any:`EnumArray` encoding the input array values.
:rtype: :any:`EnumArray`
For instance:
>>> string_identifier_array = numpy.asarray(['free_lodger', 'owner'])
>>> encoded_array = HousingOccupancyStatus.encode(string_identifier_array)
>>> encoded_array[0]
>>> 2 # Encoded value
>>> enum_item_array = numpy.asarray([HousingOccupancyStatus.free_lodger, HousingOccupancyStatus.owner])
>>> encoded_array = HousingOccupancyStatus.encode(enum_item_array)
>>> encoded_array[0]
>>> 2 # Encoded value
"""
if type(array) is EnumArray:
return array
if array.dtype.kind in {'U', 'S'}: # String array
array = np.select([array == item.name for item in cls], [item.index for item in cls]).astype(ENUM_ARRAY_DTYPE)
elif array.dtype.kind == 'O': # Enum items arrays
array = np.select([array == item for item in cls], [item.index for item in cls]).astype(ENUM_ARRAY_DTYPE)
return EnumArray(array, cls)
[docs]class EnumArray(np.ndarray):
"""
Numpy array subclass representing an array of enum items.
EnumArrays are encoded as ``int`` arrays to improve performance
"""
# Subclassing np.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, possible_values = None):
obj = np.asarray(input_array).view(cls)
obj.possible_values = possible_values
return obj
# See previous comment
def __array_finalize__(self, obj):
if obj is None:
return
self.possible_values = getattr(obj, 'possible_values', None)
def __eq__(self, other):
# When comparing to an item of self.possible_values, use the item index to speed up the comparison
if other.__class__ is self.possible_values:
return self.view(np.ndarray) == other.index # use view(np.ndarray) so that the result is a classic ndarray, not an EnumArray
return self.view(np.ndarray) == other
def __ne__(self, other):
return np.logical_not(self == other)
def _forbidden_operation(self, other):
raise TypeError("Forbidden operation. The only operations allowed on EnumArrays are '==' and '!='.")
__add__ = _forbidden_operation
__mul__ = _forbidden_operation
__lt__ = _forbidden_operation
__le__ = _forbidden_operation
__gt__ = _forbidden_operation
__ge__ = _forbidden_operation
__and__ = _forbidden_operation
__or__ = _forbidden_operation
[docs] def decode(self):
"""
Return the array of enum items corresponding to self
>>> enum_array = household('housing_occupancy_status', period)
>>> enum_array[0]
>>> 2 # Encoded value
>>> enum_array.decode()[0]
>>> <HousingOccupancyStatus.free_lodger: 'Free lodger'> # Decoded value : enum item
"""
return np.select([self == item.index for item in self.possible_values], [item for item in self.possible_values])
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, str(self.decode()))