Skip to content

Commit 1b9b1ac

Browse files
committed
enum: Version 1.3.0.
Signed-off-by: Ihor Nehrutsa <Ihor.Nehrutsa@gmail.com>
1 parent c3c61fc commit 1b9b1ac

3 files changed

Lines changed: 59 additions & 168 deletions

File tree

python-stdlib/enum/enum.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,10 @@ print(Color.GREEN.value, type(Color.GREEN.value))
170170

171171
| MicroPython v1.28.0 | Python 3.12.10 |
172172
| :--- | :--- |
173-
| [RED: 1, GREEN: 2, BLUE: 3] | [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>] |
174-
| GREEN: 2 <class 'EnumValue'> | Color.GREEN <enum 'Color'> |
175-
| GREEN: 2 | Color.GREEN |
176-
| GREEN: 2 | Color.GREEN |
173+
| [Color.RED: 1, Color.GREEN: 2, Color.BLUE: 3] | [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>] |
174+
| Color.GREEN: 2 <class 'EnumValue'> | Color.GREEN <enum 'Color'> |
175+
| Color.GREEN: 2 | Color.GREEN |
176+
| Color.GREEN: 2 | Color.GREEN |
177177
| GREEN <class 'str'> | GREEN <class 'str'> |
178178
| 2 <class 'int'> | 2 <class 'int'> |
179179

python-stdlib/enum/enum.py

Lines changed: 50 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,204 +1,77 @@
11
# enum.py
2-
# version="1.2.6"
2+
# version="1.3.0"
33

44

5-
class EnumValue:
6-
# An immutable object representing a specific enum member
7-
def __init__(self, v, n):
8-
object.__setattr__(self, "value", v)
9-
object.__setattr__(self, "name", n)
5+
def _make_enum(v, n, e):
6+
T = type(v)
107

11-
def __repr__(self):
12-
return f"{self.name}: {self.value}"
13-
14-
def __call__(self):
15-
return self.value
16-
17-
def __setattr__(self, k, v):
8+
def _setattr(self, k, v):
189
raise AttributeError("EnumValue is immutable")
1910

20-
# Helper function to extract the raw value
21-
def _get_value(self, o):
22-
return o.value if isinstance(o, EnumValue) else o
23-
24-
# Arithmetic and Bitwise operations (Forward)
25-
def __add__(self, o):
26-
return self.value + self._get_value(o)
27-
28-
def __sub__(self, o):
29-
return self.value - self._get_value(o)
30-
31-
def __mul__(self, o):
32-
return self.value * self._get_value(o)
33-
34-
def __truediv__(self, o):
35-
return self.value / self._get_value(o)
36-
37-
def __floordiv__(self, o):
38-
return self.value // self._get_value(o)
39-
40-
def __mod__(self, o):
41-
return self.value % self._get_value(o)
42-
43-
def __pow__(self, o):
44-
return self.value ** self._get_value(o)
45-
46-
def __and__(self, o):
47-
return self.value & self._get_value(o)
48-
49-
def __or__(self, o):
50-
return self.value | self._get_value(o)
51-
52-
def __xor__(self, o):
53-
return self.value ^ self._get_value(o)
54-
55-
def __lshift__(self, o):
56-
return self.value << self._get_value(o)
57-
58-
def __rshift__(self, o):
59-
return self.value >> self._get_value(o)
60-
61-
# Arithmetic and Bitwise operations (Reflected)
62-
def __radd__(self, o):
63-
return self._get_value(o) + self.value
64-
65-
def __rsub__(self, o):
66-
return self._get_value(o) - self.value
67-
68-
def __rmul__(self, o):
69-
return self._get_value(o) * self.value
70-
71-
def __rtruediv__(self, o):
72-
return self._get_value(o) / self.value
73-
74-
def __rfloordiv__(self, o):
75-
return self._get_value(o) // self.value
76-
77-
def __rand__(self, o):
78-
return self._get_value(o) & self.value
79-
80-
def __ror__(self, o):
81-
return self._get_value(o) | self.value
82-
83-
def __rxor__(self, o):
84-
return self._get_value(o) ^ self.value
85-
86-
def __rlshift__(self, o):
87-
return self._get_value(o) << self.value
88-
89-
def __rrshift__(self, o):
90-
return self._get_value(o) >> self.value
91-
92-
# Unary operators
93-
def __neg__(self):
94-
return -self.value
95-
96-
def __pos__(self):
97-
return +self.value
98-
99-
def __abs__(self):
100-
return abs(self.value)
101-
102-
def __invert__(self):
103-
return ~self.value
104-
105-
# Comparison
106-
def __eq__(self, o):
107-
return self.value == self._get_value(o)
108-
109-
def __lt__(self, o):
110-
return self.value < self._get_value(o)
111-
112-
def __le__(self, o):
113-
return self.value <= self._get_value(o)
114-
115-
def __gt__(self, o):
116-
return self.value > self._get_value(o)
117-
118-
def __ge__(self, o):
119-
return self.value >= self._get_value(o)
120-
121-
def __ne__(self, o):
122-
return self.value != self._get_value(o)
11+
# Create class: type(name, bases, dict), which inherits a base type (int, str, etc.)
12+
return type("EnumValue", (T,), {
13+
"name": n,
14+
"value": v,
15+
"__repr__": lambda s: f"{e}.{n}: {s.value}",
16+
"__str__": lambda s: f"{e}.{n}: {s.value}",
17+
"__call__": lambda s: v,
18+
"__setattr__": _setattr
19+
})(v)
12320

