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().
This commit is contained in:
Ethan Furman 2021-01-25 14:26:19 -08:00 committed by GitHub
parent 9852cb3811
commit 7aaeb2a3d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 750 additions and 288 deletions

View file

@ -197,7 +197,7 @@ Having two enum members with the same name is invalid::
... ...
Traceback (most recent call last): 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 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 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): Traceback (most recent call last):
... ...
TypeError: Cannot extend enumerations TypeError: MoreColor: cannot extend enumeration 'Color'
But this is allowed:: 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 to each other. :class:`StrEnum` exists to help avoid the problem of getting
an incorrect member:: an incorrect member::
>>> from enum import StrEnum
>>> class Directions(StrEnum): >>> class Directions(StrEnum):
... NORTH = 'north', # notice the trailing comma ... NORTH = 'north', # notice the trailing comma
... SOUTH = 'south' ... SOUTH = 'south'
@ -638,12 +639,22 @@ IntFlag
The next variation of :class:`Enum` provided, :class:`IntFlag`, is also based The next variation of :class:`Enum` provided, :class:`IntFlag`, is also based
on :class:`int`. The difference being :class:`IntFlag` members can be combined on :class:`int`. The difference being :class:`IntFlag` members can be combined
using the bitwise operators (&, \|, ^, ~) and the result is still an 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 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 used.
operations will lose the :class:`IntFlag` membership.
.. 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 .. versionadded:: 3.6
.. versionchanged:: 3.10
Sample :class:`IntFlag` class:: Sample :class:`IntFlag` class::
@ -671,21 +682,41 @@ It is also possible to name the combinations::
>>> Perm.RWX >>> Perm.RWX
<Perm.RWX: 7> <Perm.RWX: 7>
>>> ~Perm.RWX >>> ~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 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`:: if no flags are set (the value is 0), its boolean evaluation is :data:`False`::
>>> Perm.R & Perm.X >>> Perm.R & Perm.X
<Perm.0: 0> <Perm: 0>
>>> bool(Perm.R & Perm.X) >>> bool(Perm.R & Perm.X)
False False
Because :class:`IntFlag` members are also subclasses of :class:`int` they can 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.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:: :class:`IntFlag` members can also be iterated over::
@ -717,7 +748,7 @@ flags being set, the boolean evaluation is :data:`False`::
... GREEN = auto() ... GREEN = auto()
... ...
>>> Color.RED & Color.GREEN >>> Color.RED & Color.GREEN
<Color.0: 0> <Color: 0>
>>> bool(Color.RED & Color.GREEN) >>> bool(Color.RED & Color.GREEN)
False False
@ -751,7 +782,7 @@ value::
>>> purple = Color.RED | Color.BLUE >>> purple = Color.RED | Color.BLUE
>>> list(purple) >>> list(purple)
[<Color.BLUE: 2>, <Color.RED: 1>] [<Color.RED: 1>, <Color.BLUE: 2>]
.. versionadded:: 3.10 .. versionadded:: 3.10
@ -953,7 +984,7 @@ to handle any extra arguments::
... BLEACHED_CORAL = () # New color, no Pantone code yet! ... BLEACHED_CORAL = () # New color, no Pantone code yet!
... ...
>>> Swatch.SEA_GREEN >>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2> <Swatch.SEA_GREEN>
>>> Swatch.SEA_GREEN.pantone >>> Swatch.SEA_GREEN.pantone
'1246' '1246'
>>> Swatch.BLEACHED_CORAL.pantone >>> Swatch.BLEACHED_CORAL.pantone
@ -1144,6 +1175,14 @@ Supported ``_sunder_`` names
:class:`auto` to get an appropriate value for an enum member; may be :class:`auto` to get an appropriate value for an enum member; may be
overridden 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.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
.. versionadded:: 3.7 ``_ignore_`` .. versionadded:: 3.7 ``_ignore_``
@ -1159,7 +1198,9 @@ and raise an error if the two do not match::
... ...
Traceback (most recent call last): 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:: .. 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 :class:`Enum` members are instances of their :class:`Enum` class, and are
normally accessed as ``EnumClass.member``. Under certain circumstances they normally accessed as ``EnumClass.member``. In Python versions ``3.5`` to
can also be accessed as ``EnumClass.member.member``, but you should never do ``3.9`` you could access members from other members -- this practice was
this as that lookup may fail or, worse, return something besides the discouraged, and in ``3.10`` :class:`Enum` has returned to not allowing it::
:class:`Enum` member you are looking for (this is another good reason to use
all-uppercase names for members)::
>>> class FieldTypes(Enum): >>> class FieldTypes(Enum):
... name = 0 ... name = 0
@ -1191,11 +1230,12 @@ all-uppercase names for members)::
... size = 2 ... size = 2
... ...
>>> FieldTypes.value.size >>> FieldTypes.value.size
<FieldTypes.size: 2> Traceback (most recent call last):
>>> FieldTypes.size.value ...
2 AttributeError: FieldTypes: no attribute 'size'
.. versionchanged:: 3.5 .. versionchanged:: 3.5
.. versionchanged:: 3.10
Creating members that are mixed with other data types Creating members that are mixed with other data types
@ -1237,14 +1277,14 @@ but not of the class::
>>> dir(Planet) >>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__'] ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH) >>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value'] ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
Combining members of ``Flag`` Combining members of ``Flag``
""""""""""""""""""""""""""""" """""""""""""""""""""""""""""
If a combination of Flag members is not named, the :func:`repr` will include Iterating over a combination of Flag members will only return the members that
all named flags and all named combinations of flags that are in the value:: are comprised of a single bit::
>>> class Color(Flag): >>> class Color(Flag):
... RED = auto() ... RED = auto()
@ -1254,10 +1294,10 @@ all named flags and all named combinations of flags that are in the value::
... YELLOW = RED | GREEN ... YELLOW = RED | GREEN
... CYAN = GREEN | BLUE ... CYAN = GREEN | BLUE
... ...
>>> Color(3) # named combination >>> Color(3)
<Color.YELLOW: 3> <Color.YELLOW: 3>
>>> Color(7) # not named combination >>> Color(7)
<Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7> <Color.RED|GREEN|BLUE: 7>
``StrEnum`` and :meth:`str.__str__` ``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 :meth:`StrEnum.__str__` will be the same as :meth:`str.__str__` so that
``str(StrEnum.member) == StrEnum.member`` is true. ``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).

View file

@ -1,6 +1,6 @@
import sys import sys
from types import MappingProxyType, DynamicClassAttribute from types import MappingProxyType, DynamicClassAttribute
from builtins import property as _bltin_property from builtins import property as _bltin_property, bin as _bltin_bin
__all__ = [ __all__ = [
@ -8,9 +8,15 @@ __all__ = [
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag',
'auto', 'unique', 'auto', 'unique',
'property', '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): def _is_descriptor(obj):
""" """
Returns True if obj is a descriptor, False otherwise. Returns True if obj is a descriptor, False otherwise.
@ -56,6 +62,15 @@ def _is_private(cls_name, name):
else: else:
return False 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): def _make_class_unpicklable(obj):
""" """
Make the given obj un-picklable. 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, '__reduce_ex__', _break_on_call_reduce)
setattr(obj, '__module__', '<unknown>') 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() _auto_null = object()
class auto: class auto:
""" """
@ -92,22 +138,30 @@ class property(DynamicClassAttribute):
try: try:
return ownerclass._member_map_[self.name] return ownerclass._member_map_[self.name]
except KeyError: except KeyError:
raise AttributeError('%r not found in %r' % (self.name, ownerclass.__name__)) raise AttributeError(
'%s: no attribute %r' % (ownerclass.__name__, self.name)
)
else: else:
if self.fget is None: 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: else:
return self.fget(instance) return self.fget(instance)
def __set__(self, instance, value): def __set__(self, instance, value):
if self.fset is None: 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: else:
return self.fset(instance, value) return self.fset(instance, value)
def __delete__(self, instance): def __delete__(self, instance):
if self.fdel is None: 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: else:
return self.fdel(instance) return self.fdel(instance)
@ -148,11 +202,17 @@ class _proto_member:
if enum_class._member_type_ is object: if enum_class._member_type_ is object:
enum_member._value_ = value enum_member._value_ = value
else: 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_ value = enum_member._value_
enum_member._name_ = member_name enum_member._name_ = member_name
enum_member.__objclass__ = enum_class enum_member.__objclass__ = enum_class
enum_member.__init__(*args) enum_member.__init__(*args)
enum_member._sort_order_ = len(enum_class._member_names_)
# If another member with the same value was already defined, the # If another member with the same value was already defined, the
# new member becomes an alias to the existing one. # new member becomes an alias to the existing one.
for name, canonical_member in enum_class._member_map_.items(): for name, canonical_member in enum_class._member_map_.items():
@ -160,8 +220,21 @@ class _proto_member:
enum_member = canonical_member enum_member = canonical_member
break break
else: else:
# no other instances found, record this member in _member_names_ # this could still be an alias if the value is multi-bit and the
enum_class._member_names_.append(member_name) # 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_ # get redirect in place before adding to _member_map_
# but check for other instances in parent classes first # but check for other instances in parent classes first
need_override = False need_override = False
@ -193,7 +266,7 @@ class _proto_member:
# This may fail if value is not hashable. We can't add the value # 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 # to the map, and by-value lookups for this value will be
# linear. # linear.
enum_class._value2member_map_[value] = enum_member enum_class._value2member_map_.setdefault(value, enum_member)
except TypeError: except TypeError:
pass pass
@ -228,6 +301,7 @@ class _EnumDict(dict):
if key not in ( if key not in (
'_order_', '_create_pseudo_member_', '_order_', '_create_pseudo_member_',
'_generate_next_value_', '_missing_', '_ignore_', '_generate_next_value_', '_missing_', '_ignore_',
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_',
): ):
raise ValueError( raise ValueError(
'_sunder_ names, such as %r, are reserved for future Enum use' '_sunder_ names, such as %r, are reserved for future Enum use'
@ -265,10 +339,7 @@ class _EnumDict(dict):
if isinstance(value, auto): if isinstance(value, auto):
if value.value == _auto_null: if value.value == _auto_null:
value.value = self._generate_next_value( value.value = self._generate_next_value(
key, key, 1, len(self._member_names), self._last_values[:],
1,
len(self._member_names),
self._last_values[:],
) )
self._auto_called = True self._auto_called = True
value = value.value value = value.value
@ -287,15 +358,11 @@ class _EnumDict(dict):
self[name] = value 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): class EnumMeta(type):
""" """
Metaclass for Enum Metaclass for Enum
""" """
@classmethod @classmethod
def __prepare__(metacls, cls, bases, **kwds): def __prepare__(metacls, cls, bases, **kwds):
# check that previous enum members do not exist # check that previous enum members do not exist
@ -311,7 +378,7 @@ class EnumMeta(type):
) )
return enum_dict 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 # 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 # cannot be mixed with other types (int, float, etc.) if it has an
# inherited __new__ unless a new __new__ is defined (or the resulting # inherited __new__ unless a new __new__ is defined (or the resulting
@ -346,15 +413,29 @@ class EnumMeta(type):
classdict['_use_args_'] = use_args classdict['_use_args_'] = use_args
# #
# convert future enum members into temporary _proto_members # 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: 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_names_'] = []
classdict['_member_map_'] = {} classdict['_member_map_'] = {}
classdict['_value2member_map_'] = {} classdict['_value2member_map_'] = {}
classdict['_member_type_'] = member_type 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 # 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 # to pickle itself, pickle.dumps will succeed but pickle.loads will
# fail. Rather than have the error show up later and possibly far # fail. Rather than have the error show up later and possibly far
@ -408,11 +489,75 @@ class EnumMeta(type):
enum_class.__new__ = Enum.__new__ enum_class.__new__ = Enum.__new__
# #
# py3 support for definition order (helps keep py2/py3 code in sync) # 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 _order_ is not None:
if isinstance(_order_, str): if isinstance(_order_, str):
_order_ = _order_.replace(',', ' ').split() _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_: 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 return enum_class
@ -422,7 +567,7 @@ class EnumMeta(type):
""" """
return True 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. Either returns an existing member, or creates a new enum class.
@ -457,6 +602,7 @@ class EnumMeta(type):
qualname=qualname, qualname=qualname,
type=type, type=type,
start=start, start=start,
boundary=boundary,
) )
def __contains__(cls, member): def __contains__(cls, member):
@ -539,7 +685,7 @@ class EnumMeta(type):
raise AttributeError('Cannot reassign members.') raise AttributeError('Cannot reassign members.')
super().__setattr__(name, value) 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. Convenience method to create a new Enum class.
@ -589,9 +735,9 @@ class EnumMeta(type):
if qualname is not None: if qualname is not None:
classdict['__qualname__'] = qualname 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 Create a new Enum subclass that replaces a collection of global constants
""" """
@ -618,7 +764,7 @@ class EnumMeta(type):
except TypeError: except TypeError:
# unless some values aren't comparable, in which case sort by name # unless some values aren't comparable, in which case sort by name
members.sort(key=lambda t: t[0]) 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 cls.__reduce_ex__ = _reduce_ex_by_name
module_globals.update(cls.__members__) module_globals.update(cls.__members__)
module_globals[name] = cls module_globals[name] = cls
@ -733,6 +879,7 @@ class Enum(metaclass=EnumMeta):
Derive from this class to define new enumerations. Derive from this class to define new enumerations.
""" """
def __new__(cls, value): def __new__(cls, value):
# all enum instances are actually created during class construction # all enum instances are actually created during class construction
# without calling this method; this method is called by the metaclass' # without calling this method; this method is called by the metaclass'
@ -761,6 +908,11 @@ class Enum(metaclass=EnumMeta):
result = None result = None
if isinstance(result, cls): if isinstance(result, cls):
return result return result
elif (
Flag is not None and issubclass(cls, Flag)
and cls._boundary_ is EJECT and isinstance(result, int)
):
return result
else: else:
ve_exc = ValueError("%r is not a valid %s" % (value, cls.__qualname__)) ve_exc = ValueError("%r is not a valid %s" % (value, cls.__qualname__))
if result is None and exc is None: 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' 'error in %s._missing_: returned %r instead of None or a valid member'
% (cls.__name__, result) % (cls.__name__, result)
) )
exc.__context__ = ve_exc if not isinstance(exc, ValueError):
exc.__context__ = ve_exc
raise exc raise exc
def _generate_next_value_(name, start, count, last_values): def _generate_next_value_(name, start, count, last_values):
@ -875,14 +1028,14 @@ class StrEnum(str, Enum):
# it must be a string # it must be a string
if not isinstance(values[0], str): if not isinstance(values[0], str):
raise TypeError('%r is not a string' % (values[0], )) raise TypeError('%r is not a string' % (values[0], ))
if len(values) > 1: if len(values) >= 2:
# check that encoding argument is a string # check that encoding argument is a string
if not isinstance(values[1], str): if not isinstance(values[1], str):
raise TypeError('encoding must be a string, not %r' % (values[1], )) raise TypeError('encoding must be a string, not %r' % (values[1], ))
if len(values) > 2: if len(values) == 3:
# check that errors argument is a string # check that errors argument is a string
if not isinstance(values[2], str): if not isinstance(values[2], str):
raise TypeError('errors must be a string, not %r' % (values[2], )) raise TypeError('errors must be a string, not %r' % (values[2]))
value = str(*values) value = str(*values)
member = str.__new__(cls, value) member = str.__new__(cls, value)
member._value_ = value member._value_ = value
@ -900,7 +1053,22 @@ class StrEnum(str, Enum):
def _reduce_ex_by_name(self, proto): def _reduce_ex_by_name(self, proto):
return self.name 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 Support for flags
""" """
@ -916,45 +1084,108 @@ class Flag(Enum):
""" """
if not count: if not count:
return start if start is not None else 1 return start if start is not None else 1
for last_value in reversed(last_values): last_value = max(last_values)
try: try:
high_bit = _high_bit(last_value) high_bit = _high_bit(last_value)
break except Exception:
except Exception: raise TypeError('Invalid Flag value: %r' % last_value) from None
raise TypeError('Invalid Flag value: %r' % last_value) from None
return 2 ** (high_bit+1) return 2 ** (high_bit+1)
@classmethod
def _iter_member_by_value_(cls, value):
"""
Extract all members from the value in definition (i.e. increasing value) order.
"""
for val in _iter_bits_lsb(value & cls._flag_mask_):
yield cls._value2member_map_.get(val)
_iter_member_ = _iter_member_by_value_
@classmethod
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 @classmethod
def _missing_(cls, value): def _missing_(cls, value):
"""
Returns member (possibly creating it) if one can be found for value.
"""
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
@classmethod
def _create_pseudo_member_(cls, value):
""" """
Create a composite member iff value contains only members. Create a composite member iff value contains only members.
""" """
pseudo_member = cls._value2member_map_.get(value, None) if not isinstance(value, int):
if pseudo_member is None: raise ValueError(
# verify all bits are accounted for "%r is not a valid %s" % (value, cls.__qualname__)
_, extra_flags = _decompose(cls, value) )
if extra_flags: # check boundaries
raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) # - 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 # construct a singleton enum pseudo-member
pseudo_member = object.__new__(cls) 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 pseudo_member._value_ = value
# use setdefault in case another thread already created a composite if member_value:
# with this 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) 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 return pseudo_member
def __contains__(self, other): def __contains__(self, other):
@ -965,38 +1196,33 @@ class Flag(Enum):
raise TypeError( raise TypeError(
"unsupported operand type(s) for 'in': '%s' and '%s'" % ( "unsupported operand type(s) for 'in': '%s' and '%s'" % (
type(other).__qualname__, self.__class__.__qualname__)) type(other).__qualname__, self.__class__.__qualname__))
if other._value_ == 0 or self._value_ == 0:
return False
return other._value_ & self._value_ == other._value_ return other._value_ & self._value_ == other._value_
def __iter__(self): def __iter__(self):
""" """
Returns flags in decreasing value order. Returns flags in definition order.
""" """
members, extra_flags = _decompose(self.__class__, self.value) yield from self._iter_member_(self._value_)
return (m for m in members if m._value_ != 0)
def __len__(self):
return self._value_.bit_count()
def __repr__(self): def __repr__(self):
cls = self.__class__ cls = self.__class__
if self._name_ is not None: if self._name_ is not None:
return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_) return '<%s.%s: %r>' % (cls.__name__, self._name_, self._value_)
members, uncovered = _decompose(cls, self._value_) else:
return '<%s.%s: %r>' % ( # only zero is unnamed by default
cls.__name__, return '<%s: %r>' % (cls.__name__, self._value_)
'|'.join([str(m._name_ or m._value_) for m in members]),
self._value_,
)
def __str__(self): def __str__(self):
cls = self.__class__ cls = self.__class__
if self._name_ is not None: if self._name_ is not None:
return '%s.%s' % (cls.__name__, self._name_) 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: else:
return '%s.%s' % ( return '%s(%s)' % (cls.__name__, self._value_)
cls.__name__,
'|'.join([str(m._name_ or m._value_) for m in members]),
)
def __bool__(self): def __bool__(self):
return bool(self._value_) return bool(self._value_)
@ -1017,86 +1243,56 @@ class Flag(Enum):
return self.__class__(self._value_ ^ other._value_) return self.__class__(self._value_ ^ other._value_)
def __invert__(self): def __invert__(self):
members, uncovered = _decompose(self.__class__, self._value_) if self._inverted_ is None:
inverted = self.__class__(0) if self._boundary_ is KEEP:
for m in self.__class__: # use all bits
if m not in members and not (m._value_ & self._value_): self._inverted_ = self.__class__(~self._value_)
inverted = inverted | m else:
return self.__class__(inverted) # 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 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): 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 return NotImplemented
result = self.__class__(self._value_ | self.__class__(other)._value_) value = self._value_
return result return self.__class__(value | other)
def __and__(self, 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 NotImplemented
return self.__class__(self._value_ & self.__class__(other)._value_) value = self._value_
return self.__class__(value & other)
def __xor__(self, 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 NotImplemented
return self.__class__(self._value_ ^ self.__class__(other)._value_) value = self._value_
return self.__class__(value ^ other)
__ror__ = __or__ __ror__ = __or__
__rand__ = __and__ __rand__ = __and__
__rxor__ = __xor__ __rxor__ = __xor__
__invert__ = Flag.__invert__
def __invert__(self):
result = self.__class__(~self._value_)
return result
def _high_bit(value): def _high_bit(value):
""" """
@ -1119,31 +1315,7 @@ def unique(enumeration):
(enumeration, alias_details)) (enumeration, alias_details))
return enumeration return enumeration
def _decompose(flag, value): def _power_of_two(value):
""" if value < 1:
Extract all members from the value. return False
""" return value == 2 ** _high_bit(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

View file

@ -142,7 +142,7 @@ __all__ = [
__version__ = "2.2.1" __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" ASCII = A = sre_compile.SRE_FLAG_ASCII # assume ascii "locale"
IGNORECASE = I = sre_compile.SRE_FLAG_IGNORECASE # ignore case IGNORECASE = I = sre_compile.SRE_FLAG_IGNORECASE # ignore case
LOCALE = L = sre_compile.SRE_FLAG_LOCALE # assume current 8-bit locale 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 DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation
def __repr__(self): def __repr__(self):
if self._name_ is not None: res = ''
return f're.{self._name_}' if self._name_:
value = self._value_ member_names = self._name_.split('|')
members = [] constant = None
negative = value < 0 if member_names[-1].startswith('0x'):
if negative: constant = member_names.pop()
value = ~value res = 're.' + '|re.'.join(member_names)
for m in self.__class__: if constant:
if value & m._value_: res += '|%s' % constant
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}'
return res return res
__str__ = object.__str__ __str__ = object.__str__
globals().update(RegexFlag.__members__) globals().update(RegexFlag.__members__)

View file

@ -1,4 +1,5 @@
import enum import enum
import doctest
import inspect import inspect
import pydoc import pydoc
import sys import sys
@ -6,6 +7,7 @@ import unittest
import threading import threading
from collections import OrderedDict from collections import OrderedDict
from enum import Enum, IntEnum, StrEnum, EnumMeta, Flag, IntFlag, unique, auto from enum import Enum, IntEnum, StrEnum, EnumMeta, Flag, IntFlag, unique, auto
from enum import STRICT, CONFORM, EJECT, KEEP
from io import StringIO from io import StringIO
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
from test import support from test import support
@ -13,6 +15,13 @@ from test.support import ALWAYS_EQ
from test.support import threading_helper from test.support import threading_helper
from datetime import timedelta 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 # for pickle tests
try: try:
@ -2126,7 +2135,30 @@ class TestEnum(unittest.TestCase):
one = '1' one = '1'
two = b'2', 'ascii', 9 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( @unittest.skipUnless(
sys.version_info[:2] == (3, 9), sys.version_info[:2] == (3, 9),
@ -2264,9 +2296,12 @@ class TestFlag(unittest.TestCase):
class Color(Flag): class Color(Flag):
BLACK = 0 BLACK = 0
RED = 1 RED = 1
ROJO = 1
GREEN = 2 GREEN = 2
BLUE = 4 BLUE = 4
PURPLE = RED|BLUE PURPLE = RED|BLUE
WHITE = RED|GREEN|BLUE
BLANCO = RED|GREEN|BLUE
def test_str(self): def test_str(self):
Perm = self.Perm Perm = self.Perm
@ -2275,12 +2310,12 @@ class TestFlag(unittest.TestCase):
self.assertEqual(str(Perm.X), 'Perm.X') 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.R|W')
self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X') 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.R), 'Perm.W|X')
self.assertEqual(str(~Perm.W), 'Perm.R|X') self.assertEqual(str(~Perm.W), 'Perm.R|X')
self.assertEqual(str(~Perm.X), 'Perm.R|W') 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')
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') self.assertEqual(str(Perm(~0)), 'Perm.R|W|X')
Open = self.Open Open = self.Open
@ -2288,10 +2323,11 @@ class TestFlag(unittest.TestCase):
self.assertEqual(str(Open.WO), 'Open.WO') self.assertEqual(str(Open.WO), 'Open.WO')
self.assertEqual(str(Open.AC), 'Open.AC') self.assertEqual(str(Open.AC), 'Open.AC')
self.assertEqual(str(Open.RO | Open.CE), 'Open.CE') self.assertEqual(str(Open.RO | Open.CE), 'Open.CE')
self.assertEqual(str(Open.WO | Open.CE), 'Open.CE|WO') self.assertEqual(str(Open.WO | Open.CE), 'Open.WO|CE')
self.assertEqual(str(~Open.RO), 'Open.CE|AC|RW|WO') self.assertEqual(str(~Open.RO), 'Open.WO|RW|CE')
self.assertEqual(str(~Open.WO), 'Open.CE|RW') self.assertEqual(str(~Open.WO), 'Open.RW|CE')
self.assertEqual(str(~Open.AC), 'Open.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.RO | Open.CE)), 'Open.AC')
self.assertEqual(str(~(Open.WO | Open.CE)), 'Open.RW') 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.X), '<Perm.X: 1>')
self.assertEqual(repr(Perm.R | Perm.W), '<Perm.R|W: 6>') 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 | 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.R), '<Perm.W|X: 3>')
self.assertEqual(repr(~Perm.W), '<Perm.R|X: 5>') self.assertEqual(repr(~Perm.W), '<Perm.R|X: 5>')
self.assertEqual(repr(~Perm.X), '<Perm.R|W: 6>') 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: 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>') self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: 7>')
Open = self.Open Open = self.Open
@ -2315,10 +2351,11 @@ class TestFlag(unittest.TestCase):
self.assertEqual(repr(Open.WO), '<Open.WO: 1>') self.assertEqual(repr(Open.WO), '<Open.WO: 1>')
self.assertEqual(repr(Open.AC), '<Open.AC: 3>') self.assertEqual(repr(Open.AC), '<Open.AC: 3>')
self.assertEqual(repr(Open.RO | Open.CE), '<Open.CE: 524288>') 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.WO | Open.CE), '<Open.WO|CE: 524289>')
self.assertEqual(repr(~Open.RO), '<Open.CE|AC|RW|WO: 524291>') self.assertEqual(repr(~Open.RO), '<Open.WO|RW|CE: 524291>')
self.assertEqual(repr(~Open.WO), '<Open.CE|RW: 524290>') self.assertEqual(repr(~Open.WO), '<Open.RW|CE: 524290>')
self.assertEqual(repr(~Open.AC), '<Open.CE: 524288>') 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.RO | Open.CE)), '<Open.AC: 3>')
self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>') self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>')
@ -2394,6 +2431,46 @@ class TestFlag(unittest.TestCase):
for f in Open: for f in Open:
self.assertEqual(bool(f.value), bool(f)) 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): def test_programatic_function_string(self):
Perm = Flag('Perm', 'R W X') Perm = Flag('Perm', 'R W X')
lst = list(Perm) lst = list(Perm)
@ -2511,9 +2588,45 @@ class TestFlag(unittest.TestCase):
def test_member_iter(self): def test_member_iter(self):
Color = self.Color 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.BLUE), [Color.BLUE])
self.assertEqual(list(Color.GREEN), [Color.GREEN]) 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): def test_auto_number(self):
class Color(Flag): class Color(Flag):
@ -2532,20 +2645,6 @@ class TestFlag(unittest.TestCase):
red = 'not an int' red = 'not an int'
blue = auto() 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): def test_duplicate_auto(self):
class Dupes(Enum): class Dupes(Enum):
first = primero = auto() first = primero = auto()
@ -2554,11 +2653,11 @@ class TestFlag(unittest.TestCase):
self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes)) self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes))
def test_bizarre(self): def test_bizarre(self):
class Bizarre(Flag): with self.assertRaisesRegex(TypeError, "invalid Flag 'Bizarre' -- missing values: 1, 2"):
b = 3 class Bizarre(Flag):
c = 4 b = 3
d = 6 c = 4
self.assertEqual(repr(Bizarre(7)), '<Bizarre.d|c|b: 7>') d = 6
def test_multiple_mixin(self): def test_multiple_mixin(self):
class AllMixin: class AllMixin:
@ -2682,9 +2781,9 @@ class TestIntFlag(unittest.TestCase):
"""Tests of the IntFlags.""" """Tests of the IntFlags."""
class Perm(IntFlag): class Perm(IntFlag):
X = 1 << 0
W = 1 << 1
R = 1 << 2 R = 1 << 2
W = 1 << 1
X = 1 << 0
class Open(IntFlag): class Open(IntFlag):
RO = 0 RO = 0
@ -2696,9 +2795,17 @@ class TestIntFlag(unittest.TestCase):
class Color(IntFlag): class Color(IntFlag):
BLACK = 0 BLACK = 0
RED = 1 RED = 1
ROJO = 1
GREEN = 2 GREEN = 2
BLUE = 4 BLUE = 4
PURPLE = RED|BLUE PURPLE = RED|BLUE
WHITE = RED|GREEN|BLUE
BLANCO = RED|GREEN|BLUE
class Skip(IntFlag):
FIRST = 1
SECOND = 2
EIGHTH = 8
def test_type(self): def test_type(self):
Perm = self.Perm Perm = self.Perm
@ -2723,31 +2830,35 @@ class TestIntFlag(unittest.TestCase):
self.assertEqual(str(Perm.X), 'Perm.X') 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.R|W')
self.assertEqual(str(Perm.R | Perm.W | Perm.X), 'Perm.R|W|X') 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.R | 8), '12')
self.assertEqual(str(Perm(0)), 'Perm.0') self.assertEqual(str(Perm(0)), 'Perm(0)')
self.assertEqual(str(Perm(8)), 'Perm.8') self.assertEqual(str(Perm(8)), '8')
self.assertEqual(str(~Perm.R), 'Perm.W|X') self.assertEqual(str(~Perm.R), 'Perm.W|X')
self.assertEqual(str(~Perm.W), 'Perm.R|X') self.assertEqual(str(~Perm.W), 'Perm.R|X')
self.assertEqual(str(~Perm.X), 'Perm.R|W') 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')
self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm.-8') self.assertEqual(str(~(Perm.R | Perm.W | Perm.X)), 'Perm(0)')
self.assertEqual(str(~(Perm.R | 8)), 'Perm.W|X') self.assertEqual(str(~(Perm.R | 8)), '-13')
self.assertEqual(str(Perm(~0)), 'Perm.R|W|X') 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 Open = self.Open
self.assertEqual(str(Open.RO), 'Open.RO') self.assertEqual(str(Open.RO), 'Open.RO')
self.assertEqual(str(Open.WO), 'Open.WO') self.assertEqual(str(Open.WO), 'Open.WO')
self.assertEqual(str(Open.AC), 'Open.AC') self.assertEqual(str(Open.AC), 'Open.AC')
self.assertEqual(str(Open.RO | Open.CE), 'Open.CE') self.assertEqual(str(Open.RO | Open.CE), 'Open.CE')
self.assertEqual(str(Open.WO | Open.CE), 'Open.CE|WO') self.assertEqual(str(Open.WO | Open.CE), 'Open.WO|CE')
self.assertEqual(str(Open(4)), 'Open.4') self.assertEqual(str(Open(4)), '4')
self.assertEqual(str(~Open.RO), 'Open.CE|AC|RW|WO') self.assertEqual(str(~Open.RO), 'Open.WO|RW|CE')
self.assertEqual(str(~Open.WO), 'Open.CE|RW') self.assertEqual(str(~Open.WO), 'Open.RW|CE')
self.assertEqual(str(~Open.AC), 'Open.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.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): def test_repr(self):
Perm = self.Perm Perm = self.Perm
@ -2756,31 +2867,34 @@ class TestIntFlag(unittest.TestCase):
self.assertEqual(repr(Perm.X), '<Perm.X: 1>') 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.R|W: 6>')
self.assertEqual(repr(Perm.R | Perm.W | Perm.X), '<Perm.R|W|X: 7>') 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.R | 8), '12')
self.assertEqual(repr(Perm(0)), '<Perm.0: 0>') self.assertEqual(repr(Perm(0)), '<Perm: 0>')
self.assertEqual(repr(Perm(8)), '<Perm.8: 8>') self.assertEqual(repr(Perm(8)), '8')
self.assertEqual(repr(~Perm.R), '<Perm.W|X: -5>') self.assertEqual(repr(~Perm.R), '<Perm.W|X: 3>')
self.assertEqual(repr(~Perm.W), '<Perm.R|X: -3>') self.assertEqual(repr(~Perm.W), '<Perm.R|X: 5>')
self.assertEqual(repr(~Perm.X), '<Perm.R|W: -2>') self.assertEqual(repr(~Perm.X), '<Perm.R|W: 6>')
self.assertEqual(repr(~(Perm.R | Perm.W)), '<Perm.X: -7>') self.assertEqual(repr(~(Perm.R | Perm.W)), '<Perm.X: 1>')
self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm.-8: -8>') self.assertEqual(repr(~(Perm.R | Perm.W | Perm.X)), '<Perm: 0>')
self.assertEqual(repr(~(Perm.R | 8)), '<Perm.W|X: -13>') self.assertEqual(repr(~(Perm.R | 8)), '-13')
self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: -1>') self.assertEqual(repr(Perm(~0)), '<Perm.R|W|X: 7>')
self.assertEqual(repr(Perm(~8)), '<Perm.R|W|X: -9>') self.assertEqual(repr(Perm(~8)), '-9')
Open = self.Open Open = self.Open
self.assertEqual(repr(Open.RO), '<Open.RO: 0>') self.assertEqual(repr(Open.RO), '<Open.RO: 0>')
self.assertEqual(repr(Open.WO), '<Open.WO: 1>') self.assertEqual(repr(Open.WO), '<Open.WO: 1>')
self.assertEqual(repr(Open.AC), '<Open.AC: 3>') self.assertEqual(repr(Open.AC), '<Open.AC: 3>')
self.assertEqual(repr(Open.RO | Open.CE), '<Open.CE: 524288>') 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.WO | Open.CE), '<Open.WO|CE: 524289>')
self.assertEqual(repr(Open(4)), '<Open.4: 4>') self.assertEqual(repr(Open(4)), '4')
self.assertEqual(repr(~Open.RO), '<Open.CE|AC|RW|WO: -1>') self.assertEqual(repr(~Open.RO), '<Open.WO|RW|CE: 524291>')
self.assertEqual(repr(~Open.WO), '<Open.CE|RW: -2>') self.assertEqual(repr(~Open.WO), '<Open.RW|CE: 524290>')
self.assertEqual(repr(~Open.AC), '<Open.CE: -4>') self.assertEqual(repr(~Open.AC), '<Open.CE: 524288>')
self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC|RW|WO: -524289>') self.assertEqual(repr(~(Open.RO | Open.CE)), '<Open.AC: 3>')
self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: -524290>') self.assertEqual(repr(~(Open.WO | Open.CE)), '<Open.RW: 2>')
self.assertEqual(repr(Open(~4)), '<Open.CE|AC|RW|WO: -5>') self.assertEqual(repr(Open(~4)), '-5')
Skip = self.Skip
self.assertEqual(repr(Skip(~4)), '<Skip.FIRST|SECOND|EIGHTH: 11>')
def test_format(self): def test_format(self):
Perm = self.Perm Perm = self.Perm
@ -2863,8 +2977,7 @@ class TestIntFlag(unittest.TestCase):
RWX = Perm.R | Perm.W | Perm.X RWX = Perm.R | Perm.W | Perm.X
values = list(Perm) + [RW, RX, WX, RWX, Perm(0)] values = list(Perm) + [RW, RX, WX, RWX, Perm(0)]
for i in values: for i in values:
self.assertEqual(~i, ~i.value) self.assertEqual(~i, (~i).value)
self.assertEqual((~i).value, ~i.value)
self.assertIs(type(~i), Perm) self.assertIs(type(~i), Perm)
self.assertEqual(~~i, i) self.assertEqual(~~i, i)
for i in Perm: 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.WO, Open.RO)
self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE) 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): def test_programatic_function_string(self):
Perm = IntFlag('Perm', 'R W X') Perm = IntFlag('Perm', 'R W X')
lst = list(Perm) lst = list(Perm)
@ -3014,9 +3167,27 @@ class TestIntFlag(unittest.TestCase):
def test_member_iter(self): def test_member_iter(self):
Color = self.Color 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.BLUE), [Color.BLUE])
self.assertEqual(list(Color.GREEN), [Color.GREEN]) 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): def test_bool(self):
Perm = self.Perm Perm = self.Perm
@ -3026,6 +3197,13 @@ class TestIntFlag(unittest.TestCase):
for f in Open: for f in Open:
self.assertEqual(bool(f.value), bool(f)) 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): def test_multiple_mixin(self):
class AllMixin: class AllMixin:
@classproperty @classproperty
@ -3176,7 +3354,7 @@ expected_help_output_with_docs = """\
Help on class Color in module %s: Help on class Color in module %s:
class Color(enum.Enum) 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 |\x20\x20
| An enumeration. | An enumeration.
|\x20\x20 |\x20\x20
@ -3328,7 +3506,7 @@ class TestStdLib(unittest.TestCase):
class MiscTestCase(unittest.TestCase): class MiscTestCase(unittest.TestCase):
def test__all__(self): 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 # These are unordered here on purpose to ensure that declaration order

View file

@ -2176,11 +2176,13 @@ class PatternReprTests(unittest.TestCase):
"re.IGNORECASE|re.DOTALL|re.VERBOSE") "re.IGNORECASE|re.DOTALL|re.VERBOSE")
self.assertEqual(repr(re.I|re.S|re.X|(1<<20)), self.assertEqual(repr(re.I|re.S|re.X|(1<<20)),
"re.IGNORECASE|re.DOTALL|re.VERBOSE|0x100000") "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)), 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))), 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): class ImplementationTest(unittest.TestCase):

View file

@ -155,7 +155,12 @@ class DynamicClassAttribute:
class's __getattr__ method; this is done by raising AttributeError. class's __getattr__ method; this is done by raising AttributeError.
This allows one to have properties active on an instance, and have virtual 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): def __init__(self, fget=None, fset=None, fdel=None, doc=None):

View file

@ -141,6 +141,7 @@ Stefan Behnel
Reimer Behrends Reimer Behrends
Ben Bell Ben Bell
Thomas Bellman Thomas Bellman
John Belmonte
Alexander “Саша” Belopolsky Alexander “Саша” Belopolsky
Eli Bendersky Eli Bendersky
Nikhil Benesch Nikhil Benesch

View file

@ -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.