[Python-checkins] bpo-31801: Enum: add _ignore_ as class option (#5237)

Ethan Furman webhook-mailer at python.org
Mon Jan 22 10:56:40 EST 2018


https://github.com/python/cpython/commit/a4b1bb4801f7a941ff9e86b96da539be1c288833
commit: a4b1bb4801f7a941ff9e86b96da539be1c288833
branch: master
author: Ethan Furman <ethan at stoneleaf.us>
committer: GitHub <noreply at github.com>
date: 2018-01-22T07:56:37-08:00
summary:

bpo-31801:  Enum:  add _ignore_ as class option (#5237)

* bpo-31801:  Enum:  add _ignore_ as class option

_ignore_ is a list, or white-space seperated str, of names that will not
be candidates for members; these names, and _ignore_ itself, are removed
from the final class.

* bpo-31801:  Enum:  add documentation for _ignore_

* bpo-31801: Enum: remove trailing whitespace

* bpo-31801: Enum: fix bulleted list format

* bpo-31801: add version added for _ignore_

files:
A Misc/NEWS.d/next/Library/2018-01-18-13-47-40.bpo-31801.3UGH1h.rst
M Doc/library/enum.rst
M Lib/enum.py
M Lib/test/test_enum.py

diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
index 5c1b226efc7..fc65a3d078f 100644
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -379,7 +379,8 @@ The rules for what is allowed are as follows: names that start and end with
 a single underscore are reserved by enum and cannot be used; all other
 attributes defined within an enumeration will become members of this
 enumeration, with the exception of special methods (:meth:`__str__`,
-:meth:`__add__`, etc.) and descriptors (methods are also descriptors).
+:meth:`__add__`, etc.), descriptors (methods are also descriptors), and
+variable names listed in :attr:`_ignore_`.
 
 Note:  if your enumeration defines :meth:`__new__` and/or :meth:`__init__` then
 whatever value(s) were given to the enum member will be passed into those
@@ -943,6 +944,25 @@ will be passed to those methods::
     9.802652743337129
 
 
+TimePeriod
+^^^^^^^^^^
+
+An example to show the :attr:`_ignore_` attribute in use::
+
+    >>> from datetime import timedelta
+    >>> class Period(timedelta, Enum):
+    ...     "different lengths of time"
+    ...     _ignore_ = 'Period i'
+    ...     Period = vars()
+    ...     for i in range(367):
+    ...         Period['day_%d' % i] = i
+    ...
+    >>> list(Period)[:2]
+    [<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
+    >>> list(Period)[-2:]
+    [<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
+
+
 How are Enums different?
 ------------------------
 
@@ -994,6 +1014,9 @@ Supported ``_sunder_`` names
 
 - ``_missing_`` -- a lookup function used when a value is not found; may be
   overridden
+- ``_ignore_`` -- a list of names, either as a :func:`list` or a :func:`str`,
+  that will not be transformed into members, and will be removed from the final
+  class
 - ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
   (class attribute, removed during class creation)
 - ``_generate_next_value_`` -- used by the `Functional API`_ and by
@@ -1001,6 +1024,7 @@ Supported ``_sunder_`` names
   overridden
 
 .. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
+.. versionadded:: 3.7 ``_ignore_``
 
 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
diff --git a/Lib/enum.py b/Lib/enum.py
index fe7cb20fc06..e5fe6f3b94a 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -64,6 +64,7 @@ def __init__(self):
         super().__init__()
         self._member_names = []
         self._last_values = []
+        self._ignore = []
 
     def __setitem__(self, key, value):
         """Changes anything not dundered or not a descriptor.
@@ -77,17 +78,28 @@ def __setitem__(self, key, value):
         if _is_sunder(key):
             if key not in (
                     '_order_', '_create_pseudo_member_',
-                    '_generate_next_value_', '_missing_',
+                    '_generate_next_value_', '_missing_', '_ignore_',
                     ):
                 raise ValueError('_names_ are reserved for future Enum use')
             if key == '_generate_next_value_':
                 setattr(self, '_generate_next_value', value)
+            elif key == '_ignore_':
+                if isinstance(value, str):
+                    value = value.replace(',',' ').split()
+                else:
+                    value = list(value)
+                self._ignore = value
+                already = set(value) & set(self._member_names)
+                if already:
+                    raise ValueError('_ignore_ cannot specify already set names: %r' % (already, ))
         elif _is_dunder(key):
             if key == '__order__':
                 key = '_order_'
         elif key in self._member_names:
             # descriptor overwriting an enum?
             raise TypeError('Attempted to reuse key: %r' % key)
+        elif key in self._ignore:
+            pass
         elif not _is_descriptor(value):
             if key in self:
                 # enum overwriting a descriptor?
@@ -124,6 +136,12 @@ def __new__(metacls, cls, bases, classdict):
         # cannot be mixed with other types (int, float, etc.) if it has an
         # inherited __new__ unless a new __new__ is defined (or the resulting
         # class will fail).
+        #
+        # remove any keys listed in _ignore_
+        classdict.setdefault('_ignore_', []).append('_ignore_')
+        ignore = classdict['_ignore_']
+        for key in ignore:
+            classdict.pop(key, None)
         member_type, first_enum = metacls._get_mixins_(bases)
         __new__, save_new, use_args = metacls._find_new_(classdict, member_type,
                                                         first_enum)
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index e6324d4d8c8..97559712b1d 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -8,7 +8,12 @@
 from io import StringIO
 from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
 from test import support
+from datetime import timedelta
 
+try:
+    import threading
+except ImportError:
+    threading = None
 
 # for pickle tests
 try:
@@ -1547,6 +1552,34 @@ def surface_gravity(self):
         self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80)
         self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6))
 
+    def test_ignore(self):
+        class Period(timedelta, Enum):
+            '''
+            different lengths of time
+            '''
+            def __new__(cls, value, period):
+                obj = timedelta.__new__(cls, value)
+                obj._value_ = value
+                obj.period = period
+                return obj
+            _ignore_ = 'Period i'
+            Period = vars()
+            for i in range(13):
+                Period['month_%d' % i] = i*30, 'month'
+            for i in range(53):
+                Period['week_%d' % i] = i*7, 'week'
+            for i in range(32):
+                Period['day_%d' % i] = i, 'day'
+            OneDay = day_1
+            OneWeek = week_1
+            OneMonth = month_1
+        self.assertFalse(hasattr(Period, '_ignore_'))
+        self.assertFalse(hasattr(Period, 'Period'))
+        self.assertFalse(hasattr(Period, 'i'))
+        self.assertTrue(isinstance(Period.day_1, timedelta))
+        self.assertTrue(Period.month_1 is Period.day_30)
+        self.assertTrue(Period.week_4 is Period.day_28)
+
     def test_nonhash_value(self):
         class AutoNumberInAList(Enum):
             def __new__(cls):
diff --git a/Misc/NEWS.d/next/Library/2018-01-18-13-47-40.bpo-31801.3UGH1h.rst b/Misc/NEWS.d/next/Library/2018-01-18-13-47-40.bpo-31801.3UGH1h.rst
new file mode 100644
index 00000000000..48043c046fa
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-01-18-13-47-40.bpo-31801.3UGH1h.rst
@@ -0,0 +1,2 @@
+Add ``_ignore_`` to ``Enum`` so temporary variables can be used during class
+construction without being turned into members.



More information about the Python-checkins mailing list