Skip to content

Commit 7aaeb2a

Browse files
authored
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().
1 parent 9852cb3 commit 7aaeb2a

File tree

8 files changed

+747
-285
lines changed

8 files changed

+747
-285
lines changed

Doc/library/enum.rst

Lines changed: 135 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Having two enum members with the same name is invalid::
197197
...
198198
Traceback (most recent call last):
199199
...
200-
TypeError: Attempted to reuse key: 'SQUARE'
200+
TypeError: 'SQUARE' already defined as: 2
201201

202202
However, two enum members are allowed to have the same value. Given two members
203203
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::
422422
...
423423
Traceback (most recent call last):
424424
...
425-
TypeError: Cannot extend enumerations
425+
TypeError: MoreColor: cannot extend enumeration 'Color'
426426

427427
But this is allowed::
428428

@@ -617,6 +617,7 @@ by extension, string enumerations of different types can also be compared
617617
to each other. :class:`StrEnum` exists to help avoid the problem of getting
618618
an incorrect member::
619619

620+
>>> from enum import StrEnum
620621
>>> class Directions(StrEnum):
621622
... NORTH = 'north', # notice the trailing comma
622623
... SOUTH = 'south'
@@ -638,12 +639,22 @@ IntFlag
638639
The next variation of :class:`Enum` provided, :class:`IntFlag`, is also based
639640
on :class:`int`. The difference being :class:`IntFlag` members can be combined
640641
using the bitwise operators (&, \|, ^, ~) and the result is still an
641-
:class:`IntFlag` member. However, as the name implies, :class:`IntFlag`
642+
:class:`IntFlag` member, if possible. However, as the name implies, :class:`IntFlag`
642643
members also subclass :class:`int` and can be used wherever an :class:`int` is
643-
used. Any operation on an :class:`IntFlag` member besides the bit-wise
644-
operations will lose the :class:`IntFlag` membership.
644+
used.
645+
646+
.. note::
647+
648+
Any operation on an :class:`IntFlag` member besides the bit-wise operations will
649+
lose the :class:`IntFlag` membership.
650+
651+
.. note::
652+
653+
Bit-wise operations that result in invalid :class:`IntFlag` values will lose the
654+
:class:`IntFlag` membership.
645655

646656
.. versionadded:: 3.6
657+
.. versionchanged:: 3.10
647658

648659
Sample :class:`IntFlag` class::
649660

@@ -671,21 +682,41 @@ It is also possible to name the combinations::
671682
>>> Perm.RWX
672683
<Perm.RWX: 7>
673684
>>> ~Perm.RWX
674-
<Perm.-8: -8>
685+
<Perm: 0>
686+
>>> Perm(7)
687+
<Perm.RWX: 7>
688+
689+
.. note::
690+
691+
Named combinations are considered aliases. Aliases do not show up during
692+
iteration, but can be returned from by-value lookups.
693+
694+
.. versionchanged:: 3.10
675695

676696
Another important difference between :class:`IntFlag` and :class:`Enum` is that
677697
if no flags are set (the value is 0), its boolean evaluation is :data:`False`::
678698

679699
>>> Perm.R & Perm.X
680-
<Perm.0: 0>
700+
<Perm: 0>
681701
>>> bool(Perm.R & Perm.X)
682702
False
683703

