[Python-checkins] bpo-38250: [Enum] single-bit flags are canonical (GH-24215)

ethanfurman webhook-mailer at python.org
Mon Jan 25 17:26:38 EST 2021


https://github.com/python/cpython/commit/7aaeb2a3d682ecba125c33511e4b4796021d2f82
commit: 7aaeb2a3d682ecba125c33511e4b4796021d2f82
branch: master
author: Ethan Furman <ethan at stoneleaf.us>
committer: ethanfurman <ethan at stoneleaf.us>
date: 2021-01-25T14:26:19-08:00
summary:

bpo-38250: [Enum] single-bit flags are canonical (GH-24215)

Flag members are now divided by one-bit verses multi-bit, with multi-bit being treated as aliases. Iterating over a flag only returns the contained single-bit flags.

Iterating, repr(), and str() show members in definition order.

When constructing combined-member flags, any extra integer values are either discarded (CONFORM), turned into ints (EJECT) or treated as errors (STRICT). Flag classes can specify which of those three behaviors is desired:

>>> class Test(Flag, boundary=CONFORM):
...     ONE = 1
...     TWO = 2
...
>>> Test(5)
<Test.ONE: 1>

Besides the three above behaviors, there is also KEEP, which should not be used unless necessary -- for example, _convert_ specifies KEEP as there are flag sets in the stdlib that are incomplete and/or inconsistent (e.g. ssl.Options). KEEP will, as the name suggests, keep all bits; however, iterating over a flag with extra bits will only return the canonical flags contained, not the extra bits.

Iteration is now in member definition order.  If member definition order
matches increasing value order, then a more efficient method of flag
decomposition is used; otherwise, sort() is called on the results of
that method to get definition order.


``re`` module:

repr() has been modified to support as closely as possible its previous
output; the big difference is that inverted flags cannot be output as
before because the inversion operation now always returns the comparable
positive result; i.e.

   re.A|re.I|re.M|re.S is ~(re.L|re.U|re.S|re.T|re.DEBUG)

in both of the above terms, the ``value`` is 282.

re's tests have been updated to reflect the modifications to repr().

files:
A Misc/NEWS.d/next/Library/2021-01-14-15-07-16.bpo-38250.1fvhOk.rst
M Doc/library/enum.rst
M Lib/enum.py
M Lib/re.py
M Lib/test/test_enum.py
M Lib/test/test_re.py
M Lib/types.py
M Misc/ACKS

diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
index c532e2caec466..b27c5527c7f7c 100644
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -197,7 +197,7 @@ Having two enum members with the same name is invalid::
     ...
     Traceback (most recent call last):
     ...
-    TypeError: Attempted to reuse key: 'SQUARE'
+    TypeError: 'SQUARE' already defined as: 2
 
 However, two enum members are allowed to have the same value.  Given two members
 A and B with the same value (and A defined first), B is an alias to A.  By-value
@@ -422,7 +422,7 @@ any members.  So this is forbidden::
     ...
     Traceback (most recent call last):
     ...
-    TypeError: Cannot extend enumerations
+    TypeError: MoreColor: cannot extend enumeration 'Color'
 
 But this is allowed::
 
@@ -617,6 +617,7 @@ by extension, string enumerations of different types can also be compared
 to each other.  :class:`StrEnum` exists to help avoid the problem of getting
 an incorrect member::
 
+    >>> from enum import StrEnum
     >>> class Directions(StrEnum):
     ...     NORTH = 'north',    # notice the trailing comma
     ...     SOUTH = 'south'
@@ -638,12 +639,22 @@ IntFlag
 The next variation of :class:`Enum` provided, :class:`IntFlag`, is also based
 on :class:`int`.  The difference being :class:`IntFlag` members can be combined
 using the bitwise operators (&, \|, ^, ~) and the result is still an
-:class:`IntFlag` member.  However, as the name implies, :class:`IntFlag`
+:class:`IntFlag` member, if possible.  However, as the name implies, :class:`IntFlag`
 members also subclass :class:`int` and can be used wherever an :class:`int` is
-used.  Any operation on an :class:`IntFlag` member besides the bit-wise
-operations will lose the :class:`IntFlag` membership.
+used.
+
+.. note::
+
+    Any operation on an :class:`IntFlag` member besides the bit-wise operations will
+    lose the :class:`IntFlag` membership.
+
+.. note::
+
+    Bit-wise operations that result in invalid :class:`IntFlag` values will lose the
+    :class:`IntFlag` membership.
 
 .. versionadded:: 3.6
+.. versionchanged:: 3.10
 
 Sample :class:`IntFlag` class::
 
@@ -671,21 +682,41 @@ It is also possible to name the combinations::
     >>> Perm.RWX
     <Perm.RWX: 7>
     >>> ~Perm.RWX
-    <Perm.-8: -8>
+    <Perm: 0>
+    >>> Perm(7)
+    <Perm.RWX: 7>
+
+.. note::
+
+    Named combinations are considered aliases.  Aliases do not show up during
+    iteration, but can be returned from by-value lookups.
+
+.. versionchanged:: 3.10
 
 Another important difference between :class:`IntFlag` and :class:`Enum` is that
 if no flags are set (the value is 0), its boolean evaluation is :data:`False`::
 
     >>> Perm.R & Perm.X
-    <Perm.0: 0>
+    <Perm: 0>
     >>> bool(Perm.R & Perm.X)
     False
 
 Because :class:`IntFlag` members are also subclasses of :class:`int` they can
-be combined with them::
+be combined with them (but may lose :class:`IntFlag` membership::
+
+    >>> Perm.X | 4
+    <Perm.R|X: 5>
 
     >>> Perm.X | 8
-    <Perm.8|X: 9>
+    9
+
+.. note::
+
+    The negation operator, ``~``, always returns an :class:`IntFlag` member with a
+    positive value::
+
+        >>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
+        True
 
 :class:`IntFlag` members can also be iterated over::
 
@@ -717,7 +748,7 @@ flags being set, the boolean evaluation is :data:`False`::
     ...     GREEN = auto()
     ...
     >>> Color.RED & Color.GREEN
-    <Color.0: 0>
+    <Color: 0>
     >>> bool(Color.RED & Color.GREEN)
     False
 
@@ -751,7 +782,7 @@ value::
 
     >>> purple = Color.RED | Color.BLUE
     >>> list(purple)
-    [<Color.BLUE: 2>, <Color.RED: 1>]
+    [<Color.RED: 1>, <Color.BLUE: 2>]
 
 .. versionadded:: 3.10
 
@@ -953,7 +984,7 @@ to handle any extra arguments::
     ...     BLEACHED_CORAL = () # New color, no Pantone code yet!
     ...
     >>> Swatch.SEA_GREEN
-    <Swatch.SEA_GREEN: 2>
+    <Swatch.SEA_GREEN>
     >>> Swatch.SEA_GREEN.pantone
     '1246'
     >>> Swatch.BLEACHED_CORAL.pantone
@@ -1144,6 +1175,14 @@ Supported ``_sunder_`` names
   :class:`auto` to get an appropriate value for an enum member; may be
   overridden
 
+.. note::
+
+    For standard :class:`Enum` classes the next value chosen is the last value seen
+    incremented by one.
+
+    For :class:`Flag`-type classes the next value chosen will be the next highest
+    power-of-two, regardless of the last value seen.
+
 .. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
 .. versionadded:: 3.7 ``_ignore_``
 
@@ -1159,7 +1198,9 @@ and raise an error if the two do not match::
     ...
     Traceback (most recent call last):
     ...
-    TypeError: member order does not match _order_
+    TypeError: member order does not match _order_:
+    ['RED', 'BLUE', 'GREEN']
+    ['RED', 'GREEN', 'BLUE']
 
 .. note::
 
@@ -1179,11 +1220,9 @@ Private names are not converted to Enum members, but remain normal attributes.
 """"""""""""""""""""
 
 :class:`Enum` members are instances of their :class:`Enum` class, and are
-normally accessed as ``EnumClass.member``.  Under certain circumstances they
-can also be accessed as ``EnumClass.member.member``, but you should never do
-this as that lookup may fail or, worse, return something besides the
-:class:`Enum` member you are looking for (this is another good reason to use
-all-uppercase names for members)::
+normally accessed as ``EnumClass.member``.  In Python versions ``3.5`` to
+``3.9`` you could access members from other members -- this practice was
+discouraged, and in ``3.10`` :class:`Enum` has returned to not allowing it::
 
     >>> class FieldTypes(Enum):
     ...     name = 0
@@ -1191,11 +1230,12 @@ all-uppercase names for members)::
     ...     size = 2
     ...
     >>> FieldTypes.value.size
