]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-45417: [Enum] fix quadratic behavior during creation (GH-28907)
authorCarl Friedrich Bolz-Tereick <cfbolz@gmx.de>
Thu, 14 Oct 2021 20:59:51 +0000 (22:59 +0200)
committerGitHub <noreply@github.com>
Thu, 14 Oct 2021 20:59:51 +0000 (13:59 -0700)
Creating an Enum exhibited quadratic behavior based on the number of members in three places:
- `EnumDict._member_names`: a list searched with each new member's name
- member creation: a `for` loop checking each existing member to see if new member was a duplicate
- `auto()` values: a list of all previous values in enum was copied before being sent to `_generate_next_value()`

Two of those issues have been resolved:
- `_EnumDict._member_names` is now a dictionary so lookups are fast
- member creation tries a fast value lookup before falling back to the slower `for` loop lookup

The third issue still remains, as `_generate_next_value_()` can be user-overridden and could corrupt the last values list if it were not copied.

Lib/enum.py
Misc/NEWS.d/next/Library/2021-10-12-20-35-06.bpo-45417.gQM-O7.rst [new file with mode: 0644]

index 0776761ae6e7353fe6324e7d0c2c70a4c0656775..461d276eed862ac797af98a6cc54f7d3efea5f6a 100644 (file)
@@ -235,11 +235,18 @@ class _proto_member:
         enum_member._sort_order_ = len(enum_class._member_names_)
         # If another member with the same value was already defined, the
         # new member becomes an alias to the existing one.
-        for name, canonical_member in enum_class._member_map_.items():
-            if canonical_member._value_ == enum_member._value_:
-                enum_member = canonical_member
-                break
-        else:
+        try:
+            try:
+                # try to do a fast lookup to avoid the quadratic loop
+                enum_member = enum_class._value2member_map_[value]
+            except TypeError:
+                for name, canonical_member in enum_class._member_map_.items():
+                    if canonical_member._value_ == value:
+                        enum_member = canonical_member
+                        break
+                else:
+                    raise KeyError
+        except KeyError:
             # this could still be an alias if the value is multi-bit and the
             # class is a flag class
             if (
@@ -301,7 +308,7 @@ class _EnumDict(dict):
     """
     def __init__(self):
         super().__init__()
-        self._member_names = []
+        self._member_names = {} # use a dict to keep insertion order
         self._last_values = []
         self._ignore = []
         self._auto_called = False
@@ -365,7 +372,7 @@ class _EnumDict(dict):
                             )
                     self._auto_called = True
                 value = value.value
-            self._member_names.append(key)
+            self._member_names[key] = None
             self._last_values.append(value)
         super().__setitem__(key, value)
 
diff --git a/Misc/NEWS.d/next/Library/2021-10-12-20-35-06.bpo-45417.gQM-O7.rst b/Misc/NEWS.d/next/Library/2021-10-12-20-35-06.bpo-45417.gQM-O7.rst
new file mode 100644 (file)
index 0000000..a15c239
--- /dev/null
@@ -0,0 +1,2 @@
+Fix quadratic behaviour in the enum module: Creation of enum classes with a
+lot of entries was quadratic.