684704
Because :class:`IntFlag` members are also subclasses of :class:`int` they can
685-
be combined with them::
705+
be combined with them (but may lose :class:`IntFlag` membership::
706+
707+
>>> Perm.X | 4
708+
<Perm.R|X: 5>
686709

687710
>>> Perm.X | 8
688-
<Perm.8|X: 9>
711+
9
712+
713+
.. note::
714+
715+
The negation operator, ``~``, always returns an :class:`IntFlag` member with a
716+
positive value::
717+
718+
>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
719+
True
689720

690721
:class:`IntFlag` members can also be iterated over::
691722

@@ -717,7 +748,7 @@ flags being set, the boolean evaluation is :data:`False`::
717748
... GREEN = auto()
718749
...
719750
>>> Color.RED & Color.GREEN
720-
<Color.0: 0>
751+
<Color: 0>
721752
>>> bool(Color.RED & Color.GREEN)
722753
False
723754

@@ -751,7 +782,7 @@ value::
751782

752783
>>> purple = Color.RED | Color.BLUE
753784
>>> list(purple)
754-
[<Color.BLUE: 2>, <Color.RED: 1>]
785+
[<Color.RED: 1>, <Color.BLUE: 2>]
755786

756787
.. versionadded:: 3.10
757788

@@ -953,7 +984,7 @@ to handle any extra arguments::
953984
... BLEACHED_CORAL = () # New color, no Pantone code yet!
954985
...
955986
>>> Swatch.SEA_GREEN
956-
<Swatch.SEA_GREEN: 2>
987+
<Swatch.SEA_GREEN>
957988
>>> Swatch.SEA_GREEN.pantone
958989
'1246'
959990
>>> Swatch.BLEACHED_CORAL.pantone
@@ -1144,6 +1175,14 @@ Supported ``_sunder_`` names
11441175
:class:`auto` to get an appropriate value for an enum member; may be
11451176
overridden
11461177

1178+
.. note::
1179+
1180+
For standard :class:`Enum` classes the next value chosen is the last value seen
1181+
incremented by one.
1182+
1183+
For :class:`Flag`-type classes the next value chosen will be the next highest
1184+
power-of-two, regardless of the last value seen.
1185+
11471186
.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
11481187
.. versionadded:: 3.7 ``_ignore_``
11491188

@@ -1159,7 +1198,9 @@ and raise an error if the two do not match::
11591198
...
11601199
Traceback (most recent call last):
11611200
...
1162-
TypeError: member order does not match _order_
1201+
TypeError: member order does not match _order_:
1202+
['RED', 'BLUE', 'GREEN']
1203+
['RED', 'GREEN', 'BLUE']
11631204

11641205
.. note::
11651206

@@ -1179,23 +1220,22 @@ Private names are not converted to Enum members, but remain normal attributes.
11791220
""""""""""""""""""""
11801221

11811222
:class:`Enum` members are instances of their :class:`Enum` class, and are
1182-
normally accessed as ``EnumClass.member``. Under certain circumstances they
1183-
can also be accessed as ``EnumClass.member.member``, but you should never do
1184-
this as that lookup may fail or, worse, return something besides the
1185-
:class:`Enum` member you are looking for (this is another good reason to use
1186-
all-uppercase names for members)::
1223+
normally accessed as ``EnumClass.member``. In Python versions ``3.5`` to
1224+
``3.9`` you could access members from other members -- this practice was
1225+
discouraged, and in ``3.10`` :class:`Enum` has returned to not allowing it::
11871226

11881227
>>> class FieldTypes(Enum):
11891228
... name = 0
11901229
... value = 1
11911230
... size = 2
11921231
...
11931232
>>> FieldTypes.value.size
1194-
<FieldTypes.size: 2>
1195-
>>> FieldTypes.size.value
1196-
2
1233+
Traceback (most recent call last):
1234+
...
1235+
AttributeError: FieldTypes: no attribute 'size'
11971236

11981237
.. versionchanged:: 3.5
1238+
.. versionchanged:: 3.10
11991239

12001240

12011241
Creating members that are mixed with other data types
@@ -1237,14 +1277,14 @@ but not of the class::
12371277
>>> dir(Planet)
12381278
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
12391279
>>> dir(Planet.EARTH)
1240-
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
1280+
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
12411281

12421282

12431283
Combining members of ``Flag``
12441284
"""""""""""""""""""""""""""""
12451285

1246-
If a combination of Flag members is not named, the :func:`repr` will include
1247-
all named flags and all named combinations of flags that are in the value::
1286+
Iterating over a combination of Flag members will only return the members that
1287+
are comprised of a single bit::
12481288

12491289
>>> class Color(Flag):
12501290
... RED = auto()
@@ -1254,10 +1294,10 @@ all named flags and all named combinations of flags that are in the value::
12541294
... YELLOW = RED | GREEN
12551295
... CYAN = GREEN | BLUE
12561296
...
1257-
>>> Color(3) # named combination
1297+
>>> Color(3)
12581298
<Color.YELLOW: 3>
1259-
>>> Color(7) # not named combination
1260-
<Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>
1299+
>>> Color(7)
1300+
<Color.RED|GREEN|BLUE: 7>
12611301

12621302
``StrEnum`` and :meth:`str.__str__`
12631303
"""""""""""""""""""""""""""""""""""
@@ -1269,3 +1309,71 @@ parts of Python will read the string data directly, while others will call
12691309
:meth:`StrEnum.__str__` will be the same as :meth:`str.__str__` so that
12701310
``str(StrEnum.member) == StrEnum.member`` is true.
12711311

1312+
``Flag`` and ``IntFlag`` minutia
1313+
""""""""""""""""""""""""""""""""
1314+
1315+
The code sample::
1316+
1317+
>>> class Color(IntFlag):
1318+
... BLACK = 0
1319+
... RED = 1
1320+
... GREEN = 2
1321+
... BLUE = 4
1322+
... PURPLE = RED | BLUE
1323+
... WHITE = RED | GREEN | BLUE
1324+
...
1325+
1326+
- single-bit flags are canonical
1327+
- multi-bit and zero-bit flags are aliases
1328+
- only canonical flags are returned during iteration::
1329+
1330+
>>> list(Color.WHITE)
1331+
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
1332+
1333+
- negating a flag or flag set returns a new flag/flag set with the
1334+
corresponding positive integer value::
1335+
1336+
>>> Color.GREEN
1337+
<Color.GREEN: 2>
1338+
1339+
>>> ~Color.GREEN
1340+
<Color.PURPLE: 5>
1341+
1342+
- names of pseudo-flags are constructed from their members' names::
1343+
1344+
>>> (Color.RED | Color.GREEN).name
1345+
'RED|GREEN'
1346+
1347+
- multi-bit flags, aka aliases, can be returned from operations::
1348+
1349+
>>> Color.RED | Color.BLUE
1350+
<Color.PURPLE: 5>
1351+
1352+
>>> Color(7) # or Color(-1)
1353+
<Color.WHITE: 7>
1354+
1355+
- membership / containment checking has changed slightly -- zero valued flags
1356+
are never considered to be contained::
1357+
1358+
>>> Color.BLACK in Color.WHITE
1359+
False
1360+
1361+
otherwise, if all bits of one flag are in the other flag, True is returned::
1362+
1363+
>>> Color.PURPLE in Color.WHITE
1364+
True
1365+
1366+
There is a new boundary mechanism that controls how out-of-range / invalid
1367+
bits are handled: ``STRICT``, ``CONFORM``, ``EJECT`', and ``KEEP``:
1368+
1369+
* STRICT --> raises an exception when presented with invalid values
1370+
* CONFORM --> discards any invalid bits
1371+
* EJECT --> lose Flag status and become a normal int with the given value
1372+
* KEEP --> keep the extra bits
1373+
- keeps Flag status and extra bits
1374+
- extra bits do not show up in iteration
1375+
- extra bits do show up in repr() and str()
1376+
1377+
The default for Flag is ``STRICT``, the default for ``IntFlag`` is ``DISCARD``,
1378+
and the default for ``_convert_`` is ``KEEP`` (see ``ssl.Options`` for an
1379+
example of when ``KEEP`` is needed).

0 commit comments

Comments
 (0)