Skip to content

Commit b03ffee

Browse files
committed
Enum: Use Functional API CPython instead of kwargs.
Signed-off-by: Ihor Nehrutsa <Ihor.Nehrutsa@gmail.com>
1 parent 0b2955e commit b03ffee

3 files changed

Lines changed: 189 additions & 97 deletions

File tree

python-stdlib/enum/enum.py

Lines changed: 73 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# enum.py
2-
# version="1.2.2"
2+
# version="1.2.3"
33

44

55
class EnumValue:
@@ -9,10 +9,10 @@ def __init__(self, value, name):
99
object.__setattr__(self, 'name', name)
1010

1111
def __repr__(self):
12-
return f"<{self.name}: {self.value}>"
12+
return f"{self.name}: {self.value}"
1313

14-
def __str__(self):
15-
return str(self.value)
14+
# def __str__(self):
15+
# return str(self.value)
1616

1717
def __call__(self):
1818
return self.value
@@ -22,34 +22,35 @@ def __eq__(self, other):
2222
return self.value == other.value
2323
return self.value == other
2424

25-
def __int__(self):
26-
return self.value
25+
# def __int__(self):
26+
# return self.value
2727

2828
def __setattr__(self, key, value):
2929
raise AttributeError("EnumValue is immutable")
3030

3131

3232
class Enum:
33-
def __new__(cls, *args, **kwargs):
33+
def __new__(cls, name=None, names=None):
3434
# Scenario 1: Reverse lookup by value (e.g., Status(1))
35-
if len(args) == 1:
35+
if name is not None and names is None:
3636
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)")
37+
return cls._lookup(name)
4338

44-
# Scenario 3: Creating an instance (e.g. Color() або Color(BLUE=3))
39+
# Scenario 2: Functional API (e.g., Enum('Color', {'RED': 1}))
4540
return super(Enum, cls).__new__(cls)
4641

47-
def __init__(self, **kwargs):
42+
def __init__(self, name=None, names=None):
4843
# 1. Convert class-level attributes (constants) to EnumValue objects
4944
self._scan_class_attrs()
50-
# 2. Add dynamic arguments from constructor
51-
if kwargs:
52-
self.append(**kwargs)
45+
46+
# Support Functional API: Enum('Name', {'KEY': VALUE})
47+
if name is not None and isinstance(names, dict):
48+
for key, value in names.items():
49+
# Prevent addition if the key already exists
50+
if not hasattr(self, key):
51+
self._update(key, value)
52+
53+
object.__setattr__(self, '_initialized', True)
5354

5455
@classmethod
5556
def _lookup(cls, value):
@@ -63,7 +64,6 @@ def _lookup(cls, value):
6364
if not callable(attr) and attr == value:
6465
# Wrap static numbers found in class definition
6566
return EnumValue(attr, key)
66-
return attr
6767

6868
raise AttributeError(f"{value} is not in {cls.__name__} enum")
6969

@@ -77,7 +77,7 @@ def _update(self, key, value):
7777
def _scan_class_attrs(self):
7878
# Converts static class attributes into EnumValue objects
7979
# List of methods and internal names that should not be converted
80-
ignored = ('append', 'is_value', 'list_members')
80+
ignored = ('is_value', 'list_members')
8181
for key in dir(self.__class__):
8282
# Skip internal names and methods
8383
if key.startswith('_') or key in ignored:
@@ -91,17 +91,13 @@ def _scan_class_attrs(self):
9191
def is_value(self, value):
9292
return any(member.value == value for member in self)
9393

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-
10194
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)})"
95+
# Supports the condition: obj == eval(repr(obj))
96+
members = {m.name: m.value for m in self}
97+
if self.__class__.__name__ == 'Enum':
98+
return f"Enum(name='Enum', names={members})"
99+
# Return a string like: Name(names={'KEY1': VALUE1, 'KEY2': VALUE2, ..})
100+
return f"{self.__class__.__name__}(names={members})"
105101

106102
def __call__(self, value):
107103
for member in self:
@@ -110,8 +106,8 @@ def __call__(self, value):
110106
raise ValueError(f"no such value: {value}")
111107

112108
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")
109+
if hasattr(self, '_initialized'):
110+
raise AttributeError(f"Enum '{self.__class__.__name__}' is static")
115111
super().__setattr__(key, value)
116112

117113
def __delattr__(self, key):
@@ -128,58 +124,49 @@ def __iter__(self):
128124
if isinstance(attr, EnumValue):
129125
yield attr
130126

131-
132-
def enum(**kwargs): # `**kwargs` kept backwards compatible as in the Internet examples
133-
return Enum(**kwargs)
127+
def __eq__(self, other):
128+
if not isinstance(other, Enum):
129+
return False
130+
return self.list_members() == other.list_members()
134131

135132

136133
if __name__ == '__main__':
137134
# --- Usage Example 1 ---
138-
# 1. Creation via class
135+
# Standard Class Definition
139136
class Color(Enum):
140-
RED = 1
141-
GREEN = 2
137+
RED = 'red'
138+
GREEN = 'green'
142139

143-
# 2. Create instance
140+
# Create instance
144141
c = Color()
145142
print(f"Enum repr c: {c}")
146143

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
144+
# Basic access
160145
print(f"RED: Name={c.RED.name}, Value={c.RED.value}, EnumValue={c.RED}, Call={c.RED()} ")
161146

