[Python-checkins] cpython: issue23591: add docs; code cleanup; more tests

ethan.furman python-checkins at python.org
Fri Sep 2 02:55:37 EDT 2016


https://hg.python.org/cpython/rev/adbc7eec97f1
changeset:   103008:adbc7eec97f1
user:        Ethan Furman <ethan at stoneleaf.us>
date:        Thu Sep 01 23:55:19 2016 -0700
summary:
  issue23591: add docs; code cleanup; more tests

files:
  Doc/library/enum.rst  |  195 +++++++++++++++++++++--------
  Lib/enum.py           |    8 +-
  Lib/test/test_enum.py |   88 ++++++++++--
  3 files changed, 213 insertions(+), 78 deletions(-)


diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -23,9 +23,9 @@
 Module Contents
 ---------------
 
-This module defines two enumeration classes that can be used to define unique
-sets of names and values: :class:`Enum` and :class:`IntEnum`.  It also defines
-one decorator, :func:`unique`.
+This module defines four enumeration classes that can be used to define unique
+sets of names and values: :class:`Enum`, :class:`IntEnum`, and
+:class:`IntFlags`.  It also defines one decorator, :func:`unique`.
 
 .. class:: Enum
 
@@ -37,10 +37,23 @@
     Base class for creating enumerated constants that are also
     subclasses of :class:`int`.
 
+.. class:: IntFlag
+
+    Base class for creating enumerated constants that can be combined using
+    the bitwise operators without losing their :class:`IntFlag` membership.
+    :class:`IntFlag` members are also subclasses of :class:`int`.
+
+.. class:: Flag
+
+    Base class for creating enumerated constants that can be combined using
+    the bitwise operations without losing their :class:`Flag` membership.
+
 .. function:: unique
 
     Enum class decorator that ensures only one name is bound to any one value.
 
+.. versionadded:: 3.6  ``Flag``, ``IntFlag``
+
 
 Creating an Enum
 ----------------
@@ -478,7 +491,7 @@
 IntEnum
 ^^^^^^^
 
-A variation of :class:`Enum` is provided which is also a subclass of
+The first variation of :class:`Enum` that is provided is also a subclass of
 :class:`int`.  Members of an :class:`IntEnum` can be compared to integers;
 by extension, integer enumerations of different types can also be compared
 to each other::
@@ -521,13 +534,54 @@
     >>> [i for i in range(Shape.square)]
     [0, 1]
 
