bpo-40066: [Enum] update str() and format() output (GH-30582)

Undo rejected PEP-663 changes:

- restore `repr()` to its 3.10 status
- restore `str()` to its 3.10 status

New changes:

- `IntEnum` and `IntFlag` now leave `__str__` as the original `int.__str__` so that str() and format() return the same result
- zero-valued flags without a name have a slightly changed repr(), e.g. `repr(Color(0)) == '<Color: 0>'`
- update `dir()` for mixed-in types to return all the methods and attributes of the mixed-in type
- added `_numeric_repr_` to `Flag` to control display of unnamed values
- enums without doc strings have a more comprehensive doc string added
- `ReprEnum` added -- inheriting from this makes it so only `__repr__` is replaced, not `__str__` nor `__format__`; `IntEnum`, `IntFlag`, and `StrEnum` all inherit from `ReprEnum`
This commit is contained in:
Ethan Furman 2022-01-15 22:41:43 -08:00 committed by GitHub
parent 37eab55ac9
commit acf7403f9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 2085 additions and 2019 deletions

View File

@ -2,15 +2,10 @@
Enum HOWTO Enum HOWTO
========== ==========
:Author: Ethan Furman <ethan at stoneleaf dot us>
.. _enum-basic-tutorial: .. _enum-basic-tutorial:
.. currentmodule:: enum .. currentmodule:: enum
Basic Enum Tutorial
-------------------
An :class:`Enum` is a set of symbolic names bound to unique values. They are An :class:`Enum` is a set of symbolic names bound to unique values. They are
similar to global variables, but they offer a more useful :func:`repr()`, similar to global variables, but they offer a more useful :func:`repr()`,
grouping, type-safety, and a few other features. grouping, type-safety, and a few other features.
@ -28,6 +23,14 @@ selection of values. For example, the days of the week::
... SATURDAY = 6 ... SATURDAY = 6
... SUNDAY = 7 ... SUNDAY = 7
Or perhaps the RGB primary colors::
>>> from enum import Enum
>>> class Color(Enum):
... RED = 1
... GREEN = 2
... BLUE = 3
As you can see, creating an :class:`Enum` is as simple as writing a class that As you can see, creating an :class:`Enum` is as simple as writing a class that
inherits from :class:`Enum` itself. inherits from :class:`Enum` itself.
@ -41,13 +44,14 @@ important, but either way that value can be used to get the corresponding
member:: member::
>>> Weekday(3) >>> Weekday(3)
Weekday.WEDNESDAY <Weekday.WEDNESDAY: 3>
As you can see, the ``repr()`` of a member shows the enum name and the As you can see, the ``repr()`` of a member shows the enum name, the member name,
member name. The ``str()`` on a member shows only its name:: and the value. The ``str()`` of a member shows only the enum name and member
name::
>>> print(Weekday.THURSDAY) >>> print(Weekday.THURSDAY)
THURSDAY Weekday.THURSDAY
The *type* of an enumeration member is the enum it belongs to:: The *type* of an enumeration member is the enum it belongs to::
@ -97,8 +101,8 @@ The complete :class:`Weekday` enum now looks like this::
Now we can find out what today is! Observe:: Now we can find out what today is! Observe::
>>> from datetime import date >>> from datetime import date
>>> Weekday.from_date(date.today()) >>> Weekday.from_date(date.today()) # doctest: +SKIP
Weekday.TUESDAY <Weekday.TUESDAY: 2>
Of course, if you're reading this on some other day, you'll see that day instead. Of course, if you're reading this on some other day, you'll see that day instead.
@ -124,21 +128,21 @@ Just like the original :class:`Weekday` enum above, we can have a single selecti
>>> first_week_day = Weekday.MONDAY >>> first_week_day = Weekday.MONDAY
>>> first_week_day >>> first_week_day
Weekday.MONDAY <Weekday.MONDAY: 1>
But :class:`Flag` also allows us to combine several members into a single But :class:`Flag` also allows us to combine several members into a single
variable:: variable::
>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY >>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend >>> weekend
Weekday.SATURDAY|Weekday.SUNDAY <Weekday.SATURDAY|SUNDAY: 96>
You can even iterate over a :class:`Flag` variable:: You can even iterate over a :class:`Flag` variable::
>>> for day in weekend: >>> for day in weekend:
... print(day) ... print(day)
SATURDAY Weekday.SATURDAY
SUNDAY Weekday.SUNDAY
Okay, let's get some chores set up:: Okay, let's get some chores set up::
@ -173,6 +177,7 @@ yourself some work and use :func:`auto()` for the values::
.. _enum-advanced-tutorial: .. _enum-advanced-tutorial:
Programmatic access to enumeration members and their attributes Programmatic access to enumeration members and their attributes
--------------------------------------------------------------- ---------------------------------------------------------------
@ -181,16 +186,16 @@ situations where ``Color.RED`` won't do because the exact color is not known
at program-writing time). ``Enum`` allows such access:: at program-writing time). ``Enum`` allows such access::
>>> Color(1) >>> Color(1)
Color.RED <Color.RED: 1>
>>> Color(3) >>> Color(3)
Color.BLUE <Color.BLUE: 3>
If you want to access enum members by *name*, use item access:: If you want to access enum members by *name*, use item access::
>>> Color['RED'] >>> Color['RED']
Color.RED <Color.RED: 1>
>>> Color['GREEN'] >>> Color['GREEN']
Color.GREEN <Color.GREEN: 2>
If you have an enum member and need its :attr:`name` or :attr:`value`:: If you have an enum member and need its :attr:`name` or :attr:`value`::
@ -212,7 +217,7 @@ Having two enum members with the same name is invalid::
... ...
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: 'SQUARE' already defined as: 2 TypeError: 'SQUARE' already defined as 2
However, an enum member can have other names associated with it. Given two However, an enum member can have other names associated with it. Given two
entries ``A`` and ``B`` with the same value (and ``A`` defined first), ``B`` entries ``A`` and ``B`` with the same value (and ``A`` defined first), ``B``
@ -227,11 +232,11 @@ By-name lookup of ``B`` will also return the member ``A``::
... ALIAS_FOR_SQUARE = 2 ... ALIAS_FOR_SQUARE = 2
... ...
>>> Shape.SQUARE >>> Shape.SQUARE
Shape.SQUARE <Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE >>> Shape.ALIAS_FOR_SQUARE
Shape.SQUARE <Shape.SQUARE: 2>
>>> Shape(2) >>> Shape(2)
Shape.SQUARE <Shape.SQUARE: 2>
.. note:: .. note::
@ -299,7 +304,7 @@ Iteration
Iterating over the members of an enum does not provide the aliases:: Iterating over the members of an enum does not provide the aliases::
>>> list(Shape) >>> list(Shape)
[Shape.SQUARE, Shape.DIAMOND, Shape.CIRCLE] [<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
The special attribute ``__members__`` is a read-only ordered mapping of names The special attribute ``__members__`` is a read-only ordered mapping of names
to members. It includes all names defined in the enumeration, including the to members. It includes all names defined in the enumeration, including the
@ -308,10 +313,10 @@ aliases::
>>> for name, member in Shape.__members__.items(): >>> for name, member in Shape.__members__.items():
... name, member ... name, member
... ...
('SQUARE', Shape.SQUARE) ('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', Shape.DIAMOND) ('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', Shape.CIRCLE) ('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', Shape.SQUARE) ('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)
The ``__members__`` attribute can be used for detailed programmatic access to The ``__members__`` attribute can be used for detailed programmatic access to
the enumeration members. For example, finding all the aliases:: the enumeration members. For example, finding all the aliases::
@ -360,8 +365,8 @@ below)::
Allowed members and attributes of enumerations Allowed members and attributes of enumerations
---------------------------------------------- ----------------------------------------------
Most of the examples above use integers for enumeration values. Using integers is Most of the examples above use integers for enumeration values. Using integers
short and handy (and provided by default by the `Functional API`_), but not is short and handy (and provided by default by the `Functional API`_), but not
strictly enforced. In the vast majority of use-cases, one doesn't care what strictly enforced. In the vast majority of use-cases, one doesn't care what
the actual value of an enumeration is. But if the value *is* important, the actual value of an enumeration is. But if the value *is* important,
enumerations can have arbitrary values. enumerations can have arbitrary values.
@ -389,7 +394,7 @@ usual. If we have this enumeration::
Then:: Then::
>>> Mood.favorite_mood() >>> Mood.favorite_mood()
Mood.HAPPY <Mood.HAPPY: 3>
>>> Mood.HAPPY.describe() >>> Mood.HAPPY.describe()
('HAPPY', 3) ('HAPPY', 3)
>>> str(Mood.FUNKY) >>> str(Mood.FUNKY)
@ -425,7 +430,7 @@ any members. So this is forbidden::
... ...
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: MoreColor: cannot extend enumeration 'Color' TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>
But this is allowed:: But this is allowed::
@ -476,11 +481,9 @@ The :class:`Enum` class is callable, providing the following functional API::
>>> Animal >>> Animal
<enum 'Animal'> <enum 'Animal'>
>>> Animal.ANT >>> Animal.ANT
Animal.ANT <Animal.ANT: 1>
>>> Animal.ANT.value
1
>>> list(Animal) >>> list(Animal)
[Animal.ANT, Animal.BEE, Animal.CAT, Animal.DOG] [<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]
The semantics of this API resemble :class:`~collections.namedtuple`. The first The semantics of this API resemble :class:`~collections.namedtuple`. The first
argument of the call to :class:`Enum` is the name of the enumeration. argument of the call to :class:`Enum` is the name of the enumeration.
@ -625,16 +628,7 @@ StrEnum
The second variation of :class:`Enum` that is provided is also a subclass of The second variation of :class:`Enum` that is provided is also a subclass of
:class:`str`. Members of a :class:`StrEnum` can be compared to strings; :class:`str`. Members of a :class:`StrEnum` can be compared to strings;
by extension, string enumerations of different types can also be compared 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.
an incorrect member::
>>> from enum import StrEnum
>>> class Directions(StrEnum):
... NORTH = 'north', # notice the trailing comma
... SOUTH = 'south'
Before :class:`StrEnum`, ``Directions.NORTH`` would have been the :class:`tuple`
``('north',)``.
.. versionadded:: 3.11 .. versionadded:: 3.11
@ -645,9 +639,8 @@ 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, if possible. However, as the name implies, :class:`IntFlag` :class:`IntFlag` member, if possible. Like :class:`IntEnum`, :class:`IntFlag`
members also subclass :class:`int` and can be used wherever an :class:`int` is members are also integers and can be used wherever an :class:`int` is used.
used.
.. note:: .. note::
@ -670,7 +663,7 @@ Sample :class:`IntFlag` class::
... X = 1 ... X = 1
... ...
>>> Perm.R | Perm.W >>> Perm.R | Perm.W
Perm.R|Perm.W <Perm.R|W: 6>
>>> Perm.R + Perm.W >>> Perm.R + Perm.W
6 6
>>> RW = Perm.R | Perm.W >>> RW = Perm.R | Perm.W
@ -685,11 +678,11 @@ It is also possible to name the combinations::
... X = 1 ... X = 1
... RWX = 7 ... RWX = 7
>>> Perm.RWX >>> Perm.RWX
Perm.RWX <Perm.RWX: 7>
>>> ~Perm.RWX >>> ~Perm.RWX
Perm(0) <Perm: 0>
>>> Perm(7) >>> Perm(7)
Perm.RWX <Perm.RWX: 7>
.. note:: .. note::
@ -702,7 +695,7 @@ 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) <Perm: 0>
>>> bool(Perm.R & Perm.X) >>> bool(Perm.R & Perm.X)
False False
@ -710,7 +703,7 @@ Because :class:`IntFlag` members are also subclasses of :class:`int` they can
be combined with them (but may lose :class:`IntFlag` membership:: be combined with them (but may lose :class:`IntFlag` membership::
>>> Perm.X | 4 >>> Perm.X | 4
Perm.R|Perm.X <Perm.R|X: 5>
>>> Perm.X | 8 >>> Perm.X | 8
9 9
@ -726,7 +719,7 @@ be combined with them (but may lose :class:`IntFlag` membership::
:class:`IntFlag` members can also be iterated over:: :class:`IntFlag` members can also be iterated over::
>>> list(RW) >>> list(RW)
[Perm.R, Perm.W] [<Perm.R: 4>, <Perm.W: 2>]
.. versionadded:: 3.11 .. versionadded:: 3.11
@ -753,7 +746,7 @@ flags being set, the boolean evaluation is :data:`False`::
... GREEN = auto() ... GREEN = auto()
... ...
>>> Color.RED & Color.GREEN >>> Color.RED & Color.GREEN
Color(0) <Color: 0>
>>> bool(Color.RED & Color.GREEN) >>> bool(Color.RED & Color.GREEN)
False False
@ -767,7 +760,7 @@ while combinations of flags won't::
... WHITE = RED | BLUE | GREEN ... WHITE = RED | BLUE | GREEN
... ...
>>> Color.WHITE >>> Color.WHITE
Color.WHITE <Color.WHITE: 7>
Giving a name to the "no flags set" condition does not change its boolean Giving a name to the "no flags set" condition does not change its boolean
value:: value::
@ -779,7 +772,7 @@ value::
... GREEN = auto() ... GREEN = auto()
... ...
>>> Color.BLACK >>> Color.BLACK
Color.BLACK <Color.BLACK: 0>
>>> bool(Color.BLACK) >>> bool(Color.BLACK)
False False
@ -787,7 +780,7 @@ value::
>>> purple = Color.RED | Color.BLUE >>> purple = Color.RED | Color.BLUE
>>> list(purple) >>> list(purple)
[Color.RED, Color.BLUE] [<Color.RED: 1>, <Color.BLUE: 2>]
.. versionadded:: 3.11 .. versionadded:: 3.11
@ -812,16 +805,16 @@ simple to implement independently::
pass pass
This demonstrates how similar derived enumerations can be defined; for example This demonstrates how similar derived enumerations can be defined; for example
a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`. a :class:`FloatEnum` that mixes in :class:`float` instead of :class:`int`.
Some rules: Some rules:
1. When subclassing :class:`Enum`, mix-in types must appear before 1. When subclassing :class:`Enum`, mix-in types must appear before
:class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum` :class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum`
example above. example above.
2. Mix-in types must be subclassable. For example, 2. Mix-in types must be subclassable. For example, :class:`bool` and
:class:`bool` and :class:`range` are not subclassable :class:`range` are not subclassable and will throw an error during Enum
and will throw an error during Enum creation if used as the mix-in type. creation if used as the mix-in type.
3. While :class:`Enum` can have members of any type, once you mix in an 3. While :class:`Enum` can have members of any type, once you mix in an
additional type, all the members must have values of that type, e.g. additional type, all the members must have values of that type, e.g.
:class:`int` above. This restriction does not apply to mix-ins which only :class:`int` above. This restriction does not apply to mix-ins which only
@ -829,15 +822,18 @@ Some rules:
4. When another data type is mixed in, the :attr:`value` attribute is *not the 4. When another data type is mixed in, the :attr:`value` attribute is *not the
same* as the enum member itself, although it is equivalent and will compare same* as the enum member itself, although it is equivalent and will compare
equal. equal.
5. %-style formatting: `%s` and `%r` call the :class:`Enum` class's 5. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's
:meth:`__str__` and :meth:`__repr__` respectively; other codes (such as :meth:`__str__` and :meth:`__repr__` respectively; other codes (such as
`%i` or `%h` for IntEnum) treat the enum member as its mixed-in type. ``%i`` or ``%h`` for IntEnum) treat the enum member as its mixed-in type.
6. :ref:`Formatted string literals <f-strings>`, :meth:`str.format`, 6. :ref:`Formatted string literals <f-strings>`, :meth:`str.format`,
and :func:`format` will use the mixed-in type's :meth:`__format__` and :func:`format` will use the enum's :meth:`__str__` method.
unless :meth:`__str__` or :meth:`__format__` is overridden in the subclass,
in which case the overridden methods or :class:`Enum` methods will be used. .. note::
Use the !s and !r format codes to force usage of the :class:`Enum` class's
:meth:`__str__` and :meth:`__repr__` methods. Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are
designed to be drop-in replacements for existing constants, their
:meth:`__str__` method has been reset to their data types
:meth:`__str__` method.
When to use :meth:`__new__` vs. :meth:`__init__` When to use :meth:`__new__` vs. :meth:`__init__`
------------------------------------------------ ------------------------------------------------
@ -866,10 +862,10 @@ want one of them to be the value::
... ...
>>> print(Coordinate['PY']) >>> print(Coordinate['PY'])
PY Coordinate.PY
>>> print(Coordinate(3)) >>> print(Coordinate(3))
VY Coordinate.VY
Finer Points Finer Points
@ -949,35 +945,36 @@ but remain normal attributes.
"""""""""""""""""""" """"""""""""""""""""
Enum members are instances of their enum class, and are normally accessed as Enum members are instances of their enum class, and are normally accessed as
``EnumClass.member``. In Python versions ``3.5`` to ``3.9`` you could access ``EnumClass.member``. In Python versions ``3.5`` to ``3.10`` you could access
members from other members -- this practice was discouraged, and in ``3.12`` members from other members -- this practice was discouraged, and in ``3.11``
:class:`Enum` will return to not allowing it, while in ``3.10`` and ``3.11`` :class:`Enum` returns to not allowing it::
it will raise a :exc:`DeprecationWarning`::
>>> class FieldTypes(Enum): >>> class FieldTypes(Enum):
... name = 0 ... name = 0
... value = 1 ... value = 1
... size = 2 ... size = 2
... ...
>>> FieldTypes.value.size # doctest: +SKIP >>> FieldTypes.value.size
DeprecationWarning: accessing one member from another is not supported, Traceback (most recent call last):
and will be disabled in 3.12 ...
<FieldTypes.size: 2> AttributeError: <enum 'FieldTypes'> member has no attribute 'size'
.. versionchanged:: 3.5 .. versionchanged:: 3.5
.. versionchanged:: 3.11
Creating members that are mixed with other data types Creating members that are mixed with other data types
""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""
When subclassing other data types, such as :class:`int` or :class:`str`, with When subclassing other data types, such as :class:`int` or :class:`str`, with
an :class:`Enum`, all values after the `=` are passed to that data type's an :class:`Enum`, all values after the ``=`` are passed to that data type's
constructor. For example:: constructor. For example::
>>> class MyEnum(IntEnum): >>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer
... example = '11', 16 # '11' will be interpreted as a hexadecimal ... example = '11', 16 # so x='11' and base=16
... # number ...
>>> MyEnum.example.value >>> MyEnum.example.value # and hex(11) is...
17 17
@ -1000,13 +997,12 @@ Plain :class:`Enum` classes always evaluate as :data:`True`.
""""""""""""""""""""""""""""" """""""""""""""""""""""""""""
If you give your enum subclass extra methods, like the `Planet`_ If you give your enum subclass extra methods, like the `Planet`_
class below, those methods will show up in a :func:`dir` of the member and the class below, those methods will show up in a :func:`dir` of the member,
class. Attributes defined in an :func:`__init__` method will only show up in a but not of the class::
:func:`dir` of the member::
>>> dir(Planet) >>> dir(Planet) # doctest: +SKIP
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__init__', '__members__', '__module__', 'surface_gravity'] ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH) >>> dir(Planet.EARTH) # doctest: +SKIP
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value'] ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
@ -1025,19 +1021,10 @@ are comprised of a single bit::
... CYAN = GREEN | BLUE ... CYAN = GREEN | BLUE
... ...
>>> Color(3) # named combination >>> Color(3) # named combination
Color.YELLOW <Color.YELLOW: 3>
>>> Color(7) # not named combination >>> Color(7) # not named combination
Color.RED|Color.GREEN|Color.BLUE <Color.RED|GREEN|BLUE: 7>
``StrEnum`` and :meth:`str.__str__`
"""""""""""""""""""""""""""""""""""
An important difference between :class:`StrEnum` and other Enums is the
:meth:`__str__` method; because :class:`StrEnum` members are strings, some
parts of Python will read the string data directly, while others will call
:meth:`str()`. To make those two operations have the same result,
:meth:`StrEnum.__str__` will be the same as :meth:`str.__str__` so that
``str(StrEnum.member) == StrEnum.member`` is true.
``Flag`` and ``IntFlag`` minutia ``Flag`` and ``IntFlag`` minutia
"""""""""""""""""""""""""""""""" """"""""""""""""""""""""""""""""
@ -1060,16 +1047,16 @@ the following are true:
- only canonical flags are returned during iteration:: - only canonical flags are returned during iteration::
>>> list(Color.WHITE) >>> list(Color.WHITE)
[Color.RED, Color.GREEN, Color.BLUE] [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
- negating a flag or flag set returns a new flag/flag set with the - negating a flag or flag set returns a new flag/flag set with the
corresponding positive integer value:: corresponding positive integer value::
>>> Color.BLUE >>> Color.BLUE
Color.BLUE <Color.BLUE: 4>
>>> ~Color.BLUE >>> ~Color.BLUE
Color.RED|Color.GREEN <Color.RED|GREEN: 3>
- names of pseudo-flags are constructed from their members' names:: - names of pseudo-flags are constructed from their members' names::
@ -1079,25 +1066,29 @@ the following are true:
- multi-bit flags, aka aliases, can be returned from operations:: - multi-bit flags, aka aliases, can be returned from operations::
>>> Color.RED | Color.BLUE >>> Color.RED | Color.BLUE
Color.PURPLE <Color.PURPLE: 5>
>>> Color(7) # or Color(-1) >>> Color(7) # or Color(-1)
Color.WHITE <Color.WHITE: 7>
>>> Color(0) >>> Color(0)
Color.BLACK <Color.BLACK: 0>
- membership / containment checking has changed slightly -- zero-valued flags - membership / containment checking: zero-valued flags are always considered
are never considered to be contained:: to be contained::
>>> Color.BLACK in Color.WHITE >>> Color.BLACK in Color.WHITE
False True
otherwise, if all bits of one flag are in the other flag, True is returned:: otherwise, only if all bits of one flag are in the other flag will True
be returned::
>>> Color.PURPLE in Color.WHITE >>> Color.PURPLE in Color.WHITE
True True
>>> Color.GREEN in Color.PURPLE
False
There is a new boundary mechanism that controls how out-of-range / invalid There is a new boundary mechanism that controls how out-of-range / invalid
bits are handled: ``STRICT``, ``CONFORM``, ``EJECT``, and ``KEEP``: bits are handled: ``STRICT``, ``CONFORM``, ``EJECT``, and ``KEEP``:
@ -1181,7 +1172,7 @@ Using :class:`auto` would look like::
... GREEN = auto() ... GREEN = auto()
... ...
>>> Color.GREEN >>> Color.GREEN
<Color.GREEN> <Color.GREEN: 3>
Using :class:`object` Using :class:`object`
@ -1194,10 +1185,24 @@ Using :class:`object` would look like::
... GREEN = object() ... GREEN = object()
... BLUE = object() ... BLUE = object()
... ...
>>> Color.GREEN # doctest: +SKIP
<Color.GREEN: <object object at 0x...>>
This is also a good example of why you might want to write your own
:meth:`__repr__`::
>>> class Color(Enum):
... RED = object()
... GREEN = object()
... BLUE = object()
... def __repr__(self):
... return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN >>> Color.GREEN
<Color.GREEN> <Color.GREEN>
Using a descriptive string Using a descriptive string
"""""""""""""""""""""""""" """"""""""""""""""""""""""
@ -1209,9 +1214,7 @@ Using a string as the value would look like::
... BLUE = 'too fast!' ... BLUE = 'too fast!'
... ...
>>> Color.GREEN >>> Color.GREEN
<Color.GREEN> <Color.GREEN: 'go'>
>>> Color.GREEN.value
'go'
Using a custom :meth:`__new__` Using a custom :meth:`__new__`
@ -1232,9 +1235,7 @@ Using an auto-numbering :meth:`__new__` would look like::
... BLUE = () ... BLUE = ()
... ...
>>> Color.GREEN >>> Color.GREEN
<Color.GREEN> <Color.GREEN: 2>
>>> Color.GREEN.value
2
To make a more general purpose ``AutoNumber``, add ``*args`` to the signature:: To make a more general purpose ``AutoNumber``, add ``*args`` to the signature::
@ -1257,7 +1258,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> <Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone >>> Swatch.SEA_GREEN.pantone
'1246' '1246'
>>> Swatch.BLEACHED_CORAL.pantone >>> Swatch.BLEACHED_CORAL.pantone
@ -1384,30 +1385,9 @@ An example to show the :attr:`_ignore_` attribute in use::
... Period['day_%d' % i] = i ... Period['day_%d' % i] = i
... ...
>>> list(Period)[:2] >>> list(Period)[:2]
[Period.day_0, Period.day_1] [<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:] >>> list(Period)[-2:]
[Period.day_365, Period.day_366] [<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
Conforming input to Flag
^^^^^^^^^^^^^^^^^^^^^^^^
To create a :class:`Flag` enum that is more resilient to out-of-bounds results
from mathematical operations, you can use the :attr:`FlagBoundary.CONFORM`
setting::
>>> from enum import Flag, CONFORM, auto
>>> class Weekday(Flag, boundary=CONFORM):
... MONDAY = auto()
... TUESDAY = auto()
... WEDNESDAY = auto()
... THURSDAY = auto()
... FRIDAY = auto()
... SATURDAY = auto()
... SUNDAY = auto()
>>> today = Weekday.TUESDAY
>>> Weekday(today + 22) # what day is three weeks from tomorrow?
>>> Weekday.WEDNESDAY
.. _enumtype-examples: .. _enumtype-examples:

View File

@ -31,7 +31,7 @@ An enumeration:
* uses *call* syntax to return members by value * uses *call* syntax to return members by value
* uses *index* syntax to return members by name * uses *index* syntax to return members by name
Enumerations are created either by using the :keyword:`class` syntax, or by Enumerations are created either by using :keyword:`class` syntax, or by
using function-call syntax:: using function-call syntax::
>>> from enum import Enum >>> from enum import Enum
@ -45,7 +45,7 @@ using function-call syntax::
>>> # functional syntax >>> # functional syntax
>>> Color = Enum('Color', ['RED', 'GREEN', 'BLUE']) >>> Color = Enum('Color', ['RED', 'GREEN', 'BLUE'])
Even though we can use the :keyword:`class` syntax to create Enums, Enums Even though we can use :keyword:`class` syntax to create Enums, Enums
are not normal Python classes. See are not normal Python classes. See
:ref:`How are Enums different? <enum-class-differences>` for more details. :ref:`How are Enums different? <enum-class-differences>` for more details.
@ -53,7 +53,7 @@ are not normal Python classes. See
- The class :class:`Color` is an *enumeration* (or *enum*) - The class :class:`Color` is an *enumeration* (or *enum*)
- The attributes :attr:`Color.RED`, :attr:`Color.GREEN`, etc., are - The attributes :attr:`Color.RED`, :attr:`Color.GREEN`, etc., are
*enumeration members* (or *enum members*) and are functionally constants. *enumeration members* (or *members*) and are functionally constants.
- The enum members have *names* and *values* (the name of - The enum members have *names* and *values* (the name of
:attr:`Color.RED` is ``RED``, the value of :attr:`Color.BLUE` is :attr:`Color.RED` is ``RED``, the value of :attr:`Color.BLUE` is
``3``, etc.) ``3``, etc.)
@ -110,15 +110,10 @@ Module Contents
:class:`StrEnum` defaults to the lower-cased version of the member name, :class:`StrEnum` defaults to the lower-cased version of the member name,
while other Enums default to 1 and increase from there. while other Enums default to 1 and increase from there.
:func:`global_enum` :func:`property`
:class:`Enum` class decorator to apply the appropriate global `__repr__`,
and export its members into the global name space.
:func:`.property`
Allows :class:`Enum` members to have attributes without conflicting with Allows :class:`Enum` members to have attributes without conflicting with
other members' names. member names.
:func:`unique` :func:`unique`
@ -131,7 +126,7 @@ Module Contents
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto`` .. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``FlagBoundary`` .. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``FlagBoundary``, ``property``
--------------- ---------------
@ -145,6 +140,11 @@ Data Types
to subclass *EnumType* -- see :ref:`Subclassing EnumType <enumtype-examples>` to subclass *EnumType* -- see :ref:`Subclassing EnumType <enumtype-examples>`
for details. for details.
*EnumType* is responsible for setting the correct :meth:`__repr__`,
:meth:`__str__`, :meth:`__format__`, and :meth:`__reduce__` methods on the
final *enum*, as well as creating the enum members, properly handling
duplicates, providing iteration over the enum class, etc.
.. method:: EnumType.__contains__(cls, member) .. method:: EnumType.__contains__(cls, member)
Returns ``True`` if member belongs to the ``cls``:: Returns ``True`` if member belongs to the ``cls``::
@ -162,32 +162,31 @@ Data Types
.. method:: EnumType.__dir__(cls) .. method:: EnumType.__dir__(cls)
Returns ``['__class__', '__doc__', '__members__', '__module__']`` and the Returns ``['__class__', '__doc__', '__members__', '__module__']`` and the
names of the members in ``cls``. User-defined methods and methods from names of the members in *cls*::
mixin classes will also be included::
>>> dir(Color) >>> dir(Color)
['BLUE', 'GREEN', 'RED', '__class__', '__doc__', '__members__', '__module__'] ['BLUE', 'GREEN', 'RED', '__class__', '__contains__', '__doc__', '__getitem__', '__init_subclass__', '__iter__', '__len__', '__members__', '__module__', '__name__', '__qualname__']
.. method:: EnumType.__getattr__(cls, name) .. method:: EnumType.__getattr__(cls, name)
Returns the Enum member in *cls* matching *name*, or raises an :exc:`AttributeError`:: Returns the Enum member in *cls* matching *name*, or raises an :exc:`AttributeError`::
>>> Color.GREEN >>> Color.GREEN
Color.GREEN <Color.GREEN: 2>
.. method:: EnumType.__getitem__(cls, name) .. method:: EnumType.__getitem__(cls, name)
Returns the Enum member in *cls* matching *name*, or raises a :exc:`KeyError`:: Returns the Enum member in *cls* matching *name*, or raises an :exc:`KeyError`::
>>> Color['BLUE'] >>> Color['BLUE']
Color.BLUE <Color.BLUE: 3>
.. method:: EnumType.__iter__(cls) .. method:: EnumType.__iter__(cls)
Returns each member in *cls* in definition order:: Returns each member in *cls* in definition order::
>>> list(Color) >>> list(Color)
[Color.RED, Color.GREEN, Color.BLUE] [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>]
.. method:: EnumType.__len__(cls) .. method:: EnumType.__len__(cls)
@ -201,7 +200,7 @@ Data Types
Returns each member in *cls* in reverse definition order:: Returns each member in *cls* in reverse definition order::
>>> list(reversed(Color)) >>> list(reversed(Color))
[Color.BLUE, Color.GREEN, Color.RED] [<Color.BLUE: 3>, <Color.GREEN: 2>, <Color.RED: 1>]
.. class:: Enum .. class:: Enum
@ -232,7 +231,7 @@ Data Types
.. attribute:: Enum._ignore_ .. attribute:: Enum._ignore_
``_ignore_`` is only used during creation and is removed from the ``_ignore_`` is only used during creation and is removed from the
enumeration once that is complete. enumeration once creation is complete.
``_ignore_`` is a list of names that will not become members, and whose ``_ignore_`` is a list of names that will not become members, and whose
names will also be removed from the completed enumeration. See names will also be removed from the completed enumeration. See
@ -261,7 +260,7 @@ Data Types
.. method:: Enum.__dir__(self) .. method:: Enum.__dir__(self)
Returns ``['__class__', '__doc__', '__module__', 'name', 'value']`` and Returns ``['__class__', '__doc__', '__module__', 'name', 'value']`` and
any public methods defined on ``self.__class__`` or a mixin class:: any public methods defined on *self.__class__*::
>>> from datetime import date >>> from datetime import date
>>> class Weekday(Enum): >>> class Weekday(Enum):
@ -276,7 +275,7 @@ Data Types
... def today(cls): ... def today(cls):
... print('today is %s' % cls(date.today().isoweekday()).name) ... print('today is %s' % cls(date.today().isoweekday()).name)
>>> dir(Weekday.SATURDAY) >>> dir(Weekday.SATURDAY)
['__class__', '__doc__', '__module__', 'name', 'today', 'value'] ['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'today', 'value']
.. method:: Enum._generate_next_value_(name, start, count, last_values) .. method:: Enum._generate_next_value_(name, start, count, last_values)
@ -298,6 +297,11 @@ Data Types
>>> PowersOfThree.SECOND.value >>> PowersOfThree.SECOND.value
6 6
.. method:: Enum.__init_subclass__(cls, \**kwds)
A *classmethod* that is used to further configure subsequent subclasses.
By default, does nothing.
.. method:: Enum._missing_(cls, value) .. method:: Enum._missing_(cls, value)
A *classmethod* for looking up values not found in *cls*. By default it A *classmethod* for looking up values not found in *cls*. By default it
@ -317,37 +321,50 @@ Data Types
>>> Build.DEBUG.value >>> Build.DEBUG.value
'debug' 'debug'
>>> Build('deBUG') >>> Build('deBUG')
Build.DEBUG <Build.DEBUG: 'debug'>
.. method:: Enum.__repr__(self) .. method:: Enum.__repr__(self)
Returns the string used for *repr()* calls. By default, returns the Returns the string used for *repr()* calls. By default, returns the
*Enum* name and the member name, but can be overridden:: *Enum* name, member name, and value, but can be overridden::
>>> class OldStyle(Enum): >>> class OtherStyle(Enum):
... RETRO = auto() ... ALTERNATE = auto()
... OLD_SCHOOl = auto() ... OTHER = auto()
... YESTERYEAR = auto() ... SOMETHING_ELSE = auto()
... def __repr__(self): ... def __repr__(self):
... cls_name = self.__class__.__name__ ... cls_name = self.__class__.__name__
... return f'<{cls_name}.{self.name}: {self.value}>' ... return f'{cls_name}.{self.name}'
>>> OldStyle.RETRO >>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}"
<OldStyle.RETRO: 1> (OtherStyle.ALTERNATE, 'OtherStyle.ALTERNATE', 'OtherStyle.ALTERNATE')
.. method:: Enum.__str__(self) .. method:: Enum.__str__(self)
Returns the string used for *str()* calls. By default, returns the Returns the string used for *str()* calls. By default, returns the
member name, but can be overridden:: *Enum* name and member name, but can be overridden::
>>> class OldStyle(Enum): >>> class OtherStyle(Enum):
... RETRO = auto() ... ALTERNATE = auto()
... OLD_SCHOOl = auto() ... OTHER = auto()
... YESTERYEAR = auto() ... SOMETHING_ELSE = auto()
... def __str__(self): ... def __str__(self):
... cls_name = self.__class__.__name__ ... return f'{self.name}'
... return f'{cls_name}.{self.name}' >>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}"
>>> OldStyle.RETRO (<OtherStyle.ALTERNATE: 1>, 'ALTERNATE', 'ALTERNATE')
OldStyle.RETRO
.. method:: Enum.__format__(self)
Returns the string used for *format()* and *f-string* calls. By default,
returns :meth:`__str__` returns, but can be overridden::
>>> class OtherStyle(Enum):
... ALTERNATE = auto()
... OTHER = auto()
... SOMETHING_ELSE = auto()
... def __format__(self, spec):
... return f'{self.name}'
>>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}"
(<OtherStyle.ALTERNATE: 1>, 'OtherStyle.ALTERNATE', 'ALTERNATE')
.. note:: .. note::
@ -367,7 +384,7 @@ Data Types
... TWO = 2 ... TWO = 2
... THREE = 3 ... THREE = 3
>>> Numbers.THREE >>> Numbers.THREE
Numbers.THREE <Numbers.THREE: 3>
>>> Numbers.ONE + Numbers.TWO >>> Numbers.ONE + Numbers.TWO
3 3
>>> Numbers.THREE + 5 >>> Numbers.THREE + 5
@ -377,8 +394,12 @@ Data Types
.. note:: .. note::
Using :class:`auto` with :class:`IntEnum` results in integers of increasing value, Using :class:`auto` with :class:`IntEnum` results in integers of increasing
starting with ``1``. value, starting with ``1``.
.. versionchanged:: 3.11 :meth:`__str__` is now :func:`int.__str__` to
better support the *replacement of existing constants* use-case.
:meth:`__format__` was already :func:`int.__format__` for that same reason.
.. class:: StrEnum .. class:: StrEnum
@ -392,11 +413,14 @@ Data Types
instead of ``isinstance(str, unknown)``), and in those locations you instead of ``isinstance(str, unknown)``), and in those locations you
will need to use ``str(StrEnum.member)``. will need to use ``str(StrEnum.member)``.
.. note:: .. note::
Using :class:`auto` with :class:`StrEnum` results in values of the member name, Using :class:`auto` with :class:`StrEnum` results in the lower-cased member
lower-cased. name as the value.
.. note:: :meth:`__str__` is :func:`str.__str__` to better support the
*replacement of existing constants* use-case. :meth:`__format__` is likewise
:func:`int.__format__` for that same reason.
.. versionadded:: 3.11 .. versionadded:: 3.11
@ -431,9 +455,9 @@ Data Types
Returns all contained members:: Returns all contained members::
>>> list(Color.RED) >>> list(Color.RED)
[Color.RED] [<Color.RED: 1>]
>>> list(purple) >>> list(purple)
[Color.RED, Color.BLUE] [<Color.RED: 1>, <Color.BLUE: 4>]
.. method:: __len__(self): .. method:: __len__(self):
@ -461,42 +485,52 @@ Data Types
Returns current flag binary or'ed with other:: Returns current flag binary or'ed with other::
>>> Color.RED | Color.GREEN >>> Color.RED | Color.GREEN
Color.RED|Color.GREEN <Color.RED|GREEN: 3>
.. method:: __and__(self, other) .. method:: __and__(self, other)
Returns current flag binary and'ed with other:: Returns current flag binary and'ed with other::
>>> purple & white >>> purple & white
Color.RED|Color.BLUE <Color.RED|BLUE: 5>
>>> purple & Color.GREEN >>> purple & Color.GREEN
0x0 <Color: 0>
.. method:: __xor__(self, other) .. method:: __xor__(self, other)
Returns current flag binary xor'ed with other:: Returns current flag binary xor'ed with other::
>>> purple ^ white >>> purple ^ white
Color.GREEN <Color.GREEN: 2>
>>> purple ^ Color.GREEN >>> purple ^ Color.GREEN
Color.RED|Color.GREEN|Color.BLUE <Color.RED|GREEN|BLUE: 7>
.. method:: __invert__(self): .. method:: __invert__(self):
Returns all the flags in *type(self)* that are not in self:: Returns all the flags in *type(self)* that are not in self::
>>> ~white >>> ~white
0x0 <Color: 0>
>>> ~purple >>> ~purple
Color.GREEN <Color.GREEN: 2>
>>> ~Color.RED >>> ~Color.RED
Color.GREEN|Color.BLUE <Color.GREEN|BLUE: 6>
.. method:: _numeric_repr_
Function used to format any remaining unnamed numeric values. Default is
the value's repr; common choices are :func:`hex` and :func:`oct`.
.. note:: .. note::
Using :class:`auto` with :class:`Flag` results in integers that are powers Using :class:`auto` with :class:`Flag` results in integers that are powers
of two, starting with ``1``. of two, starting with ``1``.
.. versionchanged:: 3.11 The *repr()* of zero-valued flags has changed. It
is now::
>>> Color(0)
<Color: 0>
.. class:: IntFlag .. class:: IntFlag
@ -509,9 +543,9 @@ Data Types
... GREEN = auto() ... GREEN = auto()
... BLUE = auto() ... BLUE = auto()
>>> Color.RED & 2 >>> Color.RED & 2
0x0 <Color: 0>
>>> Color.RED | 2 >>> Color.RED | 2
Color.RED|Color.GREEN <Color.RED|GREEN: 3>
If any integer operation is performed with an *IntFlag* member, the result is If any integer operation is performed with an *IntFlag* member, the result is
not an *IntFlag*:: not an *IntFlag*::
@ -524,15 +558,25 @@ Data Types
* the result is a valid *IntFlag*: an *IntFlag* is returned * the result is a valid *IntFlag*: an *IntFlag* is returned
* the result is not a valid *IntFlag*: the result depends on the *FlagBoundary* setting * the result is not a valid *IntFlag*: the result depends on the *FlagBoundary* setting
The *repr()* of unnamed zero-valued flags has changed. It is now:
>>> Color(0)
<Color: 0>
.. note:: .. note::
Using :class:`auto` with :class:`IntFlag` results in integers that are powers Using :class:`auto` with :class:`IntFlag` results in integers that are powers
of two, starting with ``1``. of two, starting with ``1``.
.. versionchanged:: 3.11 :meth:`__str__` is now :func:`int.__str__` to
better support the *replacement of existing constants* use-case.
:meth:`__format__` was already :func:`int.__format__` for that same reason.
.. class:: EnumCheck .. class:: EnumCheck
*EnumCheck* contains the options used by the :func:`verify` decorator to ensure *EnumCheck* contains the options used by the :func:`verify` decorator to ensure
various constraints; failed constraints result in a :exc:`TypeError`. various constraints; failed constraints result in a :exc:`ValueError`.
.. attribute:: UNIQUE .. attribute:: UNIQUE
@ -606,7 +650,7 @@ Data Types
>>> StrictFlag(2**2 + 2**4) >>> StrictFlag(2**2 + 2**4)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: StrictFlag: invalid value: 20 ValueError: <flag 'StrictFlag'> invalid value 20
given 0b0 10100 given 0b0 10100
allowed 0b0 00111 allowed 0b0 00111
@ -621,7 +665,7 @@ Data Types
... GREEN = auto() ... GREEN = auto()
... BLUE = auto() ... BLUE = auto()
>>> ConformFlag(2**2 + 2**4) >>> ConformFlag(2**2 + 2**4)
ConformFlag.BLUE <ConformFlag.BLUE: 4>
.. attribute:: EJECT .. attribute:: EJECT
@ -647,12 +691,52 @@ Data Types
... GREEN = auto() ... GREEN = auto()
... BLUE = auto() ... BLUE = auto()
>>> KeepFlag(2**2 + 2**4) >>> KeepFlag(2**2 + 2**4)
KeepFlag.BLUE|0x10 <KeepFlag.BLUE|16: 20>
.. versionadded:: 3.11 .. versionadded:: 3.11
--------------- ---------------
Supported ``__dunder__`` names
""""""""""""""""""""""""""""""
:attr:`__members__` is a read-only ordered mapping of ``member_name``:``member``
items. It is only available on the class.
: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.
Supported ``_sunder_`` names
""""""""""""""""""""""""""""
- ``_name_`` -- name of the member
- ``_value_`` -- value of the member; can be set / modified in ``__new__``
- ``_missing_`` -- a lookup function used when a value is not found; may be
overridden
- ``_ignore_`` -- a list of names, either as a :class:`list` or a :class:`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 to get an appropriate value for an enum
member; may be overridden
.. note::
For standard :class:`Enum` classes the next value chosen is the last value seen
incremented by one.
For :class:`Flag` 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.7 ``_ignore_``
---------------
Utilities and Decorators Utilities and Decorators
------------------------ ------------------------
@ -668,15 +752,6 @@ Utilities and Decorators
``_generate_next_value_`` can be overridden to customize the values used by ``_generate_next_value_`` can be overridden to customize the values used by
*auto*. *auto*.
.. decorator:: global_enum
A :keyword:`class` decorator specifically for enumerations. It replaces the
:meth:`__repr__` method with one that shows *module_name*.*member_name*. It
also injects the members, and their aliases, into the global namespace they
were defined in.
.. versionadded:: 3.11
.. decorator:: property .. decorator:: property
A decorator similar to the built-in *property*, but specifically for A decorator similar to the built-in *property*, but specifically for
@ -726,14 +801,20 @@ Notes
These three enum types are designed to be drop-in replacements for existing These three enum types are designed to be drop-in replacements for existing
integer- and string-based values; as such, they have extra limitations: integer- and string-based values; as such, they have extra limitations:
- ``format()`` will use the value of the enum member, unless ``__str__`` - ``__str__`` uses the value and not the name of the enum member
has been overridden
- ``StrEnum.__str__`` uses the value and not the name of the enum member - ``__format__``, because it uses ``__str__``, will also use the value of
the enum member instead of its name
If you do not need/want those limitations, you can create your own base If you do not need/want those limitations, you can either create your own
class by mixing in the ``int`` or ``str`` type yourself:: base class by mixing in the ``int`` or ``str`` type yourself::
>>> from enum import Enum >>> from enum import Enum
>>> class MyIntEnum(int, Enum): >>> class MyIntEnum(int, Enum):
... pass ... pass
or you can reassign the appropriate :meth:`str`, etc., in your enum::
>>> from enum import IntEnum
>>> class MyIntEnum(IntEnum):
... __str__ = IntEnum.__str__

View File

@ -2070,7 +2070,7 @@ to speed up repeated connections from the same clients.
:attr:`SSLContext.verify_flags` returns :class:`VerifyFlags` flags: :attr:`SSLContext.verify_flags` returns :class:`VerifyFlags` flags:
>>> ssl.create_default_context().verify_flags # doctest: +SKIP >>> ssl.create_default_context().verify_flags # doctest: +SKIP
ssl.VERIFY_X509_TRUSTED_FIRST <VerifyFlags.VERIFY_X509_TRUSTED_FIRST: 32768>
.. attribute:: SSLContext.verify_mode .. attribute:: SSLContext.verify_mode
@ -2082,7 +2082,7 @@ to speed up repeated connections from the same clients.
:attr:`SSLContext.verify_mode` returns :class:`VerifyMode` enum: :attr:`SSLContext.verify_mode` returns :class:`VerifyMode` enum:
>>> ssl.create_default_context().verify_mode >>> ssl.create_default_context().verify_mode
ssl.CERT_REQUIRED <VerifyMode.CERT_REQUIRED: 2>
.. index:: single: certificates .. index:: single: certificates

View File

@ -1,16 +1,16 @@
import sys import sys
import builtins as bltns
from types import MappingProxyType, DynamicClassAttribute from types import MappingProxyType, DynamicClassAttribute
from operator import or_ as _or_ from operator import or_ as _or_
from functools import reduce from functools import reduce
from builtins import property as _bltin_property, bin as _bltin_bin
__all__ = [ __all__ = [
'EnumType', 'EnumMeta', 'EnumType', 'EnumMeta',
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum',
'auto', 'unique', 'property', 'verify', 'auto', 'unique', 'property', 'verify',
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP', 'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
'global_flag_repr', 'global_enum_repr', 'global_enum', 'global_flag_repr', 'global_enum_repr', 'global_str', 'global_enum',
'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE', 'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE',
] ]
@ -18,7 +18,7 @@ __all__ = [
# Dummy value for Enum and Flag as there are explicit checks for them # Dummy value for Enum and Flag as there are explicit checks for them
# before they have been created. # before they have been created.
# This is also why there are checks in EnumType like `if Enum is not None` # This is also why there are checks in EnumType like `if Enum is not None`
Enum = Flag = EJECT = None Enum = Flag = EJECT = _stdlib_enums = ReprEnum = None
def _is_descriptor(obj): def _is_descriptor(obj):
""" """
@ -116,9 +116,9 @@ def bin(num, max_bits=None):
ceiling = 2 ** (num).bit_length() ceiling = 2 ** (num).bit_length()
if num >= 0: if num >= 0:
s = _bltin_bin(num + ceiling).replace('1', '0', 1) s = bltns.bin(num + ceiling).replace('1', '0', 1)
else: else:
s = _bltin_bin(~num ^ (ceiling - 1) + ceiling) s = bltns.bin(~num ^ (ceiling - 1) + ceiling)
sign = s[:3] sign = s[:3]
digits = s[3:] digits = s[3:]
if max_bits is not None: if max_bits is not None:
@ -126,6 +126,19 @@ def bin(num, max_bits=None):
digits = (sign[-1] * max_bits + digits)[-max_bits:] digits = (sign[-1] * max_bits + digits)[-max_bits:]
return "%s %s" % (sign, digits) return "%s %s" % (sign, digits)
def _dedent(text):
"""
Like textwrap.dedent. Rewritten because we cannot import textwrap.
"""
lines = text.split('\n')
blanks = 0
for i, ch in enumerate(lines[0]):
if ch != ' ':
break
for j, l in enumerate(lines):
lines[j] = l[i:]
return '\n'.join(lines)
_auto_null = object() _auto_null = object()
class auto: class auto:
@ -149,22 +162,12 @@ class property(DynamicClassAttribute):
return ownerclass._member_map_[self.name] return ownerclass._member_map_[self.name]
except KeyError: except KeyError:
raise AttributeError( raise AttributeError(
'%s: no class attribute %r' % (ownerclass.__name__, self.name) '%r has no attribute %r' % (ownerclass, self.name)
) )
else: else:
if self.fget is None: if self.fget is None:
# check for member
if self.name in ownerclass._member_map_:
import warnings
warnings.warn(
"accessing one member from another is not supported, "
" and will be disabled in 3.12",
DeprecationWarning,
stacklevel=2,
)
return ownerclass._member_map_[self.name]
raise AttributeError( raise AttributeError(
'%s: no instance attribute %r' % (ownerclass.__name__, self.name) '%r member has no attribute %r' % (ownerclass, self.name)
) )
else: else:
return self.fget(instance) return self.fget(instance)
@ -172,7 +175,7 @@ class property(DynamicClassAttribute):
def __set__(self, instance, value): def __set__(self, instance, value):
if self.fset is None: if self.fset is None:
raise AttributeError( raise AttributeError(
"%s: cannot set instance attribute %r" % (self.clsname, self.name) "<enum %r> cannot set attribute %r" % (self.clsname, self.name)
) )
else: else:
return self.fset(instance, value) return self.fset(instance, value)
@ -180,7 +183,7 @@ class property(DynamicClassAttribute):
def __delete__(self, instance): def __delete__(self, instance):
if self.fdel is None: if self.fdel is None:
raise AttributeError( raise AttributeError(
"%s: cannot delete instance attribute %r" % (self.clsname, self.name) "<enum %r> cannot delete attribute %r" % (self.clsname, self.name)
) )
else: else:
return self.fdel(instance) return self.fdel(instance)
@ -328,7 +331,7 @@ class _EnumDict(dict):
elif _is_sunder(key): elif _is_sunder(key):
if key not in ( if key not in (
'_order_', '_order_',
'_generate_next_value_', '_missing_', '_ignore_', '_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_',
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_', '_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_',
): ):
raise ValueError( raise ValueError(
@ -358,13 +361,13 @@ class _EnumDict(dict):
key = '_order_' key = '_order_'
elif key in self._member_names: elif key in self._member_names:
# descriptor overwriting an enum? # descriptor overwriting an enum?
raise TypeError('%r already defined as: %r' % (key, self[key])) raise TypeError('%r already defined as %r' % (key, self[key]))
elif key in self._ignore: elif key in self._ignore:
pass pass
elif not _is_descriptor(value): elif not _is_descriptor(value):
if key in self: if key in self:
# enum overwriting a descriptor? # enum overwriting a descriptor?
raise TypeError('%r already defined as: %r' % (key, self[key])) raise TypeError('%r already defined as %r' % (key, self[key]))
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(
@ -395,7 +398,7 @@ class EnumType(type):
@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
metacls._check_for_existing_members(cls, bases) metacls._check_for_existing_members_(cls, bases)
# create the namespace dict # create the namespace dict
enum_dict = _EnumDict() enum_dict = _EnumDict()
enum_dict._cls_name = cls enum_dict._cls_name = cls
@ -413,9 +416,10 @@ class EnumType(type):
# inherited __new__ unless a new __new__ is defined (or the resulting # inherited __new__ unless a new __new__ is defined (or the resulting
# class will fail). # class will fail).
# #
# remove any keys listed in _ignore_
if _simple: if _simple:
return super().__new__(metacls, cls, bases, classdict, **kwds) return super().__new__(metacls, cls, bases, classdict, **kwds)
#
# remove any keys listed in _ignore_
classdict.setdefault('_ignore_', []).append('_ignore_') classdict.setdefault('_ignore_', []).append('_ignore_')
ignore = classdict['_ignore_'] ignore = classdict['_ignore_']
for key in ignore: for key in ignore:
@ -427,8 +431,8 @@ class EnumType(type):
# check for illegal enum names (any others?) # check for illegal enum names (any others?)
invalid_names = set(member_names) & {'mro', ''} invalid_names = set(member_names) & {'mro', ''}
if invalid_names: if invalid_names:
raise ValueError('Invalid enum member name: {0}'.format( raise ValueError('invalid enum member name(s) '.format(
','.join(invalid_names))) ','.join(repr(n) for n in invalid_names)))
# #
# adjust the sunders # adjust the sunders
_order_ = classdict.pop('_order_', None) _order_ = classdict.pop('_order_', None)
@ -458,6 +462,8 @@ class EnumType(type):
classdict['_value2member_map_'] = {} classdict['_value2member_map_'] = {}
classdict['_unhashable_values_'] = [] classdict['_unhashable_values_'] = []
classdict['_member_type_'] = member_type classdict['_member_type_'] = member_type
# now set the __repr__ for the value
classdict['_value_repr_'] = metacls._find_data_repr_(cls, bases)
# #
# Flag structures (will be removed if final class is not a Flag # Flag structures (will be removed if final class is not a Flag
classdict['_boundary_'] = ( classdict['_boundary_'] = (
@ -467,10 +473,6 @@ class EnumType(type):
classdict['_flag_mask_'] = flag_mask classdict['_flag_mask_'] = flag_mask
classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1 classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1
classdict['_inverted_'] = None classdict['_inverted_'] = None
#
# create a default docstring if one has not been provided
if '__doc__' not in classdict:
classdict['__doc__'] = 'An enumeration.'
try: try:
exc = None exc = None
enum_class = super().__new__(metacls, cls, bases, classdict, **kwds) enum_class = super().__new__(metacls, cls, bases, classdict, **kwds)
@ -481,18 +483,140 @@ class EnumType(type):
if exc is not None: if exc is not None:
raise exc raise exc
# #
# update classdict with any changes made by __init_subclass__
classdict.update(enum_class.__dict__)
#
# create a default docstring if one has not been provided
if enum_class.__doc__ is None:
if not member_names:
enum_class.__doc__ = classdict['__doc__'] = _dedent("""\
Create a collection of name/value pairs.
Example enumeration:
>>> class Color(Enum):
... RED = 1
... BLUE = 2
... GREEN = 3
Access them by:
- attribute access::
>>> Color.RED
<Color.RED: 1>
- value lookup:
>>> Color(1)
<Color.RED: 1>
- name lookup:
>>> Color['RED']
<Color.RED: 1>
Enumerations can be iterated over, and know how many members they have:
>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
Methods can be added to enumerations, and members can have their own
attributes -- see the documentation for details.
""")
else:
member = list(enum_class)[0]
enum_length = len(enum_class)
cls_name = enum_class.__name__
if enum_length == 1:
list_line = 'list(%s)' % cls_name
list_repr = '[<%s.%s: %r>]' % (cls_name, member.name, member.value)
elif enum_length == 2:
member2 = list(enum_class)[1]
list_line = 'list(%s)' % cls_name
list_repr = '[<%s.%s: %r>, <%s.%s: %r>]' % (
cls_name, member.name, member.value,
cls_name, member2.name, member2.value,
)
else:
member2 = list(enum_class)[1]
member3 = list(enum_class)[2]
list_line = 'list(%s)%s' % (cls_name, ('','[:3]')[enum_length > 3])
list_repr = '[<%s.%s: %r>, <%s.%s: %r>, <%s.%s: %r>]' % (
cls_name, member.name, member.value,
cls_name, member2.name, member2.value,
cls_name, member3.name, member3.value,
)
enum_class.__doc__ = classdict['__doc__'] = _dedent("""\
A collection of name/value pairs.
Access them by:
- attribute access::
>>> %s.%s
<%s.%s: %r>
- value lookup:
>>> %s(%r)
<%s.%s: %r>
- name lookup:
>>> %s[%r]
<%s.%s: %r>
Enumerations can be iterated over, and know how many members they have:
>>> len(%s)
%r
>>> %s
%s
Methods can be added to enumerations, and members can have their own
attributes -- see the documentation for details.
"""
% (cls_name, member.name,
cls_name, member.name, member.value,
cls_name, member.value,
cls_name, member.name, member.value,
cls_name, member.name,
cls_name, member.name, member.value,
cls_name, enum_length,
list_line, list_repr,
))
#
# double check that repr and friends are not the mixin's or various # double check that repr and friends are not the mixin's or various
# things break (such as pickle) # things break (such as pickle)
# however, if the method is defined in the Enum itself, don't replace # however, if the method is defined in the Enum itself, don't replace
# it # it
#
# Also, special handling for ReprEnum
if ReprEnum is not None and ReprEnum in bases:
if member_type is object:
raise TypeError(
'ReprEnum subclasses must be mixed with a data type (i.e.'
' int, str, float, etc.)'
)
if '__format__' not in classdict:
enum_class.__format__ = member_type.__format__
classdict['__format__'] = enum_class.__format__
if '__str__' not in classdict:
method = member_type.__str__
if method is object.__str__:
# if member_type does not define __str__, object.__str__ will use
# its __repr__ instead, so we'll also use its __repr__
method = member_type.__repr__
enum_class.__str__ = method
classdict['__str__'] = enum_class.__str__
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
if name in classdict: if name not in classdict:
continue setattr(enum_class, name, getattr(first_enum, name))
class_method = getattr(enum_class, name)
obj_method = getattr(member_type, name, None)
enum_method = getattr(first_enum, name, None)
if obj_method is not None and obj_method is class_method:
setattr(enum_class, name, enum_method)
# #
# replace any other __new__ with our own (as long as Enum is not None, # replace any other __new__ with our own (as long as Enum is not None,
# anyway) -- again, this is to support pickle # anyway) -- again, this is to support pickle
@ -569,7 +693,7 @@ class EnumType(type):
# #
return enum_class return enum_class
def __bool__(self): def __bool__(cls):
""" """
classes/types should always be True. classes/types should always be True.
""" """
@ -614,6 +738,13 @@ class EnumType(type):
) )
def __contains__(cls, member): def __contains__(cls, member):
"""
Return True if member is a member of this enum
raises TypeError if member is not an enum member
note: in 3.12 TypeError will no longer be raised, and True will also be
returned if member is the value of a member in this enum
"""
if not isinstance(member, Enum): if not isinstance(member, Enum):
import warnings import warnings
warnings.warn( warnings.warn(
@ -631,60 +762,33 @@ class EnumType(type):
# nicer error message when someone tries to delete an attribute # nicer error message when someone tries to delete an attribute
# (see issue19025). # (see issue19025).
if attr in cls._member_map_: if attr in cls._member_map_:
raise AttributeError("%s: cannot delete Enum member %r." % (cls.__name__, attr)) raise AttributeError("%r cannot delete member %r." % (cls.__name__, attr))
super().__delattr__(attr) super().__delattr__(attr)
def __dir__(self): def __dir__(cls):
# Start off with the desired result for dir(Enum) # TODO: check for custom __init__, __new__, __format__, __repr__, __str__, __init_subclass__
cls_dir = {'__class__', '__doc__', '__members__', '__module__'} # on object-based enums
add_to_dir = cls_dir.add if cls._member_type_ is object:
mro = self.__mro__ interesting = set(cls._member_names_)
this_module = globals().values() if cls._new_member_ is not object.__new__:
is_from_this_module = lambda cls: any(cls is thing for thing in this_module) interesting.add('__new__')
first_enum_base = next(cls for cls in mro if is_from_this_module(cls)) if cls.__init_subclass__ is not object.__init_subclass__:
enum_dict = Enum.__dict__ interesting.add('__init_subclass__')
sentinel = object() for method in ('__init__', '__format__', '__repr__', '__str__'):
# special-case __new__ if getattr(cls, method) not in (getattr(Enum, method), getattr(Flag, method)):
ignored = {'__new__', *filter(_is_sunder, enum_dict)} interesting.add(method)
add_to_ignored = ignored.add return sorted(set([
'__class__', '__contains__', '__doc__', '__getitem__',
# We want these added to __dir__ '__iter__', '__len__', '__members__', '__module__',
# if and only if they have been user-overridden '__name__', '__qualname__',
enum_dunders = set(filter(_is_dunder, enum_dict)) ]) | interesting
)
for cls in mro:
# Ignore any classes defined in this module
if cls is object or is_from_this_module(cls):
continue
cls_lookup = cls.__dict__
# If not an instance of EnumType,
# ensure all attributes excluded from that class's `dir()` are ignored here.
if not isinstance(cls, EnumType):
cls_lookup = set(cls_lookup).intersection(dir(cls))
for attr_name in cls_lookup:
# Already seen it? Carry on
if attr_name in cls_dir or attr_name in ignored:
continue
# Sunders defined in Enum.__dict__ are already in `ignored`,
# But sunders defined in a subclass won't be (we want all sunders excluded).
elif _is_sunder(attr_name):
add_to_ignored(attr_name)
# Not an "enum dunder"? Add it to dir() output.
elif attr_name not in enum_dunders:
add_to_dir(attr_name)
# Is an "enum dunder", and is defined by a class from enum.py? Ignore it.
elif getattr(self, attr_name) is getattr(first_enum_base, attr_name, sentinel):
add_to_ignored(attr_name)
# Is an "enum dunder", and is either user-defined or defined by a mixin class?
# Add it to dir() output.
else: else:
add_to_dir(attr_name) # return whatever mixed-in data type has
return sorted(set(
# sort the output before returning it, so that the result is deterministic. dir(cls._member_type_)
return sorted(cls_dir) + cls._member_names_
))
def __getattr__(cls, name): def __getattr__(cls, name):
""" """
@ -703,18 +807,24 @@ class EnumType(type):
raise AttributeError(name) from None raise AttributeError(name) from None
def __getitem__(cls, name): def __getitem__(cls, name):
"""
Return the member matching `name`.
"""
return cls._member_map_[name] return cls._member_map_[name]
def __iter__(cls): def __iter__(cls):
""" """
Returns members in definition order. Return members in definition order.
""" """
return (cls._member_map_[name] for name in cls._member_names_) return (cls._member_map_[name] for name in cls._member_names_)
def __len__(cls): def __len__(cls):
"""
Return the number of members (no aliases)
"""
return len(cls._member_names_) return len(cls._member_names_)
@_bltin_property @bltns.property
def __members__(cls): def __members__(cls):
""" """
Returns a mapping of member name->value. Returns a mapping of member name->value.
@ -732,7 +842,7 @@ class EnumType(type):
def __reversed__(cls): def __reversed__(cls):
""" """
Returns members in reverse definition order. Return members in reverse definition order.
""" """
return (cls._member_map_[name] for name in reversed(cls._member_names_)) return (cls._member_map_[name] for name in reversed(cls._member_names_))
@ -746,7 +856,7 @@ class EnumType(type):
""" """
member_map = cls.__dict__.get('_member_map_', {}) member_map = cls.__dict__.get('_member_map_', {})
if name in member_map: if name in member_map:
raise AttributeError('Cannot reassign member %r.' % (name, )) raise AttributeError('cannot reassign member %r' % (name, ))
super().__setattr__(name, value) super().__setattr__(name, value)
def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1, boundary=None): def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, start=1, boundary=None):
@ -801,8 +911,7 @@ class EnumType(type):
return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary) return metacls.__new__(metacls, class_name, bases, classdict, boundary=boundary)
def _convert_(cls, name, module, filter, source=None, *, boundary=None): def _convert_(cls, name, module, filter, source=None, *, boundary=None, as_global=False):
""" """
Create a new Enum subclass that replaces a collection of global constants Create a new Enum subclass that replaces a collection of global constants
""" """
@ -834,22 +943,25 @@ class EnumType(type):
tmp_cls = type(name, (object, ), body) tmp_cls = type(name, (object, ), body)
cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls) cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls)
cls.__reduce_ex__ = _reduce_ex_by_global_name cls.__reduce_ex__ = _reduce_ex_by_global_name
if as_global:
global_enum(cls) global_enum(cls)
else:
sys.modules[cls.__module__].__dict__.update(cls.__members__)
module_globals[name] = cls module_globals[name] = cls
return cls return cls
@staticmethod @classmethod
def _check_for_existing_members(class_name, bases): def _check_for_existing_members_(mcls, class_name, bases):
for chain in bases: for chain in bases:
for base in chain.__mro__: for base in chain.__mro__:
if issubclass(base, Enum) and base._member_names_: if issubclass(base, Enum) and base._member_names_:
raise TypeError( raise TypeError(
"%s: cannot extend enumeration %r" "<enum %r> cannot extend %r"
% (class_name, base.__name__) % (class_name, base)
) )
@classmethod @classmethod
def _get_mixins_(cls, class_name, bases): def _get_mixins_(mcls, class_name, bases):
""" """
Returns the type for creating enum members, and the first inherited Returns the type for creating enum members, and the first inherited
enum class. enum class.
@ -859,7 +971,33 @@ class EnumType(type):
if not bases: if not bases:
return object, Enum return object, Enum
def _find_data_type(bases): mcls._check_for_existing_members_(class_name, bases)
# ensure final parent class is an Enum derivative, find any concrete
# data type, and check that Enum has no members
first_enum = bases[-1]
if not issubclass(first_enum, Enum):
raise TypeError("new enumerations should be created as "
"`EnumName([mixin_type, ...] [data_type,] enum_type)`")
member_type = mcls._find_data_type_(class_name, bases) or object
return member_type, first_enum
@classmethod
def _find_data_repr_(mcls, class_name, bases):
for chain in bases:
for base in chain.__mro__:
if base is object:
continue
elif issubclass(base, Enum):
# if we hit an Enum, use it's _value_repr_
return base._value_repr_
elif '__repr__' in base.__dict__:
# this is our data repr
return base.__dict__['__repr__']
return None
@classmethod
def _find_data_type_(mcls, class_name, bases):
data_types = set() data_types = set()
for chain in bases: for chain in bases:
candidate = None candidate = None
@ -878,24 +1016,14 @@ class EnumType(type):
else: else:
candidate = candidate or base candidate = candidate or base
if len(data_types) > 1: if len(data_types) > 1:
raise TypeError('%r: too many data types: %r' % (class_name, data_types)) raise TypeError('too many data types for %r: %r' % (class_name, data_types))
elif data_types: elif data_types:
return data_types.pop() return data_types.pop()
else: else:
return None return None
# ensure final parent class is an Enum derivative, find any concrete @classmethod
# data type, and check that Enum has no members def _find_new_(mcls, classdict, member_type, first_enum):
first_enum = bases[-1]
if not issubclass(first_enum, Enum):
raise TypeError("new enumerations should be created as "
"`EnumName([mixin_type, ...] [data_type,] enum_type)`")
cls._check_for_existing_members(class_name, bases)
member_type = _find_data_type(bases) or object
return member_type, first_enum
@staticmethod
def _find_new_(classdict, member_type, first_enum):
""" """
Returns the __new__ to be used for creating the enum members. Returns the __new__ to be used for creating the enum members.
@ -943,9 +1071,42 @@ EnumMeta = EnumType
class Enum(metaclass=EnumType): class Enum(metaclass=EnumType):
""" """
Generic enumeration. Create a collection of name/value pairs.
Derive from this class to define new enumerations. Example enumeration:
>>> class Color(Enum):
... RED = 1
... BLUE = 2
... GREEN = 3
Access them by:
- attribute access::
>>> Color.RED
<Color.RED: 1>
- value lookup:
>>> Color(1)
<Color.RED: 1>
- name lookup:
>>> Color['RED']
<Color.RED: 1>
Enumerations can be iterated over, and know how many members they have:
>>> len(Color)
3
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
Methods can be added to enumerations, and members can have their own
attributes -- see the documentation for details.
""" """
def __new__(cls, value): def __new__(cls, value):
@ -999,6 +1160,9 @@ class Enum(metaclass=EnumType):
exc = None exc = None
ve_exc = None ve_exc = None
def __init__(self, *args, **kwds):
pass
def _generate_next_value_(name, start, count, last_values): def _generate_next_value_(name, start, count, last_values):
""" """
Generate the next value when not given. Generate the next value when not given.
@ -1021,47 +1185,44 @@ class Enum(metaclass=EnumType):
return None return None
def __repr__(self): def __repr__(self):
return "%s.%s" % ( self.__class__.__name__, self._name_) v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__
return "<%s.%s: %s>" % (self.__class__.__name__, self._name_, v_repr(self._value_))
def __str__(self): def __str__(self):
return "%s" % (self._name_, ) return "%s.%s" % (self.__class__.__name__, self._name_, )
def __dir__(self): def __dir__(self):
""" """
Returns all members and all public methods Returns all members and all public methods
""" """
cls = type(self) if self.__class__._member_type_ is object:
to_exclude = {'__members__', '__init__', '__new__', *cls._member_names_} interesting = set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value'])
filtered_self_dict = (name for name in self.__dict__ if not name.startswith('_')) else:
return sorted({'name', 'value', *dir(cls), *filtered_self_dict} - to_exclude) interesting = set(object.__dir__(self))
for name in getattr(self, '__dict__', []):
if name[0] != '_':
interesting.add(name)
for cls in self.__class__.mro():
for name, obj in cls.__dict__.items():
if name[0] == '_':
continue
if isinstance(obj, property):
# that's an enum.property
if obj.fget is not None or name not in self._member_map_:
interesting.add(name)
else:
# in case it was added by `dir(self)`
interesting.discard(name)
else:
interesting.add(name)
names = sorted(
set(['__class__', '__doc__', '__eq__', '__hash__', '__module__'])
| interesting
)
return names
def __format__(self, format_spec): def __format__(self, format_spec):
""" return str.__format__(str(self), format_spec)
Returns format using actual value type unless __str__ has been overridden.
"""
# 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, or branch with __str__ explicitly overridden
str_overridden = type(self).__str__ not in (Enum.__str__, IntEnum.__str__, Flag.__str__)
if self._member_type_ is object or str_overridden:
cls = str
val = str(self)
# mix-in branch
else:
if not format_spec or format_spec in ('{}','{:}'):
import warnings
warnings.warn(
"in 3.12 format() will use the enum member, not the enum member's value;\n"
"use a format specifier, such as :d for an integer-based Enum, to maintain "
"the current display",
DeprecationWarning,
stacklevel=2,
)
cls = self._member_type_
val = self._value_
return cls.__format__(val, format_spec)
def __hash__(self): def __hash__(self):
return hash(self._name_) return hash(self._name_)
@ -1088,34 +1249,25 @@ class Enum(metaclass=EnumType):
return self._value_ return self._value_
class IntEnum(int, Enum): class ReprEnum(Enum):
"""
Only changes the repr(), leaving str() and format() to the mixed-in type.
"""
class IntEnum(int, ReprEnum):
""" """
Enum where members are also (and must be) ints Enum where members are also (and must be) ints
""" """
def __str__(self):
return "%s" % (self._name_, )
def __format__(self, format_spec): class StrEnum(str, ReprEnum):
"""
Returns format using actual value unless __str__ has been overridden.
"""
str_overridden = type(self).__str__ != IntEnum.__str__
if str_overridden:
cls = str
val = str(self)
else:
cls = self._member_type_
val = self._value_
return cls.__format__(val, format_spec)
class StrEnum(str, Enum):
""" """
Enum where members are also (and must be) strings Enum where members are also (and must be) strings
""" """
def __new__(cls, *values): def __new__(cls, *values):
"values must already be of type `str`"
if len(values) > 3: if len(values) > 3:
raise TypeError('too many arguments for str(): %r' % (values, )) raise TypeError('too many arguments for str(): %r' % (values, ))
if len(values) == 1: if len(values) == 1:
@ -1135,10 +1287,6 @@ class StrEnum(str, Enum):
member._value_ = value member._value_ = value
return member return member
__str__ = str.__str__
__format__ = str.__format__
def _generate_next_value_(name, start, count, last_values): def _generate_next_value_(name, start, count, last_values):
""" """
Return the lower-cased version of the member name. Return the lower-cased version of the member name.
@ -1169,6 +1317,8 @@ class Flag(Enum, boundary=STRICT):
Support for flags Support for flags
""" """
_numeric_repr_ = repr
def _generate_next_value_(name, start, count, last_values): def _generate_next_value_(name, start, count, last_values):
""" """
Generate the next value when not given. Generate the next value when not given.
@ -1184,7 +1334,7 @@ class Flag(Enum, boundary=STRICT):
try: try:
high_bit = _high_bit(last_value) high_bit = _high_bit(last_value)
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 @classmethod
@ -1232,8 +1382,8 @@ class Flag(Enum, boundary=STRICT):
if cls._boundary_ is STRICT: if cls._boundary_ is STRICT:
max_bits = max(value.bit_length(), flag_mask.bit_length()) max_bits = max(value.bit_length(), flag_mask.bit_length())
raise ValueError( raise ValueError(
"%s: invalid value: %r\n given %s\n allowed %s" % ( "%r invalid value %r\n given %s\n allowed %s" % (
cls.__name__, value, bin(value, max_bits), bin(flag_mask, max_bits), cls, value, bin(value, max_bits), bin(flag_mask, max_bits),
)) ))
elif cls._boundary_ is CONFORM: elif cls._boundary_ is CONFORM:
value = value & flag_mask value = value & flag_mask
@ -1247,7 +1397,7 @@ class Flag(Enum, boundary=STRICT):
) )
else: else:
raise ValueError( raise ValueError(
'unknown flag boundary: %r' % (cls._boundary_, ) '%r unknown flag boundary %r' % (cls, cls._boundary_, )
) )
if value < 0: if value < 0:
neg_value = value neg_value = value
@ -1274,7 +1424,7 @@ class Flag(Enum, boundary=STRICT):
m._name_ for m in cls._iter_member_(member_value) m._name_ for m in cls._iter_member_(member_value)
]) ])
if unknown: if unknown:
pseudo_member._name_ += '|0x%x' % unknown pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown)
else: else:
pseudo_member._name_ = None pseudo_member._name_ = None
# use setdefault in case another thread already created a composite # use setdefault in case another thread already created a composite
@ -1292,10 +1442,8 @@ class Flag(Enum, boundary=STRICT):
""" """
if not isinstance(other, self.__class__): if not isinstance(other, self.__class__):
raise TypeError( raise TypeError(
"unsupported operand type(s) for 'in': '%s' and '%s'" % ( "unsupported operand type(s) for 'in': %r and %r" % (
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):
@ -1309,27 +1457,18 @@ class Flag(Enum, boundary=STRICT):
def __repr__(self): def __repr__(self):
cls_name = self.__class__.__name__ cls_name = self.__class__.__name__
v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__
if self._name_ is None: if self._name_ is None:
return "0x%x" % (self._value_, ) return "<%s: %s>" % (cls_name, v_repr(self._value_))
if _is_single_bit(self._value_):
return '%s.%s' % (cls_name, self._name_)
if self._boundary_ is not FlagBoundary.KEEP:
return '%s.' % cls_name + ('|%s.' % cls_name).join(self.name.split('|'))
else: else:
name = [] return "<%s.%s: %s>" % (cls_name, self._name_, v_repr(self._value_))
for n in self._name_.split('|'):
if n.startswith('0'):
name.append(n)
else:
name.append('%s.%s' % (cls_name, n))
return '|'.join(name)
def __str__(self): def __str__(self):
cls = self.__class__ cls_name = self.__class__.__name__
if self._name_ is None: if self._name_ is None:
return '%s(%x)' % (cls.__name__, self._value_) return '%s(%r)' % (cls_name, self._value_)
else: else:
return self._name_ return "%s.%s" % (cls_name, self._name_)
def __bool__(self): def __bool__(self):
return bool(self._value_) return bool(self._value_)
@ -1362,20 +1501,11 @@ class Flag(Enum, boundary=STRICT):
return self._inverted_ return self._inverted_
class IntFlag(int, Flag, boundary=EJECT): class IntFlag(int, ReprEnum, Flag, boundary=EJECT):
""" """
Support for integer-based Flags Support for integer-based Flags
""" """
def __format__(self, format_spec):
"""
Returns format using actual value unless __str__ has been overridden.
"""
str_overridden = type(self).__str__ != Flag.__str__
value = self
if not str_overridden:
value = self._value_
return int.__format__(value, format_spec)
def __or__(self, other): def __or__(self, other):
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
@ -1412,6 +1542,7 @@ class IntFlag(int, Flag, boundary=EJECT):
__rxor__ = __xor__ __rxor__ = __xor__
__invert__ = Flag.__invert__ __invert__ = Flag.__invert__
def _high_bit(value): def _high_bit(value):
""" """
returns index of highest bit, or -1 if value is zero or negative returns index of highest bit, or -1 if value is zero or negative
@ -1456,7 +1587,7 @@ def global_flag_repr(self):
module = self.__class__.__module__.split('.')[-1] module = self.__class__.__module__.split('.')[-1]
cls_name = self.__class__.__name__ cls_name = self.__class__.__name__
if self._name_ is None: if self._name_ is None:
return "%s.%s(0x%x)" % (module, cls_name, self._value_) return "%s.%s(%r)" % (module, cls_name, self._value_)
if _is_single_bit(self): if _is_single_bit(self):
return '%s.%s' % (module, self._name_) return '%s.%s' % (module, self._name_)
if self._boundary_ is not FlagBoundary.KEEP: if self._boundary_ is not FlagBoundary.KEEP:
@ -1464,14 +1595,22 @@ def global_flag_repr(self):
else: else:
name = [] name = []
for n in self._name_.split('|'): for n in self._name_.split('|'):
if n.startswith('0'): if n[0].isdigit():
name.append(n) name.append(n)
else: else:
name.append('%s.%s' % (module, n)) name.append('%s.%s' % (module, n))
return '|'.join(name) return '|'.join(name)
def global_str(self):
"""
use enum_name instead of class.enum_name
"""
if self._name_ is None:
return "%s(%r)" % (cls_name, self._value_)
else:
return self._name_
def global_enum(cls): def global_enum(cls, update_str=False):
""" """
decorator that makes the repr() of an enum member reference its module decorator that makes the repr() of an enum member reference its module
instead of its class; also exports all members to the enum's module's instead of its class; also exports all members to the enum's module's
@ -1481,6 +1620,8 @@ def global_enum(cls):
cls.__repr__ = global_flag_repr cls.__repr__ = global_flag_repr
else: else:
cls.__repr__ = global_enum_repr cls.__repr__ = global_enum_repr
if not issubclass(cls, ReprEnum) or update_str:
cls.__str__ = global_str
sys.modules[cls.__module__].__dict__.update(cls.__members__) sys.modules[cls.__module__].__dict__.update(cls.__members__)
return cls return cls
@ -1522,6 +1663,7 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
body['_value2member_map_'] = value2member_map = {} body['_value2member_map_'] = value2member_map = {}
body['_unhashable_values_'] = [] body['_unhashable_values_'] = []
body['_member_type_'] = member_type = etype._member_type_ body['_member_type_'] = member_type = etype._member_type_
body['_value_repr_'] = etype._value_repr_
if issubclass(etype, Flag): if issubclass(etype, Flag):
body['_boundary_'] = boundary or etype._boundary_ body['_boundary_'] = boundary or etype._boundary_
body['_flag_mask_'] = None body['_flag_mask_'] = None
@ -1543,13 +1685,8 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
# it # it
enum_class = type(cls_name, (etype, ), body, boundary=boundary, _simple=True) enum_class = type(cls_name, (etype, ), body, boundary=boundary, _simple=True)
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
if name in body: if name not in body:
continue setattr(enum_class, name, getattr(etype, name))
class_method = getattr(enum_class, name)
obj_method = getattr(member_type, name, None)
enum_method = getattr(etype, name, None)
if obj_method is not None and obj_method is class_method:
setattr(enum_class, name, enum_method)
gnv_last_values = [] gnv_last_values = []
if issubclass(enum_class, Flag): if issubclass(enum_class, Flag):
# Flag / IntFlag # Flag / IntFlag
@ -1760,8 +1897,8 @@ def _test_simple_enum(checked_enum, simple_enum):
+ list(simple_enum._member_map_.keys()) + list(simple_enum._member_map_.keys())
) )
for key in set(checked_keys + simple_keys): for key in set(checked_keys + simple_keys):
if key in ('__module__', '_member_map_', '_value2member_map_'): if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'):
# keys known to be different # keys known to be different, or very long
continue continue
elif key in member_names: elif key in member_names:
# members are checked below # members are checked below
@ -1882,3 +2019,5 @@ def _old_convert_(etype, name, module, filter, source=None, *, boundary=None):
cls.__reduce_ex__ = _reduce_ex_by_global_name cls.__reduce_ex__ = _reduce_ex_by_global_name
cls.__repr__ = global_enum_repr cls.__repr__ = global_enum_repr
return cls return cls
_stdlib_enums = IntEnum, StrEnum, IntFlag

View File

@ -2567,15 +2567,21 @@ class _empty:
class _ParameterKind(enum.IntEnum): class _ParameterKind(enum.IntEnum):
POSITIONAL_ONLY = 0 POSITIONAL_ONLY = 'positional-only'
POSITIONAL_OR_KEYWORD = 1 POSITIONAL_OR_KEYWORD = 'positional or keyword'
VAR_POSITIONAL = 2 VAR_POSITIONAL = 'variadic positional'
KEYWORD_ONLY = 3 KEYWORD_ONLY = 'keyword-only'
VAR_KEYWORD = 4 VAR_KEYWORD = 'variadic keyword'
@property def __new__(cls, description):
def description(self): value = len(cls.__members__)
return _PARAM_NAME_MAPPING[self] member = int.__new__(cls, value)
member._value_ = value
member.description = description
return member
def __str__(self):
return self.name
_POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY _POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY
_POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD _POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD
@ -2583,14 +2589,6 @@ _VAR_POSITIONAL = _ParameterKind.VAR_POSITIONAL
_KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY _KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY
_VAR_KEYWORD = _ParameterKind.VAR_KEYWORD _VAR_KEYWORD = _ParameterKind.VAR_KEYWORD
_PARAM_NAME_MAPPING = {
_POSITIONAL_ONLY: 'positional-only',
_POSITIONAL_OR_KEYWORD: 'positional or keyword',
_VAR_POSITIONAL: 'variadic positional',
_KEYWORD_ONLY: 'keyword-only',
_VAR_KEYWORD: 'variadic keyword'
}
class Parameter: class Parameter:
"""Represents a parameter in a function signature. """Represents a parameter in a function signature.

View File

@ -61,7 +61,8 @@ import struct
from xml.parsers.expat import ParserCreate from xml.parsers.expat import ParserCreate
PlistFormat = enum.global_enum(enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)) PlistFormat = enum.Enum('PlistFormat', 'FMT_XML FMT_BINARY', module=__name__)
globals().update(PlistFormat.__members__)
class UID: class UID:

View File

@ -155,6 +155,8 @@ class RegexFlag:
# sre extensions (experimental, don't rely on these) # sre extensions (experimental, don't rely on these)
TEMPLATE = T = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking TEMPLATE = T = sre_compile.SRE_FLAG_TEMPLATE # disable backtracking
DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation DEBUG = sre_compile.SRE_FLAG_DEBUG # dump pattern after compilation
__str__ = object.__str__
_numeric_repr_ = hex
# sre exception # sre exception
error = sre_compile.error error = sre_compile.error

View File

@ -119,7 +119,6 @@ from _ssl import (
) )
from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION
_IntEnum._convert_( _IntEnum._convert_(
'_SSLMethod', __name__, '_SSLMethod', __name__,
lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23', lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23',

File diff suppressed because it is too large Load Diff

View File

@ -908,7 +908,7 @@ class PendingSignalsTests(unittest.TestCase):
%s %s
blocked = %r blocked = %s
signum = signal.SIGALRM signum = signal.SIGALRM
# child: block and wait the signal # child: block and wait the signal

View File

@ -1517,9 +1517,11 @@ class GeneralModuleTests(unittest.TestCase):
infos = socket.getaddrinfo(HOST, 80, socket.AF_INET, socket.SOCK_STREAM) infos = socket.getaddrinfo(HOST, 80, socket.AF_INET, socket.SOCK_STREAM)
for family, type, _, _, _ in infos: for family, type, _, _, _ in infos:
self.assertEqual(family, socket.AF_INET) self.assertEqual(family, socket.AF_INET)
self.assertEqual(str(family), 'AF_INET') self.assertEqual(repr(family), '<AddressFamily.AF_INET: 2>')
self.assertEqual(str(family), '2')
self.assertEqual(type, socket.SOCK_STREAM) self.assertEqual(type, socket.SOCK_STREAM)
self.assertEqual(str(type), 'SOCK_STREAM') self.assertEqual(repr(type), '<SocketKind.SOCK_STREAM: 1>')
self.assertEqual(str(type), '1')
infos = socket.getaddrinfo(HOST, None, 0, socket.SOCK_STREAM) infos = socket.getaddrinfo(HOST, None, 0, socket.SOCK_STREAM)
for _, socktype, _, _, _ in infos: for _, socktype, _, _, _ in infos:
self.assertEqual(socktype, socket.SOCK_STREAM) self.assertEqual(socktype, socket.SOCK_STREAM)
@ -1793,8 +1795,10 @@ class GeneralModuleTests(unittest.TestCase):
# Make sure that the AF_* and SOCK_* constants have enum-like string # Make sure that the AF_* and SOCK_* constants have enum-like string
# reprs. # reprs.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
self.assertEqual(str(s.family), 'AF_INET') self.assertEqual(repr(s.family), '<AddressFamily.AF_INET: 2>')
self.assertEqual(str(s.type), 'SOCK_STREAM') self.assertEqual(repr(s.type), '<SocketKind.SOCK_STREAM: 1>')
self.assertEqual(str(s.family), '2')
self.assertEqual(str(s.type), '1')
def test_socket_consistent_sock_type(self): def test_socket_consistent_sock_type(self):
SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0) SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', 0)

View File

@ -373,7 +373,8 @@ class BasicSocketTests(unittest.TestCase):
# Make sure that the PROTOCOL_* constants have enum-like string # Make sure that the PROTOCOL_* constants have enum-like string
# reprs. # reprs.
proto = ssl.PROTOCOL_TLS_CLIENT proto = ssl.PROTOCOL_TLS_CLIENT
self.assertEqual(str(proto), 'PROTOCOL_TLS_CLIENT') self.assertEqual(repr(proto), '<_SSLMethod.PROTOCOL_TLS_CLIENT: 16>')
self.assertEqual(str(proto), '16')
ctx = ssl.SSLContext(proto) ctx = ssl.SSLContext(proto)
self.assertIs(ctx.protocol, proto) self.assertIs(ctx.protocol, proto)
@ -622,7 +623,7 @@ class BasicSocketTests(unittest.TestCase):
with self.assertWarns(DeprecationWarning) as cm: with self.assertWarns(DeprecationWarning) as cm:
ssl.SSLContext(protocol) ssl.SSLContext(protocol)
self.assertEqual( self.assertEqual(
f'{protocol!r} is deprecated', f'ssl.{protocol.name} is deprecated',
str(cm.warning) str(cm.warning)
) )
@ -631,8 +632,9 @@ class BasicSocketTests(unittest.TestCase):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
with self.assertWarns(DeprecationWarning) as cm: with self.assertWarns(DeprecationWarning) as cm:
ctx.minimum_version = version ctx.minimum_version = version
version_text = '%s.%s' % (version.__class__.__name__, version.name)
self.assertEqual( self.assertEqual(
f'ssl.{version!r} is deprecated', f'ssl.{version_text} is deprecated',
str(cm.warning) str(cm.warning)
) )

View File

@ -1490,8 +1490,10 @@ class UnicodeTest(string_tests.CommonTest,
# issue18780 # issue18780
import enum import enum
class Float(float, enum.Enum): class Float(float, enum.Enum):
# a mixed-in type will use the name for %s etc.
PI = 3.1415926 PI = 3.1415926
class Int(enum.IntEnum): class Int(enum.IntEnum):
# IntEnum uses the value and not the name for %s etc.
IDES = 15 IDES = 15
class Str(enum.StrEnum): class Str(enum.StrEnum):
# StrEnum uses the value and not the name for %s etc. # StrEnum uses the value and not the name for %s etc.
@ -1508,8 +1510,10 @@ class UnicodeTest(string_tests.CommonTest,
# formatting jobs delegated from the string implementation: # formatting jobs delegated from the string implementation:
self.assertEqual('...%(foo)s...' % {'foo':Str.ABC}, self.assertEqual('...%(foo)s...' % {'foo':Str.ABC},
'...abc...') '...abc...')
self.assertEqual('...%(foo)r...' % {'foo':Int.IDES},
'...<Int.IDES: 15>...')
self.assertEqual('...%(foo)s...' % {'foo':Int.IDES}, self.assertEqual('...%(foo)s...' % {'foo':Int.IDES},
'...IDES...') '...15...')
self.assertEqual('...%(foo)i...' % {'foo':Int.IDES}, self.assertEqual('...%(foo)i...' % {'foo':Int.IDES},
'...15...') '...15...')
self.assertEqual('...%(foo)d...' % {'foo':Int.IDES}, self.assertEqual('...%(foo)d...' % {'foo':Int.IDES},

View File

@ -0,0 +1,2 @@
``IntEnum``, ``IntFlag``, and ``StrEnum`` use the mixed-in type for their
``str()`` and ``format()`` output.