aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Doc/library/enum.rst162
-rw-r--r--Lib/enum.py502
-rw-r--r--Lib/re.py31
-rw-r--r--Lib/test/test_enum.py316
-rw-r--r--Lib/test/test_re.py8
-rw-r--r--Lib/types.py7
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Library/2021-01-14-15-07-16.bpo-38250.1fvhOk.rst5
8 files changed, 747 insertions, 285 deletions
diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
index c532e2caec4..b27c5527c7f 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 8ca385420da..d4b11521ab2 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 @@ __all__ = [
'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 _make_class_unpicklable(obj):
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 @@ class property(DynamicClassAttribute):
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 @@ class _proto_member:
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 @@ class _proto_member:
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 @@ class _proto_member:
# 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 @@ class _EnumDict(dict):
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 @@ class _EnumDict(dict):
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 @@ class _EnumDict(dict):
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 @@ class EnumMeta(type):
)
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 @@ class EnumMeta(type):
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 @@ class EnumMeta(type):
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 @@ class EnumMeta(type):
"""
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 @@ class EnumMeta(type):
qualname=qualname,
type=type,
start=start,
+ boundary=boundary,
)
def __contains__(cls, member):
@@ -539,7 +685,7 @@ class EnumMeta(type):
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 @@ class EnumMeta(type):
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 @@ class EnumMeta(type):
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 @@ class Enum(metaclass=EnumMeta):
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 @@ class Enum(metaclass=EnumMeta):
'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 @@ class StrEnum(str, Enum):
# 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 @@ class StrEnum(str, Enum):
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 @@ class Flag(Enum):
"""
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 @@ class Flag(Enum):
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 @@ class Flag(Enum):
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 bfb7b1ccd93..a39ff047c26 100644
--- a/Lib/re.py
+++ b/Lib/re.py
@@ -142,7 +142,7 @@ __all__ = [
__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 3ea623e9c88..daca2e3c83f 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 unittest
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 ALWAYS_EQ
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 TestEnum(unittest.TestCase):
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 TestFlag(unittest.TestCase):
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 @@ class TestFlag(unittest.TestCase):
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 @@ class TestFlag(unittest.TestCase):
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 @@ class TestFlag(unittest.TestCase):
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 @@ class TestFlag(unittest.TestCase):
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 @@ class TestFlag(unittest.TestCase):
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 @@ class TestFlag(unittest.TestCase):
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 TestFlag(unittest.TestCase):
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 TestFlag(unittest.TestCase):
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 TestIntFlag(unittest.TestCase):
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 @@ class TestIntFlag(unittest.TestCase):
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 @@ class TestIntFlag(unittest.TestCase):
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 @@ class TestIntFlag(unittest.TestCase):
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 @@ class TestIntFlag(unittest.TestCase):
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 @@ class TestIntFlag(unittest.TestCase):
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 @@ class TestIntFlag(unittest.TestCase):
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 @@ expected_help_output_with_docs = """\
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 @@ class TestStdLib(unittest.TestCase):
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 c1d02cfaf0d..bd689582523 100644
--- a/Lib/test/test_re.py
+++ b/Lib/test/test_re.py
@@ -2176,11 +2176,13 @@ class PatternReprTests(unittest.TestCase):
"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 532f4806fc0..c509b242d5d 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 136266965a8..29ef9864f98 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 00000000000..e5a72468370
--- /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.