[3.11] gh-108682: [Enum] raise TypeError if super().__new__ called in custom __new__ (GH-108704) (GH-108739)

When overriding the `__new__` method of an enum, the underlying data type should be created directly; i.e. .

    member = object.__new__(cls)
    member = int.__new__(cls, value)
    member = str.__new__(cls, value)

Calling `super().__new__()` finds the lookup version of `Enum.__new__`, and will now raise an exception when detected.

(cherry picked from commit d48760b2f1)
This commit is contained in:
Ethan Furman 2023-09-07 18:57:48 -07:00 committed by GitHub
parent e46be0d2fa
commit effa2ecdcf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 1 deletions

View file

@ -422,10 +422,17 @@ enumeration, with the exception of special methods (:meth:`__str__`,
:meth:`__add__`, etc.), descriptors (methods are also descriptors), and :meth:`__add__`, etc.), descriptors (methods are also descriptors), and
variable names listed in :attr:`_ignore_`. variable names listed in :attr:`_ignore_`.
Note: if your enumeration defines :meth:`__new__` and/or :meth:`__init__` then Note: if your enumeration defines :meth:`__new__` and/or :meth:`__init__`,
any value(s) given to the enum member will be passed into those methods. any value(s) given to the enum member will be passed into those methods.
See `Planet`_ for an example. See `Planet`_ for an example.
.. note::
The :meth:`__new__` method, if defined, is used during creation of the Enum
members; it is then replaced by Enum's :meth:`__new__` which is used after
class creation for lookup of existing members. See :ref:`new-vs-init` for
more details.
Restricted Enum subclassing Restricted Enum subclassing
--------------------------- ---------------------------
@ -860,6 +867,8 @@ Some rules:
:meth:`__str__` method has been reset to their data types' :meth:`__str__` method has been reset to their data types'
:meth:`__str__` method. :meth:`__str__` method.
.. _new-vs-init:
When to use :meth:`__new__` vs. :meth:`__init__` When to use :meth:`__new__` vs. :meth:`__init__`
------------------------------------------------ ------------------------------------------------
@ -892,6 +901,11 @@ want one of them to be the value::
>>> print(Coordinate(3)) >>> print(Coordinate(3))
Coordinate.VY Coordinate.VY
.. warning::
*Do not* call ``super().__new__()``, as the lookup-only ``__new__`` is the one
that is found; instead, use the data type directly.
Finer Points Finer Points
^^^^^^^^^^^^ ^^^^^^^^^^^^
@ -1316,6 +1330,13 @@ to handle any extra arguments::
members; it is then replaced by Enum's :meth:`__new__` which is used after members; it is then replaced by Enum's :meth:`__new__` which is used after
class creation for lookup of existing members. class creation for lookup of existing members.
.. warning::
*Do not* call ``super().__new__()``, as the lookup-only ``__new__`` is the one
that is found; instead, use the data type directly -- e.g.::
obj = int.__new__(cls, value)
OrderedEnum OrderedEnum
^^^^^^^^^^^ ^^^^^^^^^^^

View file

@ -863,6 +863,8 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s
value = first_enum._generate_next_value_(name, start, count, last_values[:]) value = first_enum._generate_next_value_(name, start, count, last_values[:])
last_values.append(value) last_values.append(value)
names.append((name, value)) names.append((name, value))
if names is None:
names = ()
# Here, names is either an iterable of (name, value) or a mapping. # Here, names is either an iterable of (name, value) or a mapping.
for item in names: for item in names:
@ -1107,6 +1109,11 @@ def __new__(cls, value):
for member in cls._member_map_.values(): for member in cls._member_map_.values():
if member._value_ == value: if member._value_ == value:
return member return member
# still not found -- verify that members exist, in-case somebody got here mistakenly
# (such as via super when trying to override __new__)
if not cls._member_map_:
raise TypeError("%r has no members defined" % cls)
#
# still not found -- try _missing_ hook # still not found -- try _missing_ hook
try: try:
exc = None exc = None

View file

@ -322,6 +322,17 @@ def spam(cls):
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
del Season.SPRING.name del Season.SPRING.name
def test_bad_new_super(self):
with self.assertRaisesRegex(
TypeError,
'has no members defined',
):
class BadSuper(self.enum_type):
def __new__(cls, value):
obj = super().__new__(cls, value)
return obj
failed = 1
def test_basics(self): def test_basics(self):
TE = self.MainEnum TE = self.MainEnum
if self.is_flag: if self.is_flag:

View file

@ -0,0 +1,2 @@
Enum: raise :exc:`TypeError` if ``super().__new__()`` is called from a
custom ``__new__``.