]> granicus.if.org Git - python/commitdiff
Close #18738: Route __format__ calls to mixed-in type for mixed Enums (such as IntEnum).
authorEthan Furman <ethan@stoneleaf.us>
Sun, 1 Sep 2013 02:17:41 +0000 (19:17 -0700)
committerEthan Furman <ethan@stoneleaf.us>
Sun, 1 Sep 2013 02:17:41 +0000 (19:17 -0700)
Doc/library/enum.rst
Lib/enum.py
Lib/test/test_enum.py

index 686470534d62563943073ef222c629843b83b50b..14dcfa7ad75da695db81ef3c70548f3229677336 100644 (file)
@@ -463,6 +463,12 @@ Some rules:
 3. When another data type is mixed in, the :attr:`value` attribute is *not the
    same* as the enum member itself, although it is equivalant and will compare
    equal.
+4. %-style formatting:  `%s` and `%r` call :class:`Enum`'s :meth:`__str__` and
+   :meth:`__repr__` respectively; other codes (such as `%i` or `%h` for
+   IntEnum) treat the enum member as its mixed-in type.
+5. :class:`str`.:meth:`__format__` (or :func:`format`) will use the mixed-in
+   type's :meth:`__format__`.  If the :class:`Enum`'s :func:`str` or
+   :func:`repr` is desired use the `!s` or `!r` :class:`str` format codes.
 
 
 Interesting examples
index 5722b5f7eb1ec38b7f1cdaf793e386c67518881a..8219005bb61996d82445decb3cb3aa936f6bdba8 100644 (file)
@@ -50,7 +50,6 @@ def _make_class_unpicklable(cls):
     cls.__reduce__ = _break_on_call_reduce
     cls.__module__ = '<unknown>'
 
