Skip to content

Commit

Permalink
gh-108682: [Enum] raise TypeError if super().__new__ called in custom…
Browse files Browse the repository at this point in the history
… __new__ (GH-108704)

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.
  • Loading branch information
ethanfurman authored Aug 31, 2023
1 parent 13a0007 commit d48760b
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 56 deletions.
23 changes: 22 additions & 1 deletion Doc/howto/enum.rst
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,17 @@ enumeration, with the exception of special methods (:meth:`__str__`,
:meth:`__add__`, etc.), descriptors (methods are also descriptors), and
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.
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
---------------------------
Expand Down Expand Up @@ -895,6 +902,8 @@ Some rules:
:meth:`__str__` method has been reset to their data types'
:meth:`__str__` method.

.. _new-vs-init:

When to use :meth:`__new__` vs. :meth:`__init__`
------------------------------------------------

Expand Down Expand Up @@ -927,6 +936,11 @@ want one of them to be the value::
>>> print(Coordinate(3))
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
^^^^^^^^^^^^
Expand Down Expand Up @@ -1353,6 +1367,13 @@ to handle any extra arguments::
members; it is then replaced by Enum's :meth:`__new__` which is used after
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
^^^^^^^^^^^
Expand Down
7 changes: 7 additions & 0 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,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[:])
last_values.append(value)
names.append((name, value))
if names is None:
names = ()

# Here, names is either an iterable of (name, value) or a mapping.
for item in names:
Expand Down Expand Up @@ -1112,6 +1114,11 @@ def __new__(cls, value):
for member in cls._member_map_.values():
if member._value_ == value:
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
try:
exc = None
Expand Down
Loading

0 comments on commit d48760b

Please sign in to comment.