162-
# 6. Assertions
163-
assert c.RED == 1
164-
assert c.RED.value == 1
147+
# Assertions
165148
assert c.RED.name == 'RED'
149+
assert c.RED.value == 'red'
150+
assert c.RED == 'red'
151+
assert c.RED() == 'red'
166152

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
153+
# Reverse Lookup via instance call
154+
print(f"c('red') lookup object: {c('red')}, Name={c('red').name}, value={c('red').value}") # RED
155+
assert c('red').name == 'RED'
156+
assert c('red').value == 'red'
157+
assert c('red') == 'red'
171158

172-
# 8. Iteration
159+
# Iteration
173160
print("Values list:", [member.value for member in c])
174161
print("Names list:", [member.name for member in c])
175162

176163
try:
177-
c(7)
164+
c(999)
178165
except ValueError as e:
179166
print(f"\nValueError: {c} {e}\n")
180167

181168
# --- Usage Example 2 ---
182-
# 1. Define an Enum class
169+
# Define an Enum class
183170
class Status(Enum):
184171
IDLE = 0
185172
RUNNING = 1
@@ -189,38 +176,48 @@ class Status(Enum):
189176
# This simulates receiving a byte from the hardware
190177
received_byte = 1
191178
status = Status(received_byte)
192-
print(f"Lookup check: Received {received_byte} -> {status!r}")
179+
print(f"Lookup check: Received {received_byte} -> {status}")
193180
assert status == Status.RUNNING
194181
assert status.name == "RUNNING"
195182

196-
# 3. Test: Comparisons
183+
# Test: Comparisons
197184
print(f"Comparison check: {status} == 1 is {status == 1}")
198185
assert status == 1
199186
assert status != 0
200187
assert status == Status.RUNNING
201188

202-
# 4. Test: Immutability
189+
# Immutability Check
203190
try:
204-
Status.RUNNING.value = 99
191+
Status.RUNNING.value = 999
205192
except AttributeError as e:
206193
print(f"\nImmutability check: Passed (Cannot modify EnumValue): {e}\n")
207194

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
195+
# Test: Iteration
215196
print("Iteration check: ", end="")
216197
for m in Status():
217198
print(f"{m.name}, ", end="")
218199
print("-> Passed")
219200

220-
# 7. Test: Error handling for invalid lookup
201+
# Test: Error handling for invalid lookup
221202
try:
222-
Status(99)
203+
Status(999)
223204
except AttributeError as e:
224205
print(f"\nAttributeError: Invalid lookup check: Caught expected error -> {e}\n")
225206

207+
# --- Example 3: Functional API and serialization ---
208+
print("\n--- Functional API and Eval Check ---")
209+
210+
# Verify that eval(repr(obj)) restores the object
211+
c2 = eval(repr(c))
212+
print(f"Original: {repr(c)}")
213+
print(f"Restored: {repr(c2)}")
214+
print(f"Objects are equal: {c == c2}")
215+
assert c == c2
216+
217+
# Direct creation using the Enum base class
218+
state = eval("Enum(name='State', names={'ON':1, 'OFF':2})")
219+
print(f"Functional Enum instance (state): {state}")
220+
assert state.ON == 1
221+
assert state.ON.name == 'ON'
222+
226223
print("\nAll tests passed successfully!")

python-stdlib/enum/test_enum.py

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
# test_enum.py
12
# version="1.2.1"
23

34
import unittest
4-
from enum import Enum, EnumValue, enum
5+
from enum import Enum, EnumValue
56

67

78
class TestEnum(unittest.TestCase):
@@ -21,18 +22,6 @@ def test_class_attributes(self):
2122
self.assertEqual(self.color.RED.name, 'RED')
2223
self.assertIsInstance(self.color.RED, EnumValue)
2324

24-
def test_init_kwargs(self):
25-
"""Тест додавання значень через конструктор __init__(**kwargs)"""
26-
c = self.ColorClass(YELLOW=4, BLACK=0)
27-
self.assertEqual(c.YELLOW.value, 4)
28-
self.assertEqual(c.BLACK.name, 'BLACK')
29-
30-
def test_append(self):
31-
"""Тест методу append(**kwargs) та ланцюжкового виклику"""
32-
self.color.append(MAGENTA=5).append(CYAN=6)
33-
self.assertEqual(self.color.MAGENTA.value, 5)
34-
self.assertEqual(self.color.CYAN.name, 'CYAN')
35-
3625
def test_comparison(self):
3726
"""Тест порівняння EnumValue з числами та іншими об'єктами"""
3827
self.assertTrue(self.color.RED == 1)
@@ -80,14 +69,6 @@ def test_enum_value_immutability(self):
8069
def test_len(self):
8170
"""Тест магічного методу __len__"""
8271
self.assertEqual(len(self.color), 3)
83-
self.color.append(WHITE=7)
84-
self.assertEqual(len(self.color), 4)
85-
86-
def test_backwards_compatible_function(self):
87-
"""Тест глобальної функції enum(**kwargs)"""
88-
e = enum(A=10, B=20)
89-
self.assertEqual(e.A.value, 10)
90-
self.assertEqual(e.B.name, 'B')
9172

9273
def test_call_method(self):
9374
"""Тест виклику об'єкта як функції c.RED() -> 1"""

0 commit comments

Comments
 (0)