Repository navigation
python-stdlib/enum/enum.py: Add Enum class. #980
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
IhorNehrutsa
wants to merge
3
commits into
micropython:master
Choose a base branch
from
IhorNehrutsa:enum
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Lightweight Python Enum (No Metaclasses) | ||
|
|
||
| A lightweight Python implementation of enumerations (`Enum`, `IntEnum`, `StrEnum`) created without using metaclasses. Supports class syntax, functional API, strict value type checking, and immutability. | ||
|
|
||
| ## ?? Features | ||
|
|
||
| * **No Metaclasses**: Dynamic type construction using standard `type()`. | ||
| * **Types**: Base `Enum`, as well as strictly typed `IntEnum` and `StrEnum`. | ||
| * **Dual Syntax**: Declaration via standard `class` statements or functional API calls. | ||
| * **Immutability**: Attribute mutation guard preventing changes after member creation. | ||
| * **Serialization**: `.dump()` method for easy string representation and reconstruction via `eval()`. | ||
|
|
||
| ## ?? Usage | ||
|
|
||
| ### 1. Class Syntax Declaration | ||
|
|
||
| ```python | ||
| from enum import Enum, IntEnum, StrEnum | ||
|
|
||
| class Color(Enum): | ||
| RED = 1 | ||
| GREEN = 2 | ||
| BLUE = 3 | ||
|
|
||
| class Status(StrEnum): | ||
| PENDING = "pending" | ||
| DONE = "done" | ||
|
|
||
| # Accessing members | ||
| print(Color.RED) # <Color.RED: 1> | ||
| print(Color.RED.name) # 'RED' | ||
| print(Color.RED.value) # 1 | ||
| print(Status.PENDING) # 'pending' (for StrEnum, string output equals value) | ||
| ``` | ||
|
|
||
| ### 2. Functional API | ||
|
|
||
| ```python | ||
| # Enum with sequential integer values | ||
| Priority = Enum("Priority", "LOW MEDIUM HIGH", start=1) | ||
| print(Priority.HIGH.value) # 3 | ||
|
|
||
| # Enum from a dictionary mapping | ||
| HTTPStatus = IntEnum("HTTPStatus", {"OK": 200, "NOT_FOUND": 404}) | ||
| print(HTTPStatus.NOT_FOUND.value) # 404 | ||
| ``` | ||
|
|
||
| ### 3. Lookup, Iteration, and Type Enforcement | ||
|
|
||
| ```python | ||
| # Lookup by value or name | ||
| item = Color(1) # Color.RED | ||
| item = Color("RED") # Color.RED | ||
|
|
||
| # Iteration over members | ||
| for member in Color: | ||
| print(member.name, member.value) | ||
|
|
||
| # Strict type checks in IntEnum / StrEnum | ||
| try: | ||
| class BadEnum(IntEnum): | ||
| INVALID = "not an int" # Raises TypeError | ||
| except TypeError as e: | ||
| print(e) | ||
| ``` | ||
|
|
||
| ### 4. Serialization & Evaluation | ||
|
|
||
| ```python | ||
| dump_str = Color.dump() | ||
| print(dump_str) # "Enum('Color', {'RED': 1, 'GREEN': 2, 'BLUE': 3})" | ||
|
|
||
| # Reconstructing the Enum class object | ||
| RestoredColor = eval(dump_str) | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| # enum.py | ||
| # Enum implementation without metaclasses | ||
| # version="1.4.0" | ||
|
|
||
| # ============================================================================== | ||
| # Variable & Abbreviation Definitions: | ||
| # ============================================================================== | ||
| # Functions & Helper Methods: | ||
| # _c(e, n, v, v_t) -> create_enum_item: Helper function to construct a typed enum instance. | ||
| # _l(v) -> lookup: Internal classmethod to find an enum member by name or value. | ||
| # _i() -> items: Internal classmethod to initialize/retrieve tuple of enum instances. | ||
| # _s(s, k, v) -> __setattr__: Inner setter guard raising AttributeError on enum instances. | ||
| # | ||
| # Helper Arguments & Local Variables: | ||
| # e - enum_name / class_name: String representing the Enum class name (e.g., "Color"). | ||
| # n - name: String representing the enum member key (e.g., "RED"). | ||
| # v - value: Actual value assigned to the enum member (e.g., 1, "pending"). | ||
| # v_t - value_type: Target constraint type (`int`, `str`, or `None` for generic Enum). | ||
| # l - names_list: List of parsed string keys from comma/space-separated inputs. | ||
| # d - dict / mapping: Temporary dictionary for parsed enum members or dump serialization. | ||
| # i, __i - items: List or tuple containing initialized enum member instances. | ||
| # c - class / new_class: Dynamically created Enum class object via `type()`. | ||
| # s, k, o - self, key/attribute_name, other_object (standard short parameters). | ||
| # ============================================================================== | ||
|
|
||
|
|
||
| def _c(e, n, v, v_t=None): | ||
| # Create enum item | ||
| def _s(s, k, v): | ||
| raise AttributeError("cannot set attribute") | ||
|
|
||
| # Type checks for IntEnum and StrEnum | ||
| if v_t is int and not isinstance(v, int): | ||
| raise TypeError(f"IntEnum member {n!r} value must be int, got {type(v).__name__}") | ||
| elif v_t is str and not isinstance(v, str): | ||
| raise TypeError(f"StrEnum member {n!r} value must be str, got {type(v).__name__}") | ||
|
|
||
| # Ensure 'e' is a clean class name string, even if a class type object was passed | ||
| e = e.__name__ if hasattr(e, "__name__") else str(e) | ||
|
|
||
| # Create class: type(name, bases, dict), which inherits a base type (int, str, etc.) | ||
| return type( | ||
| f"{e}.{n}", | ||
| (type(v),), | ||
| { | ||
| "name": n, | ||
| "value": v, | ||
| "__str__": lambda s: str(v) if v_t in (int, str) else f"{e}.{n}", | ||
| "__repr__": lambda s: f"<{e}.{n}: {v!r}>", | ||
| "__call__": lambda s: v, | ||
| "__setattr__": _s, | ||
| }, | ||
| )(v) | ||
|
|
||
|
|
||
| class Enum: | ||
| _v_t = None # value type | ||
|
|
||
| def __new__(cls, value=None, names=None, *, start=1): | ||
| # Functional API: dynamic creation of a new Enum class | ||
| if value is not None and names is not None: | ||
| is_str_enum = cls._v_t is str | ||
|
|
||
| # Parse 'names' parameter into a key-value dictionary: | ||
| if isinstance(names, dict): | ||
| d = names | ||
| elif isinstance(names, str): | ||
| # Strings (comma/space-separated) | ||
| l = names.replace(",", " ").split() # names_list | ||
| d = ( | ||
| {k: k for k in l} | ||
| if is_str_enum | ||
| else {k: v for v, k in enumerate(l, start=start)} | ||
| ) | ||
| elif isinstance(names, (list, tuple)): | ||
| # Lists or tuples (flat or key-value pairs) | ||
| if names and isinstance(names[0], (list, tuple)): | ||
| d = dict(names) | ||
| else: | ||
| d = ( | ||
| {k: k for k in names} | ||
| if is_str_enum | ||
| else {k: v for v, k in enumerate(names, start=start)} | ||
| ) | ||
| else: | ||
| # For sets and other iterables | ||
| d = ( | ||
| {k: k for k in names} | ||
| if is_str_enum | ||
| else {k: v for v, k in enumerate(names, start=start)} | ||
| ) | ||
| # Construct and return a new class type | ||
| c = type(str(value), (cls,), {}) | ||
| i = [] | ||
| for k, v in d.items(): | ||
| e = _c(value, k, v, v_t=cls._v_t) | ||
| setattr(c, k, e) | ||
| i.append(e) | ||
| if i: | ||
| c.__i = tuple(i) | ||
| return c | ||
|
|
||
| # Lookup existing member by value or name: e.g., Color(1) or Color("RED") | ||
| if value is not None and cls not in (Enum, IntEnum, StrEnum): | ||
| return cls._l(value) | ||
|
|
||
| return super().__new__(cls) | ||
|
|
||
| def __init__(self, *args, **kwargs): | ||
| # Trigger lazy items initialization for class syntax declarations | ||
| if "__i" not in self.__class__.__dict__: | ||
| self._i() | ||
|
|
||
| def __str__(self): | ||
| if hasattr(self, "value") and self.value is not None: | ||
| return f"{type(self).__name__}.{self.name}" | ||
| return type(self).__name__ | ||
|
|
||
| def __repr__(self): | ||
| if hasattr(self, "value") and self.value is not None: | ||
| return f"<{type(self).__name__}.{self.name}: {self.value!r}>" | ||
| return f"<enum '{type(self).__name__}'>" | ||
|
|
||
| def __call__(self, v): | ||
| return self._l(v) | ||
|
|
||
| def __getitem__(self, k): | ||
| # Instance-level container lookup: Color()["RED"] | ||
| for i in self._i(): | ||
| if i.name == k: | ||
| return i | ||
| raise KeyError(k) | ||
|
|
||
| def __eq__(self, o): | ||
| return isinstance(o, Enum) and self._i() == o._i() | ||
|
|
||
| @classmethod | ||
| def __iter__(cls): | ||
| return iter(cls._i()) | ||
|
|
||
| @classmethod | ||
| def __len__(cls): | ||
| return len(cls._i()) | ||
|
|
||
| @classmethod | ||
| def __setattr__(cls, k, v): | ||
| raise AttributeError("cannot set attribute") | ||
|
|
||
| @classmethod | ||
| def __delattr__(cls, k): | ||
| raise AttributeError("cannot delete attribute") | ||
|
|
||
| @classmethod | ||
| def items(cls): | ||
| return cls._i() | ||
|
|
||
| @classmethod | ||
| def dump(cls): | ||
| # Serialize enum members to string representation for eval compatibility | ||
| # obj == eval(dump(obj)) | ||
| if cls._v_t is None: | ||
| e = "Enum" | ||
| else: | ||
| e = cls._v_t.__name__[0].upper() + cls._v_t.__name__[1:] + "Enum" | ||
| d = {i.name: i.value for i in cls._i()} | ||
| return f"{e}('{cls.__name__}', {d})" | ||
|
|
||
| @classmethod | ||
| def _i(cls): | ||
| # Get enum items | ||
| if "__i" not in cls.__dict__: | ||
| # Convert raw class attributes into typed enum instances | ||
| i = [] # items | ||
| for k, v in list(cls.__dict__.items()): | ||
| if not k.startswith("_") and not callable(v): | ||
| e = _c(cls.__name__, k, v, v_t=cls._v_t) | ||
| setattr(cls, k, e) | ||
| i.append(e) | ||
| cls.__i = tuple(i) | ||
| return cls.__i | ||
|
|
||
| @classmethod | ||
| def _l(cls, v): | ||
| # Lookup by value or name | ||
| for i in cls._i(): | ||
| if i.value == v or i.name == v: | ||
| return i | ||
| raise ValueError(f"{v!r} is not a valid {cls.__name__}") | ||
|
|
||
|
|
||
| class IntEnum(Enum): | ||
| _v_t = int | ||
|
|
||
|
|
||
| class StrEnum(Enum): | ||
| _v_t = str |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.