Enum.Flag
is very convenient and I used it successfully in a NumPy array, but I realized it took up too much space. I tried to create a Flag
that inherits from np.uint8
to address that concern but I came across other troubles. Let’s see it with an example.
from enum import auto, Flag
import numpy as np
Let’s start with an ordinary Flag:
class Color(Flag):
RED = auto()
GREEN = auto()
BLUE = auto()
color_array = np.array([Color.RED, Color.BLUE])
color_array
array([<Color.RED: 1>, <Color.BLUE: 4>], dtype=object)
The size of one item:
color_array.itemsize
8
The size of the whole array:
color_array.nbytes
16
Now with a flag that inherit from np.uint8
:
class ColorNp(np.uint8, Flag):
RED = auto()
GREEN = auto()
BLUE = auto()
colornp_array = np.array([ColorNp.RED, ColorNp.BLUE])
colornp_array
array([1, 4], dtype=uint8)
The size of one item:
colornp_array.itemsize
1
The size of the whole array:
colornp_array.nbytes
2
The size of the array has been greatly reduced! But now, the array is truly an array of np.uint8
and I lost the convenient representation of Flag. Let’s try to fake it:
np.array2string(colornp_array, formatter={"int_kind": lambda x: repr(ColorNp(x))}, separator=", ")
'[<ColorNp.RED: 1>, <ColorNp.BLUE: 4>]'
This simple example works but if I try to combine colors, it fails:
colornp_array2 = np.array([ColorNp.RED, ColorNp.BLUE, ColorNp.RED|ColorNp.BLUE])
colornp_array2
array([1, 4, 5], dtype=uint8)
np.array2string(colornp_array2, formatter={"int_kind": lambda x: repr(ColorNp(x))}, separator=", ")
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
ValueError: 5 is not a valid ColorNp
During handling of the above exception, another exception occurred:
TypeError Traceback (most recent call last)
Cell In[12], line 1
----> 1 np.array2string(colornp_array2, formatter={"int_kind": lambda x: repr(ColorNp(x))}, separator=", ")
File <__array_function__ internals>:200, in array2string(*args, **kwargs)
File ~/Projects/QTIS/Pandora/pandora2d-working/venv/lib/python3.8/site-packages/numpy/core/arrayprint.py:736, in array2string(a, max_line_width, precision, suppress_small, separator, prefix, style, formatter, threshold, edgeitems, sign, floatmode, suffix, legacy)
733 if a.size == 0:
734 return "[]"
--> 736 return _array2string(a, options, separator, prefix)
File ~/Projects/QTIS/Pandora/pandora2d-working/venv/lib/python3.8/site-packages/numpy/core/arrayprint.py:513, in _recursive_guard.<locals>.decorating_function.<locals>.wrapper(self, *args, **kwargs)
511 repr_running.add(key)
512 try:
--> 513 return f(self, *args, **kwargs)
514 finally:
515 repr_running.discard(key)
File ~/Projects/QTIS/Pandora/pandora2d-working/venv/lib/python3.8/site-packages/numpy/core/arrayprint.py:546, in _array2string(a, options, separator, prefix)
543 # skip over array(
544 next_line_prefix += " "*len(prefix)
--> 546 lst = _formatArray(a, format_function, options['linewidth'],
547 next_line_prefix, separator, options['edgeitems'],
548 summary_insert, options['legacy'])
549 return lst
File ~/Projects/QTIS/Pandora/pandora2d-working/venv/lib/python3.8/site-packages/numpy/core/arrayprint.py:889, in _formatArray(a, format_function, line_width, next_line_prefix, separator, edge_items, summary_insert, legacy)
885 return s
887 try:
888 # invoke the recursive part with an initial index and prefix
--> 889 return recurser(index=(),
890 hanging_indent=next_line_prefix,
891 curr_width=line_width)
892 finally:
893 # recursive closures have a cyclic reference to themselves, which
894 # requires gc to collect (gh-10620). To avoid this problem, for
895 # performance and PyPy friendliness, we break the cycle:
896 recurser = None
File ~/Projects/QTIS/Pandora/pandora2d-working/venv/lib/python3.8/site-packages/numpy/core/arrayprint.py:853, in _formatArray.<locals>.recurser(index, hanging_indent, curr_width)
850 if legacy <= 113:
851 # width of the separator is not considered on 1.13
852 elem_width = curr_width
--> 853 word = recurser(index + (-1,), next_hanging_indent, next_width)
854 s, line = _extendLine_pretty(
855 s, line, word, elem_width, hanging_indent, legacy)
857 s += line
File ~/Projects/QTIS/Pandora/pandora2d-working/venv/lib/python3.8/site-packages/numpy/core/arrayprint.py:799, in _formatArray.<locals>.recurser(index, hanging_indent, curr_width)
796 axes_left = a.ndim - axis
798 if axes_left == 0:
--> 799 return format_function(a[index])
801 # when recursing, add a space to align with the [ added, and reduce the
802 # length of the line by 1
803 next_hanging_indent = hanging_indent + ' '
Cell In[12], line 1, in <lambda>(x)
----> 1 np.array2string(colornp_array2, formatter={"int_kind": lambda x: repr(ColorNp(x))}, separator=", ")
File ~/.local/share/mise/installs/python/3.8.18/lib/python3.8/enum.py:339, in EnumMeta.__call__(cls, value, names, module, qualname, type, start)
314 """
315 Either returns an existing member, or creates a new enum class.
316
(...)
336 `type`, if set, will be mixed in as the first base class.
337 """
338 if names is None: # simple value lookup
--> 339 return cls.__new__(cls, value)
340 # otherwise, functional API: we're creating a new Enum type
341 return cls._create_(
342 value,
343 names,
(...)
347 start=start,
348 )
File ~/.local/share/mise/installs/python/3.8.18/lib/python3.8/enum.py:670, in Enum.__new__(cls, value)
665 exc = TypeError(
666 'error in %s._missing_: returned %r instead of None or a valid member'
667 % (cls.__name__, result)
668 )
669 exc.__context__ = ve_exc
--> 670 raise exc
671 finally:
672 # ensure all variables that could hold an exception are destroyed
673 exc = None
File ~/.local/share/mise/installs/python/3.8.18/lib/python3.8/enum.py:653, in Enum.__new__(cls, value)
651 try:
652 exc = None
--> 653 result = cls._missing_(value)
654 except Exception as e:
655 exc = e
File ~/.local/share/mise/installs/python/3.8.18/lib/python3.8/enum.py:798, in Flag._missing_(cls, value)
796 if value < 0:
797 value = ~value
--> 798 possible_member = cls._create_pseudo_member_(value)
799 if original_value < 0:
800 possible_member = ~possible_member
File ~/.local/share/mise/installs/python/3.8.18/lib/python3.8/enum.py:815, in Flag._create_pseudo_member_(cls, value)
813 raise ValueError("%r is not a valid %s" % (value, cls.__name__))
814 # construct a singleton enum pseudo-member
--> 815 pseudo_member = object.__new__(cls)
816 pseudo_member._name_ = None
817 pseudo_member._value_ = value
TypeError: object.__new__(ColorNp) is not safe, use numpy.uint8.__new__()
Indeed, this is the same error as if I do ColorNp(5)
.
But with a classical Flag it works:
Color(5)
<Color.BLUE|RED: 5>
Can someone explains why there is a TypeError
and how can I solve it?