12421

12522
class Enum:
12623
def __new__(cls, name=None, names=None):
12724
# If a name and names are provided, create a NEW subclass of Enum
12825
if name and names:
129-
# Support Functional API: Enum("Name", {"KEY": VALUE})
26+
# Support Functional API: Enum("Name", {"KEY1": VALUE1, "KEY2": VALUE2, ..})
13027
# Dynamically create: class <name>
131-
new_cls = type(name, (cls,), {})
28+
new_cls = type(name, (cls,), {"_inited": True})
13229
for k, v in names.items():
133-
new_cls._up(k, v)
134-
new_cls._inited = True
30+
setattr(new_cls, k, _make_enum(v, k, name))
13531
return super().__new__(new_cls)
13632

13733
# Reverse lookup by value or name (e.g., Color(1) or Color("RED"))
138-
if name and not names and cls is not Enum:
34+
if name and cls is not Enum:
13935
return cls._lookup(name)
14036

14137
return super().__new__(cls)
14238

14339
def __init__(self, name=None, names=None):
14440
if "_inited" not in self.__class__.__dict__:
145-
self._scan()
41+
self.list()
14642

14743
@classmethod
14844
def _lookup(cls, v):
149-
if "_inited" not in cls.__dict__:
150-
cls._scan()
151-
152-
# Finds an EnumValue by its raw value or name
153-
for k in dir(cls):
154-
a = getattr(cls, k)
155-
if isinstance(a, EnumValue) and (a.value == v or a.name == v):
156-
return a
45+
for m in cls.list():
46+
if m.value == v or m.name == v:
47+
return m
15748
raise AttributeError(f"{v} is not in {cls.__name__}")
15849

15950
@classmethod
16051
def __iter__(cls):
161-
if "_inited" not in cls.__dict__:
162-
cls._scan()
163-
164-
for k in dir(cls):
165-
attr = getattr(cls, k)
166-
if isinstance(attr, EnumValue):
167-
yield attr
52+
return iter(cls.list())
16853

16954
@classmethod
17055
def list(cls):
17156
if "_inited" not in cls.__dict__:
172-
cls._scan()
173-
174-
# Returns a list of all members
175-
return [getattr(cls, k) for k in dir(cls) if isinstance(getattr(cls, k), EnumValue)]
57+
# Copy dict.items() to avoid RuntimeError when changing the dictionary
58+
for k, v in list(cls.__dict__.items()):
59+
if not k.startswith("_") and not callable(v):
60+
setattr(cls, k, _make_enum(v, k, cls.__name__))
61+
cls._inited = True
62+
return [m for k in dir(cls) if not k.startswith("_") and hasattr(m := getattr(cls, k), "name")]
17663

17764
@classmethod
178-
def _up(cls, k, v):
179-
setattr(cls, k, EnumValue(v, k))
180-
181-
@classmethod
182-
def _scan(cls):
183-
# Convert class-level attributes (constants) to EnumValue objects
184-
for k, v in list(cls.__dict__.items()):
185-
if not k.startswith("_") and not callable(v) and not isinstance(v, EnumValue):
186-
cls._up(k, v)
187-
cls._inited = True
188-
189-
def is_value(self, v):
190-
return any(m.value == v for m in self)
65+
def is_value(cls, v):
66+
return any(m.value == v for m in cls.list())
19167

19268
def __repr__(self):
19369
# Supports the condition: obj == eval(repr(obj))
194-
d = {m.name: m.value for m in self}
70+
d = {m.name: m.value for m in self.__class__.list()}
19571
# Return a string like: Enum(name='Name', names={'KEY1': VALUE1, 'KEY2': VALUE2, ..})
19672
return f"Enum(name='{self.__class__.__name__}', names={d})"
19773

19874
def __call__(self, v):
199-
if "_inited" in self.__class__.__dict__:
200-
self._scan()
201-
20275
return self._lookup(v)
20376

20477
def __setattr__(self, k, v):
@@ -210,7 +83,7 @@ def __delattr__(self, k):
21083
raise AttributeError("Enum is immutable")
21184

21285
def __len__(self):
213-
return sum(1 for _ in self)
86+
return len(self.__class__.list())
21487

