Skip to content

Commit 0b2955e

Browse files
committed
enum.py: Add Reverse Lookup like Status(1).
class Status(Enum): IDLE = 0 RUNNING = 1 ERROR = 2 # This simulates receiving a byte from the hardware received_byte = 1 status = Status(received_byte) print(f"Lookup check: Received {received_byte} -> {status}") assert status == Status.RUNNING assert status.name == "RUNNING" Signed-off-by: Ihor Nehrutsa <Ihor.Nehrutsa@gmail.com>
1 parent 735e6b0 commit 0b2955e

1 file changed

Lines changed: 226 additions & 157 deletions

File tree

python-stdlib/enum/enum.py

Lines changed: 226 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,226 @@
1-
# enum.py
2-
# version="1.2.1"
3-
4-
5-
class EnumValue:
6-
def __init__(self, value, name):
7-
object.__setattr__(self, 'value', value)
8-
object.__setattr__(self, 'name', name)
9-
10-
def __repr__(self):
11-
return str(self.value)
12-
13-
def __call__(self):
14-
return self.value
15-
16-
def __eq__(self, other):
17-
return self.value == (other.value if isinstance(other, EnumValue) else other)
18-
19-
def __setattr__(self, key, value):
20-
raise AttributeError("EnumValue is immutable")
21-
22-
23-
class Enum:
24-
def __new__(cls, *args, **kwargs):
25-
if len(args) > 0:
26-
raise TypeError(f"{cls.__name__}() kwargs allowed only, not {args} args")
27-
return super(Enum, cls).__new__(cls)
28-
29-
def __init__(self, **kwargs):
30-
# 1. Collect class-level attributes (constants)
31-
self._scan_class_attrs()
32-
# 2. Add arguments from the constructor
33-
if kwargs:
34-
self.append(**kwargs)
35-
36-
def _update(self, key, value):
37-
setattr(self.__class__, key, EnumValue(value, key))
38-
39-
def _scan_class_attrs(self):
40-
# Converts static class attributes into EnumValue objects
41-
# List of methods and internal names that should not be converted
42-
ignored = ('is_value', 'append')
43-
44-
for key in dir(self.__class__):
45-
# Skip internal names and methods
46-
if key.startswith('_') or key in ignored:
47-
continue
48-
49-
value = getattr(self.__class__, key)
50-
# Convert only constants, not methods
51-
if not callable(value) and not isinstance(value, EnumValue):
52-
self._update(key, value)
53-
54-
def is_value(self, value):
55-
# Оптимізація: ітеруємося по self (де вже є __iter__), а не через dir()
56-
return any(member.value == value for member in self)
57-
58-
def append(self, **kwargs):
59-
forbidden = ('is_value', 'append', '_update', '_scan_class_attrs')
60-
for key, value in kwargs.items():
61-
if key in forbidden or key.startswith('_'):
62-
raise NameError(f"Cannot add enum member with reserved name: {key}")
63-
if hasattr(self.__class__, key):
64-
existing = getattr(self.__class__, key)
65-
if isinstance(existing, EnumValue):
66-
raise AttributeError(f"Enum member '{key}' already exists and is immutable")
67-
self._update(key, value)
68-
return self
69-
70-
def __repr__(self):
71-
# Implementation of the principle: obj == eval(repr(obj))
72-
# Use !r to correctly represent values ​​(e.g., quotes for strings)
73-
members = [f"{k}={getattr(self.__class__, k).value!r}" for k in dir(self.__class__) if not k.startswith('_') and isinstance(getattr(self.__class__, k), EnumValue)]
74-
# Return a string like: Color(RED=1, GREEN=2, BLUE=3)
75-
return f"{type(self).__name__}({', '.join(members)})"
76-
77-
def __call__(self, value):
78-
for member in self:
79-
if member.value == value:
80-
return member
81-
raise ValueError(f"no such value: {value}")
82-
83-
def __setattr__(self, key, value):
84-
if hasattr(self, key) and isinstance(getattr(self, key), EnumValue):
85-
raise AttributeError(f"Enum member '{key}' is immutable")
86-
super().__setattr__(key, value)
87-
88-
def __delattr__(self, key):
89-
if hasattr(self, key) and isinstance(getattr(self, key), EnumValue):
90-
raise AttributeError("Enum members cannot be deleted")
91-
super().__delattr__(key)
92-
93-
def __len__(self):
94-
return sum(1 for _ in self)
95-
96-
def __iter__(self):
97-
for key in dir(self.__class__):
98-
attr = getattr(self.__class__, key)
99-
if isinstance(attr, EnumValue):
100-
yield attr
101-
102-
103-
def enum(**kwargs): # `**kwargs` kept backwards compatible as in the Internet examples
104-
return Enum(**kwargs)
105-
106-
107-
if __name__ == '__main__':
108-
# --- Usage Example ---
109-
110-
# 1. Creation via class
111-
class Color(Enum):
112-
RED = 1
113-
GREEN = 2
114-
115-
# Create instance
116-
c = Color()
117-
print(f"Enum repr: {c}")
118-
119-
# 2. Strict __init__ control check
120-
try:
121-
c_bad = Color('BLACK')
122-
except TypeError as e:
123-
print(f"\nTypeError: Strict Init Check: {e}\n")
124-
125-
# 3. Dynamic addition
126-
c.append(BLUE=3)
127-
print(f"c after append: {c}")
128-
129-
print('dir(c):', dir(c))
130-
131-
# 4. Immutability and name protection check
132-
try:
133-
c.append(append=True)
134-
except NameError as e:
135-
print(f"\nNameError: Reserved name protection: {e}\n")
136-
137-
# 5. Basic access
138-
print(f"RED: Name={c.RED.name}, Value={c.RED.value}, EnumValue={c.RED}, Call={c.RED()} ")
139-
140-
# 6. Assertions
141-
assert c.RED == 1
142-
assert c.RED.value == 1
143-
assert c.RED.name == 'RED'
144-
145-
# 7. Reverse lookup
146-
print(f"c(1) lookup object: {c(1)}, Name={c(1).name}") # RED
147-
assert c(1).name == 'RED'
148-
assert c(1) == 1
149-
150-
# 8. Iteration
151-
print("Values list:", [member.value for member in c])
152-
print("Names list:", [member.name for member in c])
153-
154-
try:
155-
c(7)
156-
except ValueError as e:
157-
print(f"\nValueError: {c} {e}\n")
1+
# enum.py
2+
# version="1.2.2"
3+
4+
5+
class EnumValue:
6+
# An immutable object representing a specific enum member
7+
def __init__(self, value, name):
8+
object.__setattr__(self, 'value', value)
9+
object.__setattr__(self, 'name', name)
10+
11+
def __repr__(self):
12+
return f"<{self.name}: {self.value}>"
13+
14+
def __str__(self):
15+
return str(self.value)
16+
17+
def __call__(self):
18+
return self.value
19+
20+
def __eq__(self, other):
21+
if isinstance(other, EnumValue):
22+
return self.value == other.value
23+
return self.value == other
24+
25+
def __int__(self):
26+
return self.value
27+
28+
def __setattr__(self, key, value):
29+
raise AttributeError("EnumValue is immutable")
30+
31+
32+
class Enum:
33+
def __new__(cls, *args, **kwargs):
34+
# Scenario 1: Reverse lookup by value (e.g., Status(1))
35+
if len(args) == 1:
36+
if cls is not Enum:
37+
return cls._lookup(args[0])
38+
return super(Enum, cls).__new__(cls)
39+
40+
# Scenario 2: Restriction on multiple positional arguments
41+
elif len(args) > 1:
42+
raise TypeError(f"{cls.__name__}() takes at most 1 positional argument ({len(args)} given)")
43+
44+
# Scenario 3: Creating an instance (e.g. Color() або Color(BLUE=3))
45+
return super(Enum, cls).__new__(cls)
46+
47+
def __init__(self, **kwargs):
48+
# 1. Convert class-level attributes (constants) to EnumValue objects
49+
self._scan_class_attrs()
50+
# 2. Add dynamic arguments from constructor
51+
if kwargs:
52+
self.append(**kwargs)
53+
54+
@classmethod
55+
def _lookup(cls, value):
56+
# Finds an EnumValue by its raw value
57+
for key in dir(cls):
58+
if key.startswith('_'):
59+
continue
60+
attr = getattr(cls, key)
61+
if isinstance(attr, EnumValue) and (attr.value == value or attr.name == value):
62+
return attr
63+
if not callable(attr) and attr == value:
64+
# Wrap static numbers found in class definition
65+
return EnumValue(attr, key)
66+
return attr
67+
68+
raise AttributeError(f"{value} is not in {cls.__name__} enum")
69+
70+
def list_members(self):
71+
# Returns a list of tuples (name, value) for all members
72+
return [(m.name, m.value) for m in self]
73+
74+
def _update(self, key, value):
75+
setattr(self.__class__, key, EnumValue(value, key))
76+
77+
def _scan_class_attrs(self):
78+
# Converts static class attributes into EnumValue objects
79+
# List of methods and internal names that should not be converted
80+
ignored = ('append', 'is_value', 'list_members')
81+
for key in dir(self.__class__):
82+
# Skip internal names and methods
83+
if key.startswith('_') or key in ignored:
84+
continue
85+
86+
value = getattr(self.__class__, key)
87+
# Convert only constants, not methods
88+
if not callable(value) and not isinstance(value, EnumValue):
89+
self._update(key, value)
90+
91+
def is_value(self, value):
92+
return any(member.value == value for member in self)
93+
94+
def append(self, **kwargs):
95+
# Adds new members dynamically.
96+
for key, value in kwargs.items():
97+
if hasattr(self, key):
98+
raise AttributeError(f"Enum key '{key}' is immutable")
99+
self._update(key, value)
100+
101+
def __repr__(self):
102+
members = [f"{m.name}={m.value}" for m in self]
103+
# Return a string like: Color(RED=1, GREEN=2, BLUE=3)
104+
return f"{self.__class__.__name__}({', '.join(members)})"
105+
106+
def __call__(self, value):
107+
for member in self:
108+
if member.value == value:
109+
return member
110+
raise ValueError(f"no such value: {value}")
111+
112+
def __setattr__(self, key, value):
113+
if hasattr(self, key) and isinstance(getattr(self, key), EnumValue):
114+
raise AttributeError(f"Enum member '{key}' is immutable")
115+
super().__setattr__(key, value)
116+
117+
def __delattr__(self, key):
118+
if hasattr(self, key) and isinstance(getattr(self, key), EnumValue):
119+
raise AttributeError("Enum members cannot be deleted")
120+
super().__delattr__(key)
121+
122+
def __len__(self):
123+
return sum(1 for _ in self)
124+
125+
def __iter__(self):
126+
for key in dir(self.__class__):
127+
attr = getattr(self.__class__, key)
128+
if isinstance(attr, EnumValue):
129+
yield attr
130+
131+
132+
def enum(**kwargs): # `**kwargs` kept backwards compatible as in the Internet examples
133+
return Enum(**kwargs)
134+
135+
136+
if __name__ == '__main__':
137+
# --- Usage Example 1 ---
138+
# 1. Creation via class
139+
class Color(Enum):
140+
RED = 1
141+
GREEN = 2
142+
143+
# 2. Create instance
144+
c = Color()
145+
print(f"Enum repr c: {c}")
146+
147+
# 3. Dynamic addition
148+
c.append(BLUE=3)
149+
print(f"c after append: {c}")
150+
151+
print('dir(c):', dir(c))
152+
153+
# 4. Immutability and name protection check
154+
try:
155+
c.append(append=True)
156+
except AttributeError as e:
157+
print(f"\nAttributeError: Reserved name protection: {e}\n")
158+
159+
# 5. Basic access
160+
print(f"RED: Name={c.RED.name}, Value={c.RED.value}, EnumValue={c.RED}, Call={c.RED()} ")
161+
162+
# 6. Assertions
163+
assert c.RED == 1
164+
assert c.RED.value == 1
165+
assert c.RED.name == 'RED'
166+
167+
# 7. Reverse lookup
168+
print(f"c(1) lookup object: {c(1)}, Name={c(1).name}") # RED
169+
assert c(1).name == 'RED'
170+
assert c(1) == 1
171+
172+
# 8. Iteration
173+
print("Values list:", [member.value for member in c])
174+
print("Names list:", [member.name for member in c])
175+
176+
try:
177+
c(7)
178+
except ValueError as e:
179+
print(f"\nValueError: {c} {e}\n")
180+
181+
# --- Usage Example 2 ---
182+
# 1. Define an Enum class
183+
class Status(Enum):
184+
IDLE = 0
185+
RUNNING = 1
186+
ERROR = 2
187+
188+
# 2. Test: Reverse Lookup
189+
# This simulates receiving a byte from the hardware
190+
received_byte = 1
191+
status = Status(received_byte)
192+
print(f"Lookup check: Received {received_byte} -> {status!r}")
193+
assert status == Status.RUNNING
194+
assert status.name == "RUNNING"
195+
196+
# 3. Test: Comparisons
197+
print(f"Comparison check: {status} == 1 is {status == 1}")
198+
assert status == 1
199+
assert status != 0
200+
assert status == Status.RUNNING
201+
202+
# 4. Test: Immutability
203+
try:
204+
Status.RUNNING.value = 99
205+
except AttributeError as e:
206+
print(f"\nImmutability check: Passed (Cannot modify EnumValue): {e}\n")
207+
208+
# 5. Test: Dynamic Append
209+
powers = Enum()
210+
powers.append(LOW=10, HIGH=100)
211+
print(f"Dynamic Enum check: {powers}")
212+
assert powers.LOW == 10
213+
214+
# 6. Test: Iteration
215+
print("Iteration check: ", end="")
216+
for m in Status():
217+
print(f"{m.name}, ", end="")
218+
print("-> Passed")
219+
220+
# 7. Test: Error handling for invalid lookup
221+
try:
222+
Status(99)
223+
except AttributeError as e:
224+
print(f"\nAttributeError: Invalid lookup check: Caught expected error -> {e}\n")
225+
226+
print("\nAll tests passed successfully!")

0 commit comments

Comments
 (0)