-    <FieldTypes.size: 2>
-    >>> FieldTypes.size.value
-    2
+    Traceback (most recent call last):
+    ...
+    AttributeError: FieldTypes: no attribute 'size'
 
 .. versionchanged:: 3.5
+.. versionchanged:: 3.10
 
 
 Creating members that are mixed with other data types
@@ -1237,14 +1277,14 @@ but not of the class::
     >>> dir(Planet)
     ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
     >>> dir(Planet.EARTH)
-    ['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
+    ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
 
 
 Combining members of ``Flag``
 """""""""""""""""""""""""""""
 
-If a combination of Flag members is not named, the :func:`repr` will include
-all named flags and all named combinations of flags that are in the value::
+Iterating over a combination of Flag members will only return the members that
+are comprised of a single bit::
 
     >>> class Color(Flag):
     ...     RED = auto()
@@ -1254,10 +1294,10 @@ all named flags and all named combinations of flags that are in the value::
     ...     YELLOW = RED | GREEN
     ...     CYAN = GREEN | BLUE
     ...
-    >>> Color(3)  # named combination
+    >>> Color(3)
     <Color.YELLOW: 3>
-    >>> Color(7)      # not named combination
-    <Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>
+    >>> Color(7)
+    <Color.RED|GREEN|BLUE: 7>
 
 ``StrEnum`` and :meth:`str.__str__`
 """""""""""""""""""""""""""""""""""
@@ -1269,3 +1309,71 @@ parts of Python will read the string data directly, while others will call
 :meth:`StrEnum.__str__` will be the same as :meth:`str.__str__` so that
 ``str(StrEnum.member) == StrEnum.member`` is true.
 
+``Flag`` and ``IntFlag`` minutia
+""""""""""""""""""""""""""""""""
+
+The code sample::
+
+    >>> class Color(IntFlag):
+    ...     BLACK = 0
+    ...     RED = 1
+    ...     GREEN = 2
+    ...     BLUE = 4
+    ...     PURPLE = RED | BLUE
+    ...     WHITE = RED | GREEN | BLUE
+    ...
+
+- single-bit flags are canonical
+- multi-bit and zero-bit flags are aliases
+- only canonical flags are returned during iteration::
+
+    >>> list(Color.WHITE)
+    [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
+
+- negating a flag or flag set returns a new flag/flag set with the
+  corresponding positive integer value::
+
+    >>> Color.GREEN
+    <Color.GREEN: 2>
+
+    >>> ~Color.GREEN
+    <Color.PURPLE: 5>
+
+- names of pseudo-flags are constructed from their members' names::
+
+    >>> (Color.RED | Color.GREEN).name
+    'RED|GREEN'
+
+- multi-bit flags, aka aliases, can be returned from operations::
+
+    >>> Color.RED | Color.BLUE
+    <Color.PURPLE: 5>
+
+    >>> Color(7)  # or Color(-1)
+    <Color.WHITE: 7>
+
+- membership / containment checking has changed slightly -- zero valued flags
+  are never considered to be contained::
+
+    >>> Color.BLACK in Color.WHITE
+    False
+
+  otherwise, if all bits of one flag are in the other flag, True is returned::
+
+    >>> Color.PURPLE in Color.WHITE
+    True
+
+There is a new boundary mechanism that controls how out-of-range / invalid
+bits are handled: ``STRICT``, ``CONFORM``, ``EJECT`', and ``KEEP``:
+
+  * STRICT --> raises an exception when presented with invalid values
+  * CONFORM --> discards any invalid bits
+  * EJECT --> lose Flag status and become a normal int with the given value
+  * KEEP --> keep the extra bits
+           - keeps Flag status and extra bits
+           - extra bits do not show up in iteration
+           - extra bits do show up in repr() and str()
+
+The default for Flag is ``STRICT``, the default for ``IntFlag`` is ``DISCARD``,
+and the default for ``_convert_`` is ``KEEP`` (see ``ssl.Options`` for an
+example of when ``KEEP`` is needed).
diff --git a/Lib/enum.py b/Lib/enum.py
index 8ca385420da02..d4b11521ab27f 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -1,6 +1,6 @@
 import sys
 from types import MappingProxyType, DynamicClassAttribute
-from builtins import property as _bltin_property
+from builtins import property as _bltin_property, bin as _bltin_bin
 
 
 __all__ = [
@@ -8,9 +8,15 @@
         'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag',
         'auto', 'unique',
         'property',
+        'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
         ]
 
 
+# Dummy value for Enum and Flag as there are explicit checks for them
+# before they have been created.
+# This is also why there are checks in EnumMeta like `if Enum is not None`
+Enum = Flag = EJECT = None
+
 def _is_descriptor(obj):
     """
     Returns True if obj is a descriptor, False otherwise.
@@ -56,6 +62,15 @@ def _is_private(cls_name, name):
     else:
         return False
 
+def _is_single_bit(num):
+    """
+    True if only one bit set in num (should be an int)
+    """
+    if num == 0:
+        return False
+    num &= num - 1
+    return num == 0
+
 def _make_class_unpicklable(obj):
     """
     Make the given obj un-picklable.
@@ -71,6 +86,37 @@ def _break_on_call_reduce(self, proto):
         setattr(obj, '__reduce_ex__', _break_on_call_reduce)
         setattr(obj, '__module__', '<unknown>')
 
+def _iter_bits_lsb(num):
+    while num:
+        b = num & (~num + 1)
+        yield b
+        num ^= b
+
+def bin(num, max_bits=None):
+    """
+    Like built-in bin(), except negative values are represented in
+    twos-compliment, and the leading bit always indicates sign
+    (0=positive, 1=negative).
+
+    >>> bin(10)
+    '0b0 1010'
+    >>> bin(~10)   # ~10 is -11
+    '0b1 0101'
+    """
+
+    ceiling = 2 ** (num).bit_length()
+    if num >= 0:
+        s = _bltin_bin(num + ceiling).replace('1', '0', 1)
+    else:
+        s = _bltin_bin(~num ^ (ceiling - 1) + ceiling)
+    sign = s[:3]
+    digits = s[3:]
+    if max_bits is not None:
+        if len(digits) < max_bits:
+            digits = (sign[-1] * max_bits + digits)[-max_bits:]
+    return "%s %s" % (sign, digits)
+
+
 _auto_null = object()
 class auto:
     """
@@ -92,22 +138,30 @@ def __get__(self, instance, ownerclass=None):
             try:
                 return ownerclass._member_map_[self.name]
             except KeyError:
-                raise AttributeError('%r not found in %r' % (self.name, ownerclass.__name__))
+                raise AttributeError(
+                        '%s: no attribute %r' % (ownerclass.__name__, self.name)
+                        )
         else:
             if self.fget is None:
-                raise AttributeError('%s: cannot read attribute %r' % (ownerclass.__name__, self.name))
+                raise AttributeError(
+                        '%s: no attribute %r' % (ownerclass.__name__, self.name)
+                        )
             else:
                 return self.fget(instance)
 
     def __set__(self, instance, value):
         if self.fset is None:
-            raise AttributeError("%s: cannot set attribute %r" % (self.clsname, self.name))
+            raise AttributeError(
+                    "%s: cannot set attribute %r" % (self.clsname, self.name)
+                    )
         else:
             return self.fset(instance, value)
 
     def __delete__(self, instance):
         if self.fdel is None:
-            raise AttributeError("%s: cannot delete attribute %r" % (self.clsname, self.name))
+            raise AttributeError(
+                    "%s: cannot delete attribute %r" % (self.clsname, self.name)
+                    )
         else:
             return self.fdel(instance)
 
@@ -148,11 +202,17 @@ def __set_name__(self, enum_class, member_name):
                 if enum_class._member_type_ is object:
                     enum_member._value_ = value
                 else:
-                    enum_member._value_ = enum_class._member_type_(*args)
+                    try:
+                        enum_member._value_ = enum_class._member_type_(*args)
+                    except Exception as exc:
+                        raise TypeError(
+                                '_value_ not set in __new__, unable to create it'
+                                ) from None
         value = enum_member._value_
         enum_member._name_ = member_name
         enum_member.__objclass__ = enum_class
         enum_member.__init__(*args)
+        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():
@@ -160,8 +220,21 @@ def __set_name__(self, enum_class, member_name):
                 enum_member = canonical_member
                 break
         else:
-            # no other instances found, record this member in _member_names_
-            enum_class._member_names_.append(member_name)
+            # this could still be an alias if the value is multi-bit and the
+            # class is a flag class
+            if (
+                    Flag is None
+                    or not issubclass(enum_class, Flag)
+                ):
+                # no other instances found, record this member in _member_names_
+                enum_class._member_names_.append(member_name)
+            elif (
+                    Flag is not None
+                    and issubclass(enum_class, Flag)
+                    and _is_single_bit(value)
+                ):
+                # no other instances found, record this member in _member_names_
+                enum_class._member_names_.append(member_name)
         # get redirect in place before adding to _member_map_
         # but check for other instances in parent classes first
         need_override = False
@@ -193,7 +266,7 @@ def __set_name__(self, enum_class, member_name):
             # This may fail if value is not hashable. We can't add the value
             # to the map, and by-value lookups for this value will be
             # linear.
-            enum_class._value2member_map_[value] = enum_member
+            enum_class._value2member_map_.setdefault(value, enum_member)
         except TypeError:
             pass
 
@@ -228,6 +301,7 @@ def __setitem__(self, key, value):
             if key not in (
                     '_order_', '_create_pseudo_member_',
                     '_generate_next_value_', '_missing_', '_ignore_',
+                    '_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_',
                     ):
                 raise ValueError(
                         '_sunder_ names, such as %r, are reserved for future Enum use'
@@ -265,10 +339,7 @@ def __setitem__(self, key, value):
             if isinstance(value, auto):
                 if value.value == _auto_null:
                     value.value = self._generate_next_value(
-                            key,
-                            1,
-                            len(self._member_names),
-                            self._last_values[:],
+                            key, 1, len(self._member_names), self._last_values[:],
                             )
                     self._auto_called = True
                 value = value.value
@@ -287,15 +358,11 @@ def update(self, members, **more_members):
             self[name] = value
 
 
-# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
-# until EnumMeta finishes running the first time the Enum class doesn't exist.
-# This is also why there are checks in EnumMeta like `if Enum is not None`
-Enum = None
-
 class EnumMeta(type):
     """
     Metaclass for Enum
     """
+
     @classmethod
     def __prepare__(metacls, cls, bases, **kwds):
         # check that previous enum members do not exist
@@ -311,7 +378,7 @@ def __prepare__(metacls, cls, bases, **kwds):
                     )
         return enum_dict
 
-    def __new__(metacls, cls, bases, classdict, **kwds):
+    def __new__(metacls, cls, bases, classdict, boundary=None, **kwds):
         # an Enum class is final once enumeration items have been defined; it
         # cannot be mixed with other types (int, float, etc.) if it has an
         # inherited __new__ unless a new __new__ is defined (or the resulting
@@ -346,15 +413,29 @@ def __new__(metacls, cls, bases, classdict, **kwds):
         classdict['_use_args_'] = use_args
         #
         # convert future enum members into temporary _proto_members
+        # and record integer values in case this will be a Flag
+        flag_mask = 0
         for name in member_names:
-            classdict[name] = _proto_member(classdict[name])
+            value = classdict[name]
+            if isinstance(value, int):
+                flag_mask |= value
+            classdict[name] = _proto_member(value)
         #
-        # house keeping structures
+        # house-keeping structures
         classdict['_member_names_'] = []
         classdict['_member_map_'] = {}
         classdict['_value2member_map_'] = {}
         classdict['_member_type_'] = member_type
         #
+        # Flag structures (will be removed if final class is not a Flag
+        classdict['_boundary_'] = (
+                boundary
+                or getattr(first_enum, '_boundary_', None)
+                )
+        classdict['_flag_mask_'] = flag_mask
+        classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1
+        classdict['_inverted_'] = None
+        #
         # If a custom type is mixed into the Enum, and it does not know how
         # to pickle itself, pickle.dumps will succeed but pickle.loads will
         # fail.  Rather than have the error show up later and possibly far
@@ -408,11 +489,75 @@ def __new__(metacls, cls, bases, classdict, **kwds):
             enum_class.__new__ = Enum.__new__
         #
         # py3 support for definition order (helps keep py2/py3 code in sync)
+        #
+        # _order_ checking is spread out into three/four steps
+        # - if enum_class is a Flag:
+        #   - remove any non-single-bit flags from _order_
+        # - remove any aliases from _order_
+        # - check that _order_ and _member_names_ match
+        #
+        # step 1: ensure we have a list
         if _order_ is not None:
             if isinstance(_order_, str):
                 _order_ = _order_.replace(',', ' ').split()
+        #
+        # remove Flag structures if final class is not a Flag
+        if (
+                Flag is None and cls != 'Flag'
+                or Flag is not None and not issubclass(enum_class, Flag)
+            ):
+            delattr(enum_class, '_boundary_')
+            delattr(enum_class, '_flag_mask_')
+            delattr(enum_class, '_all_bits_')
+            delattr(enum_class, '_inverted_')
+        elif Flag is not None and issubclass(enum_class, Flag):
+            # ensure _all_bits_ is correct and there are no missing flags
+            single_bit_total = 0
+            multi_bit_total = 0
+            for flag in enum_class._member_map_.values():
+                flag_value = flag._value_
+                if _is_single_bit(flag_value):
+                    single_bit_total |= flag_value
+                else:
+                    # multi-bit flags are considered aliases
+                    multi_bit_total |= flag_value
+            if enum_class._boundary_ is not KEEP:
+                missed = list(_iter_bits_lsb(multi_bit_total & ~single_bit_total))
+                if missed:
+                    raise TypeError(
+                            'invalid Flag %r -- missing values: %s'
+                            % (cls, ', '.join((str(i) for i in missed)))
+                            )
+            enum_class._flag_mask_ = single_bit_total
+            #
+            # set correct __iter__
+            member_list = [m._value_ for m in enum_class]
+            if member_list != sorted(member_list):
+                enum_class._iter_member_ = enum_class._iter_member_by_def_
+            if _order_:
+                # _order_ step 2: remove any items from _order_ that are not single-bit
+                _order_ = [
+                        o
+                        for o in _order_
+                        if o not in enum_class._member_map_ or _is_single_bit(enum_class[o]._value_)
+                        ]
+        #
+        if _order_:
+            # _order_ step 3: remove aliases from _order_
+            _order_ = [
+                    o
+                    for o in _order_
+                    if (
+                        o not in enum_class._member_map_
+                        or
+                        (o in enum_class._member_map_ and o in enum_class._member_names_)
+                        )]
+            # _order_ step 4: verify that _order_ and _member_names_ match
             if _order_ != enum_class._member_names_:
-                raise TypeError('member order does not match _order_')
+                raise TypeError(
+                        'member order does not match _order_:\n%r\n%r'
+                        % (enum_class._member_names_, _order_)
+                        )
         #
         return enum_class
 
@@ -422,7 +567,7 @@ def __bool__(self):
         """
         return True
 
-    def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1):
+    def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None):
         """
         Either returns an existing member, or creates a new enum class.
 
@@ -457,6 +602,7 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, s
                 qualname=qualname,
                 type=type,
                 start=start,
+                boundary=boundary,
                 )
 
     def __contains__(cls, member):
@@ -539,7 +685,7 @@ def __setattr__(cls, name, value):
             raise AttributeError('Cannot reassign members.')
         super().__setattr__(name, value)
 
-    def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1):
+    def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1, boundary=None):
         """
         Convenience method to create a new Enum class.
 
@@ -589,9 +735,9 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s
         if qualname is not None:
             classdict['__qualname__'] = qualname
 
-        return metacls.__new__(metacls, class_name, bases, classdict)
+        return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary)
 
-    def _convert_(cls, name, module, filter, source=None):
+    def _convert_(cls, name, module, filter, source=None, boundary=None):
         """
         Create a new Enum subclass that replaces a collection of global constants
         """
@@ -618,7 +764,7 @@ def _convert_(cls, name, module, filter, source=None):
         except TypeError:
             # unless some values aren't comparable, in which case sort by name
             members.sort(key=lambda t: t[0])
-        cls = cls(name, members, module=module)
+        cls = cls(name, members, module=module, boundary=boundary or KEEP)
         cls.__reduce_ex__ = _reduce_ex_by_name
         module_globals.update(cls.__members__)
         module_globals[name] = cls
@@ -733,6 +879,7 @@ class Enum(metaclass=EnumMeta):
 
     Derive from this class to define new enumerations.
     """
+
     def __new__(cls, value):
         # all enum instances are actually created during class construction
         # without calling this method; this method is called by the metaclass'
@@ -761,6 +908,11 @@ def __new__(cls, value):
             result = None
         if isinstance(result, cls):
             return result
+        elif (
+                Flag is not None and issubclass(cls, Flag)
+                and cls._boundary_ is EJECT and isinstance(result, int)
+            ):
+            return result
         else:
             ve_exc = ValueError("%r is not a valid %s" % (value, cls.__qualname__))
             if result is None and exc is None:
@@ -770,7 +922,8 @@ def __new__(cls, value):
                         'error in %s._missing_: returned %r instead of None or a valid member'
                         % (cls.__name__, result)
                         )
-            exc.__context__ = ve_exc
+            if not isinstance(exc, ValueError):
+                exc.__context__ = ve_exc
             raise exc
 
     def _generate_next_value_(name, start, count, last_values):
@@ -875,14 +1028,14 @@ def __new__(cls, *values):
             # it must be a string
             if not isinstance(values[0], str):
                 raise TypeError('%r is not a string' % (values[0], ))
-        if len(values) > 1:
+        if len(values) >= 2:
             # check that encoding argument is a string
             if not isinstance(values[1], str):
                 raise TypeError('encoding must be a string, not %r' % (values[1], ))
-            if len(values) > 2:
-                # check that errors argument is a string
-                if not isinstance(values[2], str):
-                    raise TypeError('errors must be a string, not %r' % (values[2], ))
+        if len(values) == 3:
+            # check that errors argument is a string
+            if not isinstance(values[2], str):
+                raise TypeError('errors must be a string, not %r' % (values[2]))
         value = str(*values)
         member = str.__new__(cls, value)
         member._value_ = value
@@ -900,7 +1053,22 @@ def _generate_next_value_(name, start, count, last_values):
 def _reduce_ex_by_name(self, proto):
     return self.name
 
-class Flag(Enum):
+class FlagBoundary(StrEnum):
+    """
+    control how out of range values are handled
+    "strict" -> error is raised  [default for Flag]
+    "conform" -> extra bits are discarded
+    "eject" -> lose flag status [default for IntFlag]
+    "keep" -> keep flag status and all bits
+    """
+    STRICT = auto()
+    CONFORM = auto()
+    EJECT = auto()
+    KEEP = auto()
+STRICT, CONFORM, EJECT, KEEP = FlagBoundary
+
+
+class Flag(Enum, boundary=STRICT):
     """
     Support for flags
     """
@@ -916,45 +1084,108 @@ def _generate_next_value_(name, start, count, last_values):
         """
         if not count:
             return start if start is not None else 1
-        for last_value in reversed(last_values):
-            try:
-                high_bit = _high_bit(last_value)
-                break
-            except Exception:
-                raise TypeError('Invalid Flag value: %r' % last_value) from None
+        last_value = max(last_values)
+        try:
+            high_bit = _high_bit(last_value)
+        except Exception:
+            raise TypeError('Invalid Flag value: %r' % last_value) from None
         return 2 ** (high_bit+1)
 
     @classmethod
-    def _missing_(cls, value):
+    def _iter_member_by_value_(cls, value):
         """
-        Returns member (possibly creating it) if one can be found for value.
+        Extract all members from the value in definition (i.e. increasing value) order.
         """
-        original_value = value
-        if value < 0:
-            value = ~value
-        possible_member = cls._create_pseudo_member_(value)
-        if original_value < 0:
-            possible_member = ~possible_member
-        return possible_member
+        for val in _iter_bits_lsb(value & cls._flag_mask_):
+            yield cls._value2member_map_.get(val)
+
+    _iter_member_ = _iter_member_by_value_
 
     @classmethod
-    def _create_pseudo_member_(cls, value):
+    def _iter_member_by_def_(cls, value):
+        """
+        Extract all members from the value in definition order.
+        """
+        yield from sorted(
+                cls._iter_member_by_value_(value),
+                key=lambda m: m._sort_order_,
+                )
+
+    @classmethod
+    def _missing_(cls, value):
         """
         Create a composite member iff value contains only members.
         """
-        pseudo_member = cls._value2member_map_.get(value, None)
-        if pseudo_member is None:
-            # verify all bits are accounted for
-            _, extra_flags = _decompose(cls, value)
-            if extra_flags:
-                raise ValueError("%r is not a valid %s" % (value, cls.__qualname__))
+        if not isinstance(value, int):
+            raise ValueError(
+                    "%r is not a valid %s" % (value, cls.__qualname__)
+                    )
+        # check boundaries
+        # - value must be in range (e.g. -16 <-> +15, i.e. ~15 <-> 15)
+        # - value must not include any skipped flags (e.g. if bit 2 is not
+        #   defined, then 0d10 is invalid)
+        flag_mask = cls._flag_mask_
+        all_bits = cls._all_bits_
+        neg_value = None
+        if (
+                not ~all_bits <= value <= all_bits
+                or value & (all_bits ^ flag_mask)
+            ):
+            if cls._boundary_ is STRICT:
+                max_bits = max(value.bit_length(), flag_mask.bit_length())
+                raise ValueError(
+                        "%s: invalid value: %r\n    given %s\n  allowed %s" % (
+                            cls.__name__, value, bin(value, max_bits), bin(flag_mask, max_bits),
+                            ))
+            elif cls._boundary_ is CONFORM:
+                value = value & flag_mask
+            elif cls._boundary_ is EJECT:
+                return value
+            elif cls._boundary_ is KEEP:
+                if value < 0:
+                    value = (
+                            max(all_bits+1, 2**(value.bit_length()))
+                            + value
+                            )
+            else:
+                raise ValueError(
+                        'unknown flag boundary: %r' % (cls._boundary_, )
+                        )
+        if value < 0:
+            neg_value = value
+            value = all_bits + 1 + value
+        # get members and unknown
+        unknown = value & ~flag_mask
+        member_value = value & flag_mask
+        if unknown and cls._boundary_ is not KEEP:
+            raise ValueError(
+                    '%s(%r) -->  unknown values %r [%s]'
+                    % (cls.__name__, value, unknown, bin(unknown))
+                    )
+        # normal Flag?
+        __new__ = getattr(cls, '__new_member__', None)
+        if cls._member_type_ is object and not __new__:
             # construct a singleton enum pseudo-member
             pseudo_member = object.__new__(cls)
-            pseudo_member._name_ = None
+        else:
+            pseudo_member = (__new__ or cls._member_type_.__new__)(cls, value)
+        if not hasattr(pseudo_member, 'value'):
             pseudo_member._value_ = value
-            # use setdefault in case another thread already created a composite
-            # with this value
+        if member_value:
+            pseudo_member._name_ = '|'.join([
+                m._name_ for m in cls._iter_member_(member_value)
+                ])
+            if unknown:
+                pseudo_member._name_ += '|0x%x' % unknown
+        else:
+            pseudo_member._name_ = None
+        # use setdefault in case another thread already created a composite
+        # with this value, but only if all members are known
+        # note: zero is a special case -- add it
+        if not unknown:
             pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member)
+            if neg_value is not None:
+                cls._value2member_map_[neg_value] = pseudo_member
         return pseudo_member
 
     def __contains__(self, other):
@@ -965,38 +1196,33 @@ def __contains__(self, other):
             raise TypeError(
                 "unsupported operand type(s) for 'in': '%s' and '%s'" % (
                     type(other).__qualname__, self.__class__.__qualname__))
+        if other._value_ == 0 or self._value_ == 0:
+            return False
         return other._value_ & self._value_ == other._value_
 
     def __iter__(self):
         """
-        Returns flags in decreasing value order.
+        Returns flags in definition order.
         """
-        members, extra_flags = _decompose(self.__class__, self.value)
-        return (m for m in members if m._value_ != 0)
+        yield from self._iter_member_(self._value_)
+
+    def __len__(self):
+        return self._value_.bit_count()
 
     def __repr__(self):
         cls = self.__class__
         if self._name_ is not None:
             return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_)
-        members, uncovered = _decompose(cls, self._value_)
-        return '<%s.%s: %r>' % (
-                cls.__name__,
-                '|'.join([str(m._name_ or m._value_) for m in members]),
-                self._value_,
-                )
+        else:
+            # only zero is unnamed by default
+            return '<%s: %r>' % (cls.__name__, self._value_)
 
     def __str__(self):
         cls = self.__class__
         if self._name_ is not None:
             return '%s.%s' % (cls.__name__, self._name_)
-        members, uncovered = _decompose(cls, self._value_)
-        if len(members) == 1 and members[0]._name_ is None:
-            return '%s.%r' % (cls.__name__, members[0]._value_)
         else:
-            return '%s.%s' % (
-                    cls.__name__,
-                    '|'.join([str(m._name_ or m._value_) for m in members]),
-                    )
+            return '%s(%s)' % (cls.__name__, self._value_)
 
     def __bool__(self):
         return bool(self._value_)
@@ -1017,86 +1243,56 @@ def __xor__(self, other):
         return self.__class__(self._value_ ^ other._value_)
 
     def __invert__(self):
-        members, uncovered = _decompose(self.__class__, self._value_)
-        inverted = self.__class__(0)
-        for m in self.__class__:
-            if m not in members and not (m._value_ & self._value_):
-                inverted = inverted | m
-        return self.__class__(inverted)
+        if self._inverted_ is None:
+            if self._boundary_ is KEEP:
+                # use all bits
+                self._inverted_ = self.__class__(~self._value_)
+            else:
+                # calculate flags not in this member
+                self._inverted_ = self.__class__(self._flag_mask_ ^ self._value_)
+            self._inverted_._inverted_ = self
+        return self._inverted_
 
 
-class IntFlag(int, Flag):
+class IntFlag(int, Flag, boundary=EJECT):
     """
     Support for integer-based Flags
     """
 
-    @classmethod
-    def _missing_(cls, value):
-        """
-        Returns member (possibly creating it) if one can be found for value.
-        """
-        if not isinstance(value, int):
-            raise ValueError("%r is not a valid %s" % (value, cls.__qualname__))
-        new_member = cls._create_pseudo_member_(value)
-        return new_member
-
-    @classmethod
-    def _create_pseudo_member_(cls, value):
-        """
-        Create a composite member iff value contains only members.
-        """
-        pseudo_member = cls._value2member_map_.get(value, None)
-        if pseudo_member is None:
-            need_to_create = [value]
-            # get unaccounted for bits
-            _, extra_flags = _decompose(cls, value)
-            # timer = 10
-            while extra_flags:
-                # timer -= 1
-                bit = _high_bit(extra_flags)
-                flag_value = 2 ** bit
-                if (flag_value not in cls._value2member_map_ and
-                        flag_value not in need_to_create
-                        ):
-                    need_to_create.append(flag_value)
-                if extra_flags == -flag_value:
-                    extra_flags = 0
-                else:
-                    extra_flags ^= flag_value
-            for value in reversed(need_to_create):
-                # construct singleton pseudo-members
-                pseudo_member = int.__new__(cls, value)
-                pseudo_member._name_ = None
-                pseudo_member._value_ = value
-                # use setdefault in case another thread already created a composite
-                # with this value
-                pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member)
-        return pseudo_member
-
     def __or__(self, other):
-        if not isinstance(other, (self.__class__, int)):
+        if isinstance(other, self.__class__):
+            other = other._value_
+        elif isinstance(other, int):
+            other = other
+        else:
             return NotImplemented
-        result = self.__class__(self._value_ | self.__class__(other)._value_)
-        return result
+        value = self._value_
+        return self.__class__(value | other)
 
     def __and__(self, other):
-        if not isinstance(other, (self.__class__, int)):
+        if isinstance(other, self.__class__):
+            other = other._value_
+        elif isinstance(other, int):
+            other = other
+        else:
             return NotImplemented
-        return self.__class__(self._value_ & self.__class__(other)._value_)
+        value = self._value_
+        return self.__class__(value & other)
 
     def __xor__(self, other):
-        if not isinstance(other, (self.__class__, int)):
+        if isinstance(other, self.__class__):
+            other = other._value_
+        elif isinstance(other, int):
+            other = other
+        else:
             return NotImplemented
-        return self.__class__(self._value_ ^ self.__class__(other)._value_)
+        value = self._value_
+        return self.__class__(value ^ other)
 
     __ror__ = __or__
     __rand__ = __and__
     __rxor__ = __xor__
-
-    def __invert__(self):
-        result = self.__class__(~self._value_)
-        return result
-
+    __invert__ = Flag.__invert__
 
 def _high_bit(value):
     """
@@ -1119,31 +1315,7 @@ def unique(enumeration):
                 (enumeration, alias_details))
     return enumeration
 
-def _decompose(flag, value):
-    """
-    Extract all members from the value.
-    """
-    # _decompose is only called if the value is not named
-    not_covered = value
-    negative = value < 0
-    members = []
-    for member in flag:
-        member_value = member.value
-        if member_value and member_value & value == member_value:
-            members.append(member)
-            not_covered &= ~member_value
-    if not negative:
-        tmp = not_covered
-        while tmp:
-            flag_value = 2 ** _high_bit(tmp)
-            if flag_value in flag._value2member_map_:
-                members.append(flag._value2member_map_[flag_value])
-                not_covered &= ~flag_value
-            tmp &= ~flag_value
-    if not members and value in flag._value2member_map_:
-        members.append(flag._value2member_map_[value])
-    members.sort(key=lambda m: m._value_, reverse=True)
-    if len(members) > 1 and members[0].value == value:
-        # we have the breakdown, don't need the value member itself
-        members.pop(0)
-    return members, not_covered
+def _power_of_two(value):
+    if value < 1:
+        return False
+    return value == 2 ** _high_bit(value)
diff --git a/Lib/re.py b/Lib/re.py
index bfb7b1ccd9346..a39ff047c26b2 100644
--- a/Lib/re.py
+++ b/Lib/re.py
@@ -142,7 +142,7 @@
 
 __version__ = "2.2.1"
 
-class RegexFlag(enum.IntFlag):
+class RegexFlag(enum.IntFlag, boundary=enum.KEEP):
     ASCII = A = sre_compile.SRE_FLAG_ASCII # assume ascii "locale"
     IGNORECASE = I = sre_compile.SRE_FLAG_IGNORECASE # ignore case
     LOCALE = L = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale
@@ -155,26 +155,17 @@ class RegexFlag(enum.IntFlag):
     DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation
 
     def __repr__(self):
-        if self._name_ is not None:
-            return f're.{self._name_}'
-        value = self._value_
-        members = []
-        negative = value < 0
-        if negative:
-            value = ~value
-        for m in self.__class__:
-            if value & m._value_:
-                value &= ~m._value_
-                members.append(f're.{m._name_}')
-        if value:
-            members.append(hex(value))
-        res = '|'.join(members)
-        if negative:
-            if len(members) > 1:
-                res = f'~({res})'
-            else:
-                res = f'~{res}'
+        res = ''
+        if self._name_:
+            member_names = self._name_.split('|')
+            constant = None
+            if member_names[-1].startswith('0x'):
+                constant = member_names.pop()
+            res = 're.' + '|re.'.join(member_names)
+            if constant:
+                res += '|%s' % constant
         return res
+
     __str__ = object.__str__
 
 globals().update(RegexFlag.__members__)
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index 3ea623e9c883c..daca2e3c83f27 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -1,4 +1,5 @@
 import enum
+import doctest
 import inspect
 import pydoc
 import sys
@@ -6,6 +7,7 @@
 import threading
 from collections import OrderedDict
 from enum import Enum, IntEnum, StrEnum, EnumMeta, Flag, IntFlag, unique, auto
+from enum import STRICT, CONFORM, EJECT, KEEP
 from io import StringIO
 from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
 from test import support
@@ -13,6 +15,13 @@
 from test.support import threading_helper
 from datetime import timedelta
 
+def load_tests(loader, tests, ignore):
+    tests.addTests(doctest.DocTestSuite(enum))
+    tests.addTests(doctest.DocFileSuite(
+            '../../Doc/library/enum.rst',
+            optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE,
+            ))
+    return tests
 
 # for pickle tests
 try:
@@ -2126,7 +2135,30 @@ class ThirdFailedStrEnum(StrEnum):
                 one = '1'
                 two = b'2', 'ascii', 9
 
-
+    def test_missing_value_error(self):
+        with self.assertRaisesRegex(TypeError, "_value_ not set in __new__"):
+            class Combined(str, Enum):
+                #
+                def __new__(cls, value, sequence):
+                    enum = str.__new__(cls, value)
+                    if '(' in value:
+                        fis_name, segment = value.split('(', 1)
+                        segment = segment.strip(' )')
+                    else:
+                        fis_name = value
+                        segment = None
+                    enum.fis_name = fis_name
+                    enum.segment = segment
+                    enum.sequence = sequence
+                    return enum
+                #
+                def __repr__(self):
+                    return "<%s.%s>" % (self.__class__.__name__, self._name_)
+                #
+                key_type      = 'An$(1,2)', 0
+                company_id    = 'An$(3,2)', 1
+                code          = 'An$(5,1)', 2
+                description   = 'Bn$',      3
 
     @unittest.skipUnless(
             sys.version_info[:2] == (3, 9),
@@ -2264,9 +2296,12 @@ class Open(Flag):
     class Color(Flag):
         BLACK = 0
         RED = 1
+        ROJO = 1
         GREEN = 2
         BLUE = 4
         PURPLE = RED|BLUE
+        WHITE = RED|GREEN|BLUE
+        BLANCO = RED|GREEN|BLUE
 
     def test_str(self):
         Perm = self.Perm
@@ -2275,12 +2310,12 @@ def test_str(self):
         self.assertEqual(str(Perm.X), 'Perm.X')
         self.assertEqual(str(Perm.R | Perm.W), 'Perm.R|W')
         self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X')
-        self.assertEqual(str(Perm(0)), 'Perm.0')
+        self.assertEqual(str(Perm(0)), 'Perm(0)')
         self.assertEqual(str(~Perm.R), 'Perm.W|X')
         self.assertEqual(str(~Perm.W), 'Perm.R|X')
         self.assertEqual(str(~Perm.X), 'Perm.R|W')
         self.assertEqual(str(~(Perm.R | Perm.W)), 'Perm.X')
-        self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm.0')
+        self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm(0)')
         self.assertEqual(str(Perm(~0)), 'Perm.R|W|X')
 
         Open = self.Open
@@ -2288,10 +2323,11 @@ def test_str(self):
         self.assertEqual(str(Open.WO), 'Open.WO')
         self.assertEqual(str(Open.AC), 'Open.AC')
         self.assertEqual(str(Open.RO | Open.CE), 'Open.CE')
-        self.assertEqual(str(Open.WO | Open.CE), 'Open.CE|WO')
-        self.assertEqual(str(~Open.RO), 'Open.CE|AC|RW|WO')
-        self.assertEqual(str(~Open.WO), 'Open.CE|RW')
+        self.assertEqual(str(Open.WO | Open.CE), 'Open.WO|CE')
+        self.assertEqual(str(~Open.RO), 'Open.WO|RW|CE')
+        self.assertEqual(str(~Open.WO), 'Open.RW|CE')
         self.assertEqual(str(~Open.AC), 'Open.CE')
+        self.assertEqual(str(~Open.CE), 'Open.AC')
         self.assertEqual(str(~(Open.RO | Open.CE)), 'Open.AC')
         self.assertEqual(str(~(Open.WO | Open.CE)), 'Open.RW')
 
@@ -2302,12 +2338,12 @@ def test_repr(self):
         self.assertEqual(repr(Perm.X), '<Perm.X: 1>')
         self.assertEqual(repr(Perm.R | Perm.W), '<Perm.R|W: 6>')
         self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '<Perm.R|W|X: 7>')
-        self.assertEqual(repr(Perm(0)), '<Perm.0: 0>')
+        self.assertEqual(repr(Perm(0)), '<Perm: 0>')
         self.assertEqual(repr(~Perm.R), '<Perm.W|X: 3>')
         self.assertEqual(repr(~Perm.W), '<Perm.R|X: 5>')
         self.assertEqual(repr(~Perm.X), '<Perm.R|W: 6>')
         self.assertEqual(repr(~(Perm.R | Perm.W)), '<Perm.X: 1>')
-        self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm.0: 0>')
+        self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm: 0>')
         self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: 7>')
 
         Open = self.Open
@@ -2315,10 +2351,11 @@ def test_repr(self):
         self.assertEqual(repr(Open.WO), '<Open.WO: 1>')
         self.assertEqual(repr(Open.AC), '<Open.AC: 3>')
         self.assertEqual(repr(Open.RO | Open.CE), '<Open.CE: 524288>')
-        self.assertEqual(repr(Open.WO | Open.CE), '<Open.CE|WO: 524289>')
-        self.assertEqual(repr(~Open.RO), '<Open.CE|AC|RW|WO: 524291>')
-        self.assertEqual(repr(~Open.WO), '<Open.CE|RW: 524290>')
+        self.assertEqual(repr(Open.WO | Open.CE), '<Open.WO|CE: 524289>')
+        self.assertEqual(repr(~Open.RO), '<Open.WO|RW|CE: 524291>')
+        self.assertEqual(repr(~Open.WO), '<Open.RW|CE: 524290>')
         self.assertEqual(repr(~Open.AC), '<Open.CE: 524288>')
+        self.assertEqual(repr(~Open.CE), '<Open.AC: 3>')
         self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC: 3>')
         self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>')
 
@@ -2394,6 +2431,46 @@ def test_bool(self):
         for f in Open:
             self.assertEqual(bool(f.value), bool(f))
 
+    def test_boundary(self):
+        self.assertIs(enum.Flag._boundary_, STRICT)
+        class Iron(Flag, boundary=STRICT):
+            ONE = 1
+            TWO = 2
+            EIGHT = 8
+        self.assertIs(Iron._boundary_, STRICT)
+        #
+        class Water(Flag, boundary=CONFORM):
+            ONE = 1
+            TWO = 2
+            EIGHT = 8
+        self.assertIs(Water._boundary_, CONFORM)
+        #
+        class Space(Flag, boundary=EJECT):
+            ONE = 1
+            TWO = 2
+            EIGHT = 8
+        self.assertIs(Space._boundary_, EJECT)
+        #
+        class Bizarre(Flag, boundary=KEEP):
+            b = 3
+            c = 4
+            d = 6
+        #
+        self.assertRaisesRegex(ValueError, 'invalid value: 7', Iron, 7)
+        self.assertIs(Water(7), Water.ONE|Water.TWO)
+        self.assertIs(Water(~9), Water.TWO)
+        self.assertEqual(Space(7), 7)
+        self.assertTrue(type(Space(7)) is int)
+        self.assertEqual(list(Bizarre), [Bizarre.c])
+        self.assertIs(Bizarre(3), Bizarre.b)
+        self.assertIs(Bizarre(6), Bizarre.d)
+
+    def test_iter(self):
+        Color = self.Color
+        Open = self.Open
+        self.assertEqual(list(Color), [Color.RED, Color.GREEN, Color.BLUE])
+        self.assertEqual(list(Open), [Open.WO, Open.RW, Open.CE])
+
     def test_programatic_function_string(self):
         Perm = Flag('Perm', 'R W X')
         lst = list(Perm)
@@ -2511,9 +2588,45 @@ def test_member_contains(self):
 
     def test_member_iter(self):
         Color = self.Color
-        self.assertEqual(list(Color.PURPLE), [Color.BLUE, Color.RED])
+        self.assertEqual(list(Color.BLACK), [])
+        self.assertEqual(list(Color.PURPLE), [Color.RED, Color.BLUE])
         self.assertEqual(list(Color.BLUE), [Color.BLUE])
         self.assertEqual(list(Color.GREEN), [Color.GREEN])
+        self.assertEqual(list(Color.WHITE), [Color.RED, Color.GREEN, Color.BLUE])
+        self.assertEqual(list(Color.WHITE), [Color.RED, Color.GREEN, Color.BLUE])
+
+    def test_member_length(self):
+        self.assertEqual(self.Color.__len__(self.Color.BLACK), 0)
+        self.assertEqual(self.Color.__len__(self.Color.GREEN), 1)
+        self.assertEqual(self.Color.__len__(self.Color.PURPLE), 2)
+        self.assertEqual(self.Color.__len__(self.Color.BLANCO), 3)
+
+    def test_number_reset_and_order_cleanup(self):
+        class Confused(Flag):
+            _order_ = 'ONE TWO FOUR DOS EIGHT SIXTEEN'
+            ONE = auto()
+            TWO = auto()
+            FOUR = auto()
+            DOS = 2
+            EIGHT = auto()
+            SIXTEEN = auto()
+        self.assertEqual(
+                list(Confused),
+                [Confused.ONE, Confused.TWO, Confused.FOUR, Confused.EIGHT, Confused.SIXTEEN])
+        self.assertIs(Confused.TWO, Confused.DOS)
+        self.assertEqual(Confused.DOS._value_, 2)
+        self.assertEqual(Confused.EIGHT._value_, 8)
+        self.assertEqual(Confused.SIXTEEN._value_, 16)
+
+    def test_aliases(self):
+        Color = self.Color
+        self.assertEqual(Color(1).name, 'RED')
+        self.assertEqual(Color['ROJO'].name, 'RED')
+        self.assertEqual(Color(7).name, 'WHITE')
+        self.assertEqual(Color['BLANCO'].name, 'WHITE')
+        self.assertIs(Color.BLANCO, Color.WHITE)
+        Open = self.Open
+        self.assertIs(Open['AC'], Open.AC)
 
     def test_auto_number(self):
         class Color(Flag):
@@ -2532,20 +2645,6 @@ class Color(Flag):
                 red = 'not an int'
                 blue = auto()
 
-    def test_cascading_failure(self):
-        class Bizarre(Flag):
-            c = 3
-            d = 4
-            f = 6
-        # Bizarre.c | Bizarre.d
-        name = "TestFlag.test_cascading_failure.<locals>.Bizarre"
-        self.assertRaisesRegex(ValueError, "5 is not a valid " + name, Bizarre, 5)
-        self.assertRaisesRegex(ValueError, "5 is not a valid " + name, Bizarre, 5)
-        self.assertRaisesRegex(ValueError, "2 is not a valid " + name, Bizarre, 2)
-        self.assertRaisesRegex(ValueError, "2 is not a valid " + name, Bizarre, 2)
-        self.assertRaisesRegex(ValueError, "1 is not a valid " + name, Bizarre, 1)
-        self.assertRaisesRegex(ValueError, "1 is not a valid " + name, Bizarre, 1)
-
     def test_duplicate_auto(self):
         class Dupes(Enum):
             first = primero = auto()
@@ -2554,11 +2653,11 @@ class Dupes(Enum):
         self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes))
 
     def test_bizarre(self):
-        class Bizarre(Flag):
-            b = 3
-            c = 4
-            d = 6
-        self.assertEqual(repr(Bizarre(7)), '<Bizarre.d|c|b: 7>')
+        with self.assertRaisesRegex(TypeError, "invalid Flag 'Bizarre' -- missing values: 1, 2"):
+            class Bizarre(Flag):
+                b = 3
+                c = 4
+                d = 6
 
     def test_multiple_mixin(self):
         class AllMixin:
@@ -2682,9 +2781,9 @@ class TestIntFlag(unittest.TestCase):
     """Tests of the IntFlags."""
 
     class Perm(IntFlag):
-        X = 1 << 0
-        W = 1 << 1
         R = 1 << 2
+        W = 1 << 1
+        X = 1 << 0
 
     class Open(IntFlag):
         RO = 0
@@ -2696,9 +2795,17 @@ class Open(IntFlag):
     class Color(IntFlag):
         BLACK = 0
         RED = 1
+        ROJO = 1
         GREEN = 2
         BLUE = 4
         PURPLE = RED|BLUE
+        WHITE = RED|GREEN|BLUE
+        BLANCO = RED|GREEN|BLUE
+
+    class Skip(IntFlag):
+        FIRST = 1
+        SECOND = 2
+        EIGHTH = 8
 
     def test_type(self):
         Perm = self.Perm
@@ -2723,31 +2830,35 @@ def test_str(self):
         self.assertEqual(str(Perm.X), 'Perm.X')
         self.assertEqual(str(Perm.R | Perm.W), 'Perm.R|W')
         self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X')
-        self.assertEqual(str(Perm.R | 8), 'Perm.8|R')
-        self.assertEqual(str(Perm(0)), 'Perm.0')
-        self.assertEqual(str(Perm(8)), 'Perm.8')
+        self.assertEqual(str(Perm.R | 8), '12')
+        self.assertEqual(str(Perm(0)), 'Perm(0)')
+        self.assertEqual(str(Perm(8)), '8')
         self.assertEqual(str(~Perm.R), 'Perm.W|X')
         self.assertEqual(str(~Perm.W), 'Perm.R|X')
         self.assertEqual(str(~Perm.X), 'Perm.R|W')
         self.assertEqual(str(~(Perm.R | Perm.W)), 'Perm.X')
-        self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm.-8')
-        self.assertEqual(str(~(Perm.R | 8)), 'Perm.W|X')
+        self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm(0)')
+        self.assertEqual(str(~(Perm.R | 8)), '-13')
         self.assertEqual(str(Perm(~0)), 'Perm.R|W|X')
-        self.assertEqual(str(Perm(~8)), 'Perm.R|W|X')
+        self.assertEqual(str(Perm(~8)), '-9')
 
         Open = self.Open
         self.assertEqual(str(Open.RO), 'Open.RO')
         self.assertEqual(str(Open.WO), 'Open.WO')
         self.assertEqual(str(Open.AC), 'Open.AC')
         self.assertEqual(str(Open.RO | Open.CE), 'Open.CE')
-        self.assertEqual(str(Open.WO | Open.CE), 'Open.CE|WO')
-        self.assertEqual(str(Open(4)), 'Open.4')
-        self.assertEqual(str(~Open.RO), 'Open.CE|AC|RW|WO')
-        self.assertEqual(str(~Open.WO), 'Open.CE|RW')
+        self.assertEqual(str(Open.WO | Open.CE), 'Open.WO|CE')
+        self.assertEqual(str(Open(4)), '4')
+        self.assertEqual(str(~Open.RO), 'Open.WO|RW|CE')
+        self.assertEqual(str(~Open.WO), 'Open.RW|CE')
         self.assertEqual(str(~Open.AC), 'Open.CE')
-        self.assertEqual(str(~(Open.RO | Open.CE)), 'Open.AC|RW|WO')
+        self.assertEqual(str(~Open.CE), 'Open.AC')
+        self.assertEqual(str(~(Open.RO | Open.CE)), 'Open.AC')
         self.assertEqual(str(~(Open.WO | Open.CE)), 'Open.RW')
-        self.assertEqual(str(Open(~4)), 'Open.CE|AC|RW|WO')
+        self.assertEqual(str(Open(~4)), '-5')
+
+        Skip = self.Skip
+        self.assertEqual(str(Skip(~4)), 'Skip.FIRST|SECOND|EIGHTH')
 
     def test_repr(self):
         Perm = self.Perm
@@ -2756,31 +2867,34 @@ def test_repr(self):
         self.assertEqual(repr(Perm.X), '<Perm.X: 1>')
         self.assertEqual(repr(Perm.R | Perm.W), '<Perm.R|W: 6>')
         self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '<Perm.R|W|X: 7>')
-        self.assertEqual(repr(Perm.R | 8), '<Perm.8|R: 12>')
-        self.assertEqual(repr(Perm(0)), '<Perm.0: 0>')
-        self.assertEqual(repr(Perm(8)), '<Perm.8: 8>')
-        self.assertEqual(repr(~Perm.R), '<Perm.W|X: -5>')
-        self.assertEqual(repr(~Perm.W), '<Perm.R|X: -3>')
-        self.assertEqual(repr(~Perm.X), '<Perm.R|W: -2>')
-        self.assertEqual(repr(~(Perm.R | Perm.W)), '<Perm.X: -7>')
-        self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm.-8: -8>')
-        self.assertEqual(repr(~(Perm.R | 8)), '<Perm.W|X: -13>')
-        self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: -1>')
-        self.assertEqual(repr(Perm(~8)), '<Perm.R|W|X: -9>')
+        self.assertEqual(repr(Perm.R | 8), '12')
+        self.assertEqual(repr(Perm(0)), '<Perm: 0>')
+        self.assertEqual(repr(Perm(8)), '8')
+        self.assertEqual(repr(~Perm.R), '<Perm.W|X: 3>')
+        self.assertEqual(repr(~Perm.W), '<Perm.R|X: 5>')
+        self.assertEqual(repr(~Perm.X), '<Perm.R|W: 6>')
+        self.assertEqual(repr(~(Perm.R | Perm.W)), '<Perm.X: 1>')
+        self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm: 0>')
+        self.assertEqual(repr(~(Perm.R | 8)), '-13')
+        self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: 7>')
+        self.assertEqual(repr(Perm(~8)), '-9')
 
         Open = self.Open
         self.assertEqual(repr(Open.RO), '<Open.RO: 0>')
         self.assertEqual(repr(Open.WO), '<Open.WO: 1>')
         self.assertEqual(repr(Open.AC), '<Open.AC: 3>')
         self.assertEqual(repr(Open.RO | Open.CE), '<Open.CE: 524288>')
-        self.assertEqual(repr(Open.WO | Open.CE), '<Open.CE|WO: 524289>')
-        self.assertEqual(repr(Open(4)), '<Open.4: 4>')
-        self.assertEqual(repr(~Open.RO), '<Open.CE|AC|RW|WO: -1>')
-        self.assertEqual(repr(~Open.WO), '<Open.CE|RW: -2>')
-        self.assertEqual(repr(~Open.AC), '<Open.CE: -4>')
-        self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC|RW|WO: -524289>')
-        self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: -524290>')
-        self.assertEqual(repr(Open(~4)), '<Open.CE|AC|RW|WO: -5>')
+        self.assertEqual(repr(Open.WO | Open.CE), '<Open.WO|CE: 524289>')
+        self.assertEqual(repr(Open(4)), '4')
+        self.assertEqual(repr(~Open.RO), '<Open.WO|RW|CE: 524291>')
+        self.assertEqual(repr(~Open.WO), '<Open.RW|CE: 524290>')
+        self.assertEqual(repr(~Open.AC), '<Open.CE: 524288>')
+        self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC: 3>')
+        self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>')
+        self.assertEqual(repr(Open(~4)), '-5')
+
+        Skip = self.Skip
+        self.assertEqual(repr(Skip(~4)), '<Skip.FIRST|SECOND|EIGHTH: 11>')
 
     def test_format(self):
         Perm = self.Perm
@@ -2863,8 +2977,7 @@ def test_invert(self):
         RWX = Perm.R | Perm.W | Perm.X
         values = list(Perm) + [RW, RX, WX, RWX, Perm(0)]
         for i in values:
-            self.assertEqual(~i, ~i.value)
-            self.assertEqual((~i).value, ~i.value)
+            self.assertEqual(~i, (~i).value)
             self.assertIs(type(~i), Perm)
             self.assertEqual(~~i, i)
         for i in Perm:
@@ -2873,6 +2986,46 @@ def test_invert(self):
         self.assertIs(Open.WO & ~Open.WO, Open.RO)
         self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE)
 
+    def test_boundary(self):
+        self.assertIs(enum.IntFlag._boundary_, EJECT)
+        class Iron(IntFlag, boundary=STRICT):
+            ONE = 1
+            TWO = 2
+            EIGHT = 8
+        self.assertIs(Iron._boundary_, STRICT)
+        #
+        class Water(IntFlag, boundary=CONFORM):
+            ONE = 1
+            TWO = 2
+            EIGHT = 8
+        self.assertIs(Water._boundary_, CONFORM)
+        #
+        class Space(IntFlag, boundary=EJECT):
+            ONE = 1
+            TWO = 2
+            EIGHT = 8
+        self.assertIs(Space._boundary_, EJECT)
+        #
+        class Bizarre(IntFlag, boundary=KEEP):
+            b = 3
+            c = 4
+            d = 6
+        #
+        self.assertRaisesRegex(ValueError, 'invalid value: 5', Iron, 5)
+        self.assertIs(Water(7), Water.ONE|Water.TWO)
+        self.assertIs(Water(~9), Water.TWO)
+        self.assertEqual(Space(7), 7)
+        self.assertTrue(type(Space(7)) is int)
+        self.assertEqual(list(Bizarre), [Bizarre.c])
+        self.assertIs(Bizarre(3), Bizarre.b)
+        self.assertIs(Bizarre(6), Bizarre.d)
+
+    def test_iter(self):
+        Color = self.Color
+        Open = self.Open
+        self.assertEqual(list(Color), [Color.RED, Color.GREEN, Color.BLUE])
+        self.assertEqual(list(Open), [Open.WO, Open.RW, Open.CE])
+
     def test_programatic_function_string(self):
         Perm = IntFlag('Perm', 'R W X')
         lst = list(Perm)
@@ -3014,9 +3167,27 @@ def test_member_contains(self):
 
     def test_member_iter(self):
         Color = self.Color
-        self.assertEqual(list(Color.PURPLE), [Color.BLUE, Color.RED])
+        self.assertEqual(list(Color.BLACK), [])
+        self.assertEqual(list(Color.PURPLE), [Color.RED, Color.BLUE])
         self.assertEqual(list(Color.BLUE), [Color.BLUE])
         self.assertEqual(list(Color.GREEN), [Color.GREEN])
+        self.assertEqual(list(Color.WHITE), [Color.RED, Color.GREEN, Color.BLUE])
+
+    def test_member_length(self):
+        self.assertEqual(self.Color.__len__(self.Color.BLACK), 0)
+        self.assertEqual(self.Color.__len__(self.Color.GREEN), 1)
+        self.assertEqual(self.Color.__len__(self.Color.PURPLE), 2)
+        self.assertEqual(self.Color.__len__(self.Color.BLANCO), 3)
+
+    def test_aliases(self):
+        Color = self.Color
+        self.assertEqual(Color(1).name, 'RED')
+        self.assertEqual(Color['ROJO'].name, 'RED')
+        self.assertEqual(Color(7).name, 'WHITE')
+        self.assertEqual(Color['BLANCO'].name, 'WHITE')
+        self.assertIs(Color.BLANCO, Color.WHITE)
+        Open = self.Open
+        self.assertIs(Open['AC'], Open.AC)
 
     def test_bool(self):
         Perm = self.Perm
@@ -3026,6 +3197,13 @@ def test_bool(self):
         for f in Open:
             self.assertEqual(bool(f.value), bool(f))
 
+    def test_bizarre(self):
+        with self.assertRaisesRegex(TypeError, "invalid Flag 'Bizarre' -- missing values: 1, 2"):
+            class Bizarre(IntFlag):
+                b = 3
+                c = 4
+                d = 6
+
     def test_multiple_mixin(self):
         class AllMixin:
             @classproperty
@@ -3176,7 +3354,7 @@ class Sillier(IntEnum):
 Help on class Color in module %s:
 
 class Color(enum.Enum)
- |  Color(value, names=None, *, module=None, qualname=None, type=None, start=1)
+ |  Color(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)
  |\x20\x20
  |  An enumeration.
  |\x20\x20
@@ -3328,7 +3506,7 @@ def test_inspect_classify_class_attrs(self):
 
 class MiscTestCase(unittest.TestCase):
     def test__all__(self):
-        support.check__all__(self, enum)
+        support.check__all__(self, enum, not_exported={'bin'})
 
 
 # These are unordered here on purpose to ensure that declaration order
diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py
index c1d02cfaf0dcb..bd689582523c3 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -2176,11 +2176,13 @@ def test_flags_repr(self):
                          "re.IGNORECASE|re.DOTALL|re.VERBOSE")
         self.assertEqual(repr(re.I|re.S|re.X|(1<<20)),
                          "re.IGNORECASE|re.DOTALL|re.VERBOSE|0x100000")
-        self.assertEqual(repr(~re.I), "~re.IGNORECASE")
+        self.assertEqual(
+                repr(~re.I),
+                "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.DOTALL|re.VERBOSE|re.TEMPLATE|re.DEBUG")
         self.assertEqual(repr(~(re.I|re.S|re.X)),
-                         "~(re.IGNORECASE|re.DOTALL|re.VERBOSE)")
+                         "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG")
         self.assertEqual(repr(~(re.I|re.S|re.X|(1<<20))),
-                         "~(re.IGNORECASE|re.DOTALL|re.VERBOSE|0x100000)")
+                         "re.ASCII|re.LOCALE|re.UNICODE|re.MULTILINE|re.TEMPLATE|re.DEBUG|0xffe00")
 
 
 class ImplementationTest(unittest.TestCase):
diff --git a/Lib/types.py b/Lib/types.py
index 532f4806fc022..c509b242d5d8f 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -155,7 +155,12 @@ class DynamicClassAttribute:
     class's __getattr__ method; this is done by raising AttributeError.
 
     This allows one to have properties active on an instance, and have virtual
-    attributes on the class with the same name (see Enum for an example).
+    attributes on the class with the same name.  (Enum used this between Python
+    versions 3.4 - 3.9 .)
+
+    Subclass from this to use a different method of accessing virtual atributes
+    and still be treated properly by the inspect module. (Enum uses this since
+    Python 3.10 .)
 
     """
     def __init__(self, fget=None, fset=None, fdel=None, doc=None):
diff --git a/Misc/ACKS b/Misc/ACKS
index 136266965a869..29ef9864f9827 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -141,6 +141,7 @@ Stefan Behnel
 Reimer Behrends
 Ben Bell
 Thomas Bellman
+John Belmonte
 Alexander “Саша” Belopolsky
 Eli Bendersky
 Nikhil Benesch
diff --git a/Misc/NEWS.d/next/Library/2021-01-14-15-07-16.bpo-38250.1fvhOk.rst b/Misc/NEWS.d/next/Library/2021-01-14-15-07-16.bpo-38250.1fvhOk.rst
new file mode 100644
index 0000000000000..e5a72468370fb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-01-14-15-07-16.bpo-38250.1fvhOk.rst
@@ -0,0 +1,5 @@
+[Enum] Flags consisting of a single bit are now considered canonical, and
+will be the only flags returned from listing and iterating over a Flag class
+or a Flag member.  Multi-bit flags are considered aliases; they will be
+returned from lookups and operations that result in their value.
+Iteration for both Flag and Flag members is in definition order.



More information about the Python-checkins mailing list