21588
def __eq__(self, o):
21689
if not isinstance(o, Enum):
@@ -226,19 +99,26 @@ class Color(Enum):
22699
GREEN = 2
227100
BLUE = 3
228101

102+
# Basic access
103+
print(f"RED: repr={repr(Color.RED)}, type={type(Color.RED)}, {Color(1).name} ")
104+
print(f"RED: name={Color.RED.name}, value={Color.RED.value}, str={str(Color.RED)}, call={Color.RED()} ")
105+
assert Color(1).value == 1
106+
assert Color.BLUE.value >= Color.GREEN.value
107+
229108
print("Color.list():", Color.list())
230109

231110
# Iteration
232111
print("Members list:", [member for member in Color()])
233112
print("Names list:", [member.name for member in Color()])
234113
print("Values list:", [member.value for member in Color()])
114+
print()
235115

236116
# Create instance
237117
c = Color()
238118
print(f"Enum c: {c}")
239119

240120
# Basic access
241-
print(f"RED: Name={c.RED.name}, Value={c.RED.value}, EnumValue={c.RED}, Call={c.RED()} ")
121+
print(f"RED: name={c.RED.name}, value={c.RED.value}, str={str(c.RED)}, call={c.RED()} ")
242122

243123
# Assertions
244124
assert c.RED.name == "RED"
@@ -247,13 +127,15 @@ class Color(Enum):
247127
assert c.RED() == 1
248128

249129
# Reverse Lookup via instance call
250-
print(f"c(1) lookup object: {c(1)}, name={c(1).name}, value={c(1).value}") # RED
130+
o = c(1)
131+
print(f"c(1) lookup object: {o}, name={o.name}, value={o.value}")
251132
assert c(1).name == "RED"
252133
assert c(1).value == 1
253134
assert c(1) == 1
254135

255136
try:
256137
c(999)
138+
0 / 0
257139
except AttributeError as e:
258140
print(f"\nAttributeError: {e}: {c}\n")
259141

@@ -282,6 +164,7 @@ class Status(Enum):
282164
# Immutability Check
283165
try:
284166
Status.RUNNING.value = 999
167+
0 / 0
285168
except AttributeError as e:
286169
print(f"\nImmutability check: Passed (Cannot modify EnumValue): {e}\n")
287170

@@ -294,6 +177,7 @@ class Status(Enum):
294177
# Test: Error handling for invalid lookup
295178
try:
296179
Status(999)
180+
0 / 0
297181
except AttributeError as e:
298182
print(f"\nAttributeError: Invalid lookup check: Caught expected error -> {e}\n")
299183

@@ -302,8 +186,8 @@ class Status(Enum):
302186

303187
# Verify that eval(repr(obj)) restores the object
304188
c_repr = repr(c)
305-
c_restored = eval(c_repr)
306189
print(f"Original: {c_repr}")
190+
c_restored = eval(c_repr)
307191
print(f"Restored: {repr(c_restored)}")
308192
print(f"Objects are equal: {c == c_restored}")
309193
assert c == c_restored
@@ -314,6 +198,8 @@ class Status(Enum):
314198
print(type(state))
315199
assert state.ON == 1
316200
assert state.ON.name == "ON"
201+
assert state.ON > 0
202+
assert state.ON.value | state.OFF.value == 3
317203

318204
# --- 1. Unique Data Types & Class Methods ---
319205
# Enums can hold more than just integers; here we use strings and add a method.
@@ -361,11 +247,13 @@ class Empty(Enum):
361247
# Ensuring the Enum structure cannot be tampered with after creation.
362248
try:
363249
api_call.NEW_METHOD = "PATCH"
250+
0 / 0
364251
except AttributeError as e:
365252
print(f"Caught expected mutation error: {e}")
366253

367254
try:
368255
del api_call.GET
256+
0 / 0
369257
except AttributeError as e:
370258
print(f"Caught expected deletion error: {e}")
371259

python-stdlib/enum/test_enum.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# test_enum.py
2-
# version="1.2.5"
2+
# version="1.3.0"
33

44
import unittest
5-
from enum import Enum, EnumValue
5+
from enum import Enum
66

77

88
class TestEnum(unittest.TestCase):
@@ -27,6 +27,9 @@ def test_class_attributes(self):
2727
"""Test basic access to Enum members, names, and values."""
2828
self.assertEqual(self.color.RED.value, 1)
2929
self.assertEqual(self.color.RED.name, 'RED')
30+
self.assertEqual(str(type(self.color.RED)), "<class 'EnumValue'>")
31+
self.assertEqual(type(self.color.RED).__name__, 'EnumValue')
32+
EnumValue = type(self.color.RED)
3033
self.assertIsInstance(self.color.RED, EnumValue)
3134
self.assertEqual(self.status.IDLE.value, 0)
3235

0 commit comments

Comments
 (0)