-For the vast majority of code, :class:`Enum` is strongly recommended,
-since :class:`IntEnum` breaks some semantic promises of an enumeration (by
-being comparable to integers, and thus by transitivity to other
-unrelated enumerations).  It should be used only in special cases where
-there's no other choice; for example, when integer constants are
-replaced with enumerations and backwards compatibility is required with code
-that still expects integers.
+
+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`
+members also subclass :class:`int` and can be used wherever an :class:`int` is.
+Any operation on an :class:`IntFlag` member besides the bit-wise operations
+will lose the :class:`IntFlag` membership.
+
+    >>> from enum import IntFlag
+    >>> class Perm(IntFlag):
+    ...     R = 4
+    ...     W = 2
+    ...     X = 1
+    ...
+    >>> Perm.R | Perm.W
+    <Perm.R|W: 6>
+    >>> Perm.R + Perm.W
+    6
+    >>> RW = Perm.R | Perm.W
+    >>> Perm.R in RW
+    True
+
+.. versionadded:: 3.6
+
+
+Flag
+^^^^
+
+The last variation is :class:`Flag`.  Like :class:`IntFlag`, :class:`Flag`
+members can be combined using the bitwise operators (^, \|, ^, ~).  Unlike
+:class:`IntFlag`, they cannot be combined with, nor compared against, any
+other :class:`Flag` enumeration nor :class:`int`.
+
+.. versionadded:: 3.6
+
+.. note::
+
+    For the majority of new code, :class:`Enum` and :class:`Flag` are strongly
+    recommended, since :class:`IntEnum` and :class:`IntFlag` break some
+    semantic promises of an enumeration (by being comparable to integers, and
+    thus by transitivity to other unrelated enumerations).  :class:`IntEnum`
+    and :class:`IntFlag` should be used only in cases where :class:`Enum` and
+    :class:`Flag` will not do; for example, when integer constants are replaced
+    with enumerations, or for interoperability with other systems.
 
 
 Others
@@ -567,10 +621,10 @@
 Interesting examples
 --------------------
 
-While :class:`Enum` and :class:`IntEnum` are expected to cover the majority of
-use-cases, they cannot cover them all.  Here are recipes for some different
-types of enumerations that can be used directly, or as examples for creating
-one's own.
+While :class:`Enum`, :class:`IntEnum`, :class:`IntFlag`, and :class:`Flag` are
+expected to cover the majority of use-cases, they cannot cover them all.  Here
+are recipes for some different types of enumerations that can be used directly,
+or as examples for creating one's own.
 
 
 AutoNumber
@@ -731,55 +785,33 @@
 Finer Points
 ^^^^^^^^^^^^
 
-:class:`Enum` members are instances of an :class:`Enum` class, and even
-though they are accessible as `EnumClass.member`, they should not be accessed
-directly from the member as that lookup may fail or, worse, return something
-besides the :class:`Enum` member you looking for::
+Supported ``__dunder__`` names
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    >>> class FieldTypes(Enum):
-    ...     name = 0
-    ...     value = 1
-    ...     size = 2
-    ...
-    >>> FieldTypes.value.size
-    <FieldTypes.size: 2>
-    >>> FieldTypes.size.value
-    2
+:attr:`__members__` is an :class:`OrderedDict` of ``member_name``:``member``
+items.  It is only available on the class.
 
-.. versionchanged:: 3.5
+:meth:`__new__`, if specified, must create and return the enum members; it is
+also a very good idea to set the member's :attr:`_value_` appropriately.  Once
+all the members are created it is no longer used.
 
-Boolean evaluation: Enum classes that are mixed with non-Enum types (such as
-:class:`int`, :class:`str`, etc.) are evaluated according to the mixed-in
-type's rules; otherwise, all members evaluate as ``True``.  To make your own
-Enum's boolean evaluation depend on the member's value add the following to
-your class::
 
-    def __bool__(self):
-        return bool(self.value)
+Supported ``_sunder_`` names
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The :attr:`__members__` attribute is only available on the class.
+- ``_name_`` -- name of the member
+- ``_value_`` -- value of the member; can be set / modified in ``__new__``
 
-If you give your :class:`Enum` subclass extra methods, like the `Planet`_
-class above, those methods will show up in a :func:`dir` of the member,
-but not of the class::
+- ``_missing_`` -- a lookup function used when a value is not found; may be
+  overridden
+- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
+  (class attribute, removed during class creation)
 
-    >>> dir(Planet)
-    ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
-    >>> dir(Planet.EARTH)
-    ['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
+.. versionadded:: 3.6 ``_missing_``, ``_order_``
 
-The :meth:`__new__` method will only be used for the creation of the
-:class:`Enum` members -- after that it is replaced.  Any custom :meth:`__new__`
-method must create the object and set the :attr:`_value_` attribute
-appropriately.
-
-If you wish to change how :class:`Enum` members are looked up you should either
-write a helper function or a :func:`classmethod` for the :class:`Enum`
-subclass.
-
-To help keep Python 2 / Python 3 code in sync a user-specified :attr:`_order_`,
-if provided, will be checked to ensure the actual order of the enumeration
-matches::
+To help keep Python 2 / Python 3 code in sync an :attr:`_order_` attribute can
+be provided.  It will be checked against the actual order of the enumeration
+and raise an error if the two do not match::
 
     >>> class Color(Enum):
     ...     _order_ = 'red green blue'
@@ -794,4 +826,53 @@
 .. note::
 
     In Python 2 code the :attr:`_order_` attribute is necessary as definition
-    order is lost during class creation.
+    order is lost before it can be recorded.
+
+``Enum`` member type
+~~~~~~~~~~~~~~~~~~~~
+
+:class:`Enum` members are instances of an :class:`Enum` class, and even
+though they are accessible as `EnumClass.member`, they should not be accessed
+directly from the member as that lookup may fail or, worse, return something
+besides the ``Enum`` member you looking for::
+
+    >>> class FieldTypes(Enum):
+    ...     name = 0
+    ...     value = 1
+    ...     size = 2
+    ...
+    >>> FieldTypes.value.size
+    <FieldTypes.size: 2>
+    >>> FieldTypes.size.value
+    2
+
+.. versionchanged:: 3.5
+
+
+Boolean value of ``Enum`` classes and members
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``Enum`` members that are mixed with non-Enum types (such as
+:class:`int`, :class:`str`, etc.) are evaluated according to the mixed-in
+type's rules; otherwise, all members evaluate as :data:`True`.  To make your own
+Enum's boolean evaluation depend on the member's value add the following to
+your class::
+
+    def __bool__(self):
+        return bool(self.value)
+
+``Enum`` classes always evaluate as :data:`True`.
+
+
+``Enum`` classes with methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you give your :class:`Enum` subclass extra methods, like the `Planet`_
+class above, those methods will show up in a :func:`dir` of the member,
+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']
+
diff --git a/Lib/enum.py b/Lib/enum.py
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -10,7 +10,7 @@
     from collections import OrderedDict
 
 
-__all__ = ['EnumMeta', 'Enum', 'IntEnum', 'Flags', 'IntFlags', 'unique']
+__all__ = ['EnumMeta', 'Enum', 'IntEnum', 'Flag', 'IntFlag', 'unique']
 
 
 def _is_descriptor(obj):
@@ -104,7 +104,7 @@
             enum_dict['_generate_next_value_'] = getattr(first_enum, '_generate_next_value_', None)
         return enum_dict
 
-    def __new__(metacls, cls, bases, classdict, **kwds):
+    def __new__(metacls, cls, bases, classdict):
         # 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
@@ -614,7 +614,7 @@
 def _reduce_ex_by_name(self, proto):
     return self.name
 
-class Flags(Enum):
+class Flag(Enum):
     """Support for flags"""
     @staticmethod
     def _generate_next_value_(name, start, count, last_value):
@@ -736,7 +736,7 @@
         return self.__class__(inverted)
 
 
-class IntFlags(int, Flags):
+class IntFlag(int, Flag):
     """Support for integer-based Flags"""
 
     @classmethod
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -3,7 +3,7 @@
 import pydoc
 import unittest
 from collections import OrderedDict
-from enum import Enum, IntEnum, EnumMeta, Flags, IntFlags, unique
+from enum import Enum, IntEnum, EnumMeta, Flag, IntFlag, unique
 from io import StringIO
 from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
 from test import support
@@ -33,6 +33,14 @@
 except Exception as exc:
     FloatStooges = exc
 
+try:
+    class FlagStooges(Flag):
+        LARRY = 1
+        CURLY = 2
+        MOE = 3
+except Exception as exc:
+    FlagStooges = exc
+
 # for pickle test and subclass tests
 try:
     class StrEnum(str, Enum):
@@ -1633,13 +1641,13 @@
                 verde = green
 
 
-class TestFlags(unittest.TestCase):
+class TestFlag(unittest.TestCase):
     """Tests of the Flags."""
 
-    class Perm(Flags):
+    class Perm(Flag):
         R, W, X = 4, 2, 1
 
-    class Open(Flags):
+    class Open(Flag):
         RO = 0
         WO = 1
         RW = 2
@@ -1760,7 +1768,7 @@
         self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE)
 
     def test_programatic_function_string(self):
-        Perm = Flags('Perm', 'R W X')
+        Perm = Flag('Perm', 'R W X')
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -1775,7 +1783,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_string_with_start(self):
-        Perm = Flags('Perm', 'R W X', start=8)
+        Perm = Flag('Perm', 'R W X', start=8)
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -1790,7 +1798,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_string_list(self):
-        Perm = Flags('Perm', ['R', 'W', 'X'])
+        Perm = Flag('Perm', ['R', 'W', 'X'])
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -1805,7 +1813,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_iterable(self):
-        Perm = Flags('Perm', (('R', 2), ('W', 8), ('X', 32)))
+        Perm = Flag('Perm', (('R', 2), ('W', 8), ('X', 32)))
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -1820,7 +1828,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_from_dict(self):
-        Perm = Flags('Perm', OrderedDict((('R', 2), ('W', 8), ('X', 32))))
+        Perm = Flag('Perm', OrderedDict((('R', 2), ('W', 8), ('X', 32))))
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -1834,16 +1842,42 @@
             self.assertIn(e, Perm)
             self.assertIs(type(e), Perm)
 
+    def test_pickle(self):
+        if isinstance(FlagStooges, Exception):
+            raise FlagStooges
+        test_pickle_dump_load(self.assertIs, FlagStooges.CURLY|FlagStooges.MOE)
+        test_pickle_dump_load(self.assertIs, FlagStooges)
 
-class TestIntFlags(unittest.TestCase):
+
+    def test_containment(self):
+        Perm = self.Perm
+        R, W, X = Perm
+        RW = R | W
+        RX = R | X
+        WX = W | X
+        RWX = R | W | X
+        self.assertTrue(R in RW)
+        self.assertTrue(R in RX)
+        self.assertTrue(R in RWX)
+        self.assertTrue(W in RW)
+        self.assertTrue(W in WX)
+        self.assertTrue(W in RWX)
+        self.assertTrue(X in RX)
+        self.assertTrue(X in WX)
+        self.assertTrue(X in RWX)
+        self.assertFalse(R in WX)
+        self.assertFalse(W in RX)
+        self.assertFalse(X in RW)
+
+class TestIntFlag(unittest.TestCase):
     """Tests of the IntFlags."""
 
-    class Perm(IntFlags):
+    class Perm(IntFlag):
         X = 1 << 0
         W = 1 << 1
         R = 1 << 2
 
-    class Open(IntFlags):
+    class Open(IntFlag):
         RO = 0
         WO = 1
         RW = 2
@@ -2003,7 +2037,7 @@
         self.assertIs((Open.WO|Open.CE) & ~Open.WO, Open.CE)
 
     def test_programatic_function_string(self):
-        Perm = IntFlags('Perm', 'R W X')
+        Perm = IntFlag('Perm', 'R W X')
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -2019,7 +2053,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_string_with_start(self):
-        Perm = IntFlags('Perm', 'R W X', start=8)
+        Perm = IntFlag('Perm', 'R W X', start=8)
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -2035,7 +2069,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_string_list(self):
-        Perm = IntFlags('Perm', ['R', 'W', 'X'])
+        Perm = IntFlag('Perm', ['R', 'W', 'X'])
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -2051,7 +2085,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_iterable(self):
-        Perm = IntFlags('Perm', (('R', 2), ('W', 8), ('X', 32)))
+        Perm = IntFlag('Perm', (('R', 2), ('W', 8), ('X', 32)))
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -2067,7 +2101,7 @@
             self.assertIs(type(e), Perm)
 
     def test_programatic_function_from_dict(self):
-        Perm = IntFlags('Perm', OrderedDict((('R', 2), ('W', 8), ('X', 32))))
+        Perm = IntFlag('Perm', OrderedDict((('R', 2), ('W', 8), ('X', 32))))
         lst = list(Perm)
         self.assertEqual(len(lst), len(Perm))
         self.assertEqual(len(Perm), 3, Perm)
@@ -2083,6 +2117,26 @@
             self.assertIs(type(e), Perm)
 
 
+    def test_containment(self):
+        Perm = self.Perm
+        R, W, X = Perm
+        RW = R | W
+        RX = R | X
+        WX = W | X
+        RWX = R | W | X
+        self.assertTrue(R in RW)
+        self.assertTrue(R in RX)
+        self.assertTrue(R in RWX)
+        self.assertTrue(W in RW)
+        self.assertTrue(W in WX)
+        self.assertTrue(W in RWX)
+        self.assertTrue(X in RX)
+        self.assertTrue(X in WX)
+        self.assertTrue(X in RWX)
+        self.assertFalse(R in WX)
+        self.assertFalse(W in RX)
+        self.assertFalse(X in RW)
+
 class TestUnique(unittest.TestCase):
 
     def test_unique_clean(self):

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list