-
 class _EnumDict(dict):
     """Keeps track of definition order of the enum items.
 
@@ -182,7 +181,7 @@ class EnumMeta(type):
 
         # double check that repr and friends are not the mixin's or various
         # things break (such as pickle)
-        for name in ('__repr__', '__str__', '__getnewargs__'):
+        for name in ('__repr__', '__str__', '__format__', '__getnewargs__'):
             class_method = getattr(enum_class, name)
             obj_method = getattr(member_type, name, None)
             enum_method = getattr(first_enum, name, None)
@@ -441,6 +440,21 @@ class Enum(metaclass=EnumMeta):
             return self is other
         return NotImplemented
 
+    def __format__(self, format_spec):
+        # mixed-in Enums should use the mixed-in type's __format__, otherwise
+        # we can get strange results with the Enum name showing up instead of
+        # the value
+
+        # pure Enum branch
+        if self._member_type_ is object:
+            cls = str
+            val = str(self)
+        # mix-in branch
+        else:
+            cls = self._member_type_
+            val = self.value
+        return cls.__format__(val, format_spec)
+
     def __getnewargs__(self):
         return (self._value_, )
 
index 71c77a0f00bfcf1eb3f073a7754c72cf7085f2c2..2a589f5a3b396483b4bb3e6746112d3dfff95db4 100644 (file)
@@ -67,6 +67,33 @@ class TestEnum(unittest.TestCase):
             WINTER = 4
         self.Season = Season
 
+        class Konstants(float, Enum):
+            E = 2.7182818
+            PI = 3.1415926
+            TAU = 2 * PI
+        self.Konstants = Konstants
+
+        class Grades(IntEnum):
+            A = 5
+            B = 4
+            C = 3
+            D = 2
+            F = 0
+        self.Grades = Grades
+
+        class Directional(str, Enum):
+            EAST = 'east'
+            WEST = 'west'
+            NORTH = 'north'
+            SOUTH = 'south'
+        self.Directional = Directional
+
+        from datetime import date
+        class Holiday(date, Enum):
+            NEW_YEAR = 2013, 1, 1
+            IDES_OF_MARCH = 2013, 3, 15
+        self.Holiday = Holiday
+
     def test_dir_on_class(self):
         Season = self.Season
         self.assertEqual(
@@ -207,6 +234,77 @@ class TestEnum(unittest.TestCase):
         self.assertIs(type(Huh.name), Huh)
         self.assertEqual(Huh.name.name, 'name')
         self.assertEqual(Huh.name.value, 1)
+
+    def test_format_enum(self):
+        Season = self.Season
+        self.assertEqual('{}'.format(Season.SPRING),
+                         '{}'.format(str(Season.SPRING)))
+        self.assertEqual( '{:}'.format(Season.SPRING),
+                          '{:}'.format(str(Season.SPRING)))
+        self.assertEqual('{:20}'.format(Season.SPRING),
+                         '{:20}'.format(str(Season.SPRING)))
+        self.assertEqual('{:^20}'.format(Season.SPRING),
+                         '{:^20}'.format(str(Season.SPRING)))
+        self.assertEqual('{:>20}'.format(Season.SPRING),
+                         '{:>20}'.format(str(Season.SPRING)))
+        self.assertEqual('{:<20}'.format(Season.SPRING),
+                         '{:<20}'.format(str(Season.SPRING)))
+
+    def test_format_enum_custom(self):
+        class TestFloat(float, Enum):
+            one = 1.0
+            two = 2.0
+            def __format__(self, spec):
+                return 'TestFloat success!'
+        self.assertEqual('{}'.format(TestFloat.one), 'TestFloat success!')
+
+    def assertFormatIsValue(self, spec, member):
+        self.assertEqual(spec.format(member), spec.format(member.value))
+
+    def test_format_enum_date(self):
+        Holiday = self.Holiday
+        self.assertFormatIsValue('{}', Holiday.IDES_OF_MARCH)
+        self.assertFormatIsValue('{:}', Holiday.IDES_OF_MARCH)
+        self.assertFormatIsValue('{:20}', Holiday.IDES_OF_MARCH)
+        self.assertFormatIsValue('{:^20}', Holiday.IDES_OF_MARCH)
+        self.assertFormatIsValue('{:>20}', Holiday.IDES_OF_MARCH)
+        self.assertFormatIsValue('{:<20}', Holiday.IDES_OF_MARCH)
+        self.assertFormatIsValue('{:%Y %m}', Holiday.IDES_OF_MARCH)
+        self.assertFormatIsValue('{:%Y %m %M:00}', Holiday.IDES_OF_MARCH)
+
+    def test_format_enum_float(self):
+        Konstants = self.Konstants
+        self.assertFormatIsValue('{}', Konstants.TAU)
+        self.assertFormatIsValue('{:}', Konstants.TAU)
+        self.assertFormatIsValue('{:20}', Konstants.TAU)
+        self.assertFormatIsValue('{:^20}', Konstants.TAU)
+        self.assertFormatIsValue('{:>20}', Konstants.TAU)
+        self.assertFormatIsValue('{:<20}', Konstants.TAU)
+        self.assertFormatIsValue('{:n}', Konstants.TAU)
+        self.assertFormatIsValue('{:5.2}', Konstants.TAU)
+        self.assertFormatIsValue('{:f}', Konstants.TAU)
+
+    def test_format_enum_int(self):
+        Grades = self.Grades
+        self.assertFormatIsValue('{}', Grades.C)
+        self.assertFormatIsValue('{:}', Grades.C)
+        self.assertFormatIsValue('{:20}', Grades.C)
+        self.assertFormatIsValue('{:^20}', Grades.C)
+        self.assertFormatIsValue('{:>20}', Grades.C)
+        self.assertFormatIsValue('{:<20}', Grades.C)
+        self.assertFormatIsValue('{:+}', Grades.C)
+        self.assertFormatIsValue('{:08X}', Grades.C)
+        self.assertFormatIsValue('{:b}', Grades.C)
+
+    def test_format_enum_str(self):
+        Directional = self.Directional
+        self.assertFormatIsValue('{}', Directional.WEST)
+        self.assertFormatIsValue('{:}', Directional.WEST)
+        self.assertFormatIsValue('{:20}', Directional.WEST)
+        self.assertFormatIsValue('{:^20}', Directional.WEST)
+        self.assertFormatIsValue('{:>20}', Directional.WEST)
+        self.assertFormatIsValue('{:<20}', Directional.WEST)
+
     def test_hash(self):
         Season = self.Season
         dates = {}
@@ -232,7 +330,7 @@ class TestEnum(unittest.TestCase):
 
     def test_floatenum_from_scratch(self):
         class phy(float, Enum):
-            pi = 3.141596
+            pi = 3.1415926
             tau = 2 * pi
         self.assertTrue(phy.pi < phy.tau)
 
@@ -240,7 +338,7 @@ class TestEnum(unittest.TestCase):
         class FloatEnum(float, Enum):
             pass
         class phy(FloatEnum):
-            pi = 3.141596
+            pi = 3.1415926
             tau = 2 * pi
         self.assertTrue(phy.pi < phy.tau)