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
==========
:Author: Ethan Furman <ethan at stoneleaf dot us>
.. _enum-basic-tutorial:
.. currentmodule:: enum
Basic Enum Tutorial
-------------------
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()`,
grouping, type-safety, and a few other features.
@ -28,6 +23,14 @@ selection of values. For example, the days of the week::
... SATURDAY = 6
... 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
inherits from :class:`Enum` itself.
@ -41,13 +44,14 @@ important, but either way that value can be used to get the corresponding
member::
>>> Weekday(3)
Weekday.WEDNESDAY
<Weekday.WEDNESDAY: 3>
As you can see, the ``repr()`` of a member shows the enum name and the
member name. The ``str()`` on a member shows only its name::
As you can see, the ``repr()`` of a member shows the enum name, the member name,
and the value. The ``str()`` of a member shows only the enum name and member
name::
>>> print(Weekday.THURSDAY)
THURSDAY
Weekday.THURSDAY
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::
>>> from datetime import date
>>> Weekday.from_date(date.today())
Weekday.TUESDAY
>>> Weekday.from_date(date.today()) # doctest: +SKIP
<Weekday.TUESDAY: 2>
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
<Weekday.MONDAY: 1>
But :class:`Flag` also allows us to combine several members into a single
variable::
>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
Weekday.SATURDAY|Weekday.SUNDAY
<Weekday.SATURDAY|SUNDAY: 96>
You can even iterate over a :class:`Flag` variable::
>>> for day in weekend:
... print(day)
SATURDAY
SUNDAY
Weekday.SATURDAY
Weekday.SUNDAY
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:
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::
>>> Color(1)
Color.RED
<Color.RED: 1>
>>> Color(3)
Color.BLUE
<Color.BLUE: 3>
If you want to access enum members by *name*, use item access::
>>> Color['RED']
Color.RED
<Color.RED: 1>
>>> Color['GREEN']
Color.GREEN
<Color.GREEN: 2>
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):
...
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
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
...
>>> Shape.SQUARE
Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
Shape.SQUARE
<Shape.SQUARE: 2>
.. note::
@ -299,7 +304,7 @@ Iteration
Iterating over the members of an enum does not provide the aliases::
>>> 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
to members. It includes all names defined in the enumeration, including the
@ -308,10 +313,10 @@ aliases::
>>> for name, member in Shape.__members__.items():
... name, member
...
('SQUARE', Shape.SQUARE)
('DIAMOND', Shape.DIAMOND)
('CIRCLE', Shape.CIRCLE)
('ALIAS_FOR_SQUARE', Shape.SQUARE)
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)
The ``__members__`` attribute can be used for detailed programmatic access to
the enumeration members. For example, finding all the aliases::
@ -360,8 +365,8 @@ below)::
Allowed members and attributes of enumerations
----------------------------------------------
Most of the examples above use integers for enumeration values. Using integers is
short and handy (and provided by default by the `Functional API`_), but not
Most of the examples above use integers for enumeration values. Using integers
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
the actual value of an enumeration is. But if the value *is* important,
enumerations can have arbitrary values.
@ -389,7 +394,7 @@ usual. If we have this enumeration::
Then::
>>> Mood.favorite_mood()
Mood.HAPPY
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
@ -425,7 +430,7 @@ any members. So this is forbidden::
...
Traceback (most recent call last):
...
TypeError: MoreColor: cannot extend enumeration 'Color'
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>
But this is allowed::
@ -476,11 +481,9 @@ The :class:`Enum` class is callable, providing the following functional API::
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
Animal.ANT
>>> Animal.ANT.value
1
<Animal.ANT: 1>
>>> 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
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
:class:`str`. Members of a :class:`StrEnum` can be compared to strings;
by extension, string enumerations of different types can also be compared
to each other. :class:`StrEnum` exists to help avoid the problem of getting
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',)``.
to each other.
.. versionadded:: 3.11
@ -645,9 +639,8 @@ IntFlag
The next variation of :class:`Enum` provided, :class:`IntFlag`, is also based
on :class:`int`. The difference being :class:`IntFlag` members can be combined
using the bitwise operators (&, \|, ^, ~) and the result is still an
:class:`IntFlag` member, if possible. However, as the name implies, :class:`IntFlag`
members also subclass :class:`int` and can be used wherever an :class:`int` is
used.
:class:`IntFlag` member, if possible. Like :class:`IntEnum`, :class:`IntFlag`
members are also integers and can be used wherever an :class:`int` is used.
.. note::
@ -670,7 +663,7 @@ Sample :class:`IntFlag` class::
... X = 1
...
>>> Perm.R | Perm.W
Perm.R|Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
@ -685,11 +678,11 @@ It is also possible to name the combinations::
... X = 1
... RWX = 7
>>> Perm.RWX
Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
Perm(0)
<Perm: 0>
>>> Perm(7)
Perm.RWX
<Perm.RWX: 7>
.. 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`::
>>> Perm.R & Perm.X
Perm(0)
<Perm: 0>
>>> bool(Perm.R & Perm.X)
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::
>>> Perm.X | 4
Perm.R|Perm.X
<Perm.R|X: 5>
>>> Perm.X | 8
9
@ -726,7 +719,7 @@ be combined with them (but may lose :class:`IntFlag` membership::
:class:`IntFlag` members can also be iterated over::
>>> list(RW)
[Perm.R, Perm.W]
[<Perm.R: 4>, <Perm.W: 2>]
.. versionadded:: 3.11
@ -753,7 +746,7 @@ flags being set, the boolean evaluation is :data:`False`::
... GREEN = auto()
...
>>> Color.RED & Color.GREEN
Color(0)
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False
@ -767,7 +760,7 @@ while combinations of flags won't::
... WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
Color.WHITE
<Color.WHITE: 7>
Giving a name to the "no flags set" condition does not change its boolean
value::
@ -779,7 +772,7 @@ value::
... GREEN = auto()
...
>>> Color.BLACK
Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False
@ -787,7 +780,7 @@ value::
>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[Color.RED, Color.BLUE]
[<Color.RED: 1>, <Color.BLUE: 2>]
.. versionadded:: 3.11
@ -812,16 +805,16 @@ simple to implement independently::
pass
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:
1. When subclassing :class:`Enum`, mix-in types must appear before
:class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum`
example above.
2. Mix-in types must be subclassable. For example,
:class:`bool` and :class:`range` are not subclassable
and will throw an error during Enum creation if used as the mix-in type.
2. Mix-in types must be subclassable. For example, :class:`bool` and
:class:`range` are not subclassable and will throw an error during Enum
creation if used as the mix-in type.
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.
: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
same* as the enum member itself, although it is equivalent and will compare
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
`%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`,
and :func:`format` will use the mixed-in type's :meth:`__format__`
unless :meth:`__str__` or :meth:`__format__` is overridden in the subclass,
in which case the overridden methods or :class:`Enum` methods will be used.
Use the !s and !r format codes to force usage of the :class:`Enum` class's
:meth:`__str__` and :meth:`__repr__` methods.
and :func:`format` will use the enum's :meth:`__str__` method.
.. note::
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__`
------------------------------------------------
@ -866,10 +862,10 @@ want one of them to be the value::
...
>>> print(Coordinate['PY'])
PY
Coordinate.PY
>>> print(Coordinate(3))
VY
Coordinate.VY
Finer Points
@ -949,35 +945,36 @@ but remain normal attributes.
""""""""""""""""""""
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
members from other members -- this practice was discouraged, and in ``3.12``
:class:`Enum` will return to not allowing it, while in ``3.10`` and ``3.11``
it will raise a :exc:`DeprecationWarning`::
``EnumClass.member``. In Python versions ``3.5`` to ``3.10`` you could access
members from other members -- this practice was discouraged, and in ``3.11``
:class:`Enum` returns to not allowing it::
>>> class FieldTypes(Enum):
... name = 0
... value = 1
... size = 2
...
>>> FieldTypes.value.size # doctest: +SKIP
DeprecationWarning: accessing one member from another is not supported,
and will be disabled in 3.12
<FieldTypes.size: 2>
>>> FieldTypes.value.size
Traceback (most recent call last):
...
AttributeError: <enum 'FieldTypes'> member has no attribute 'size'
.. versionchanged:: 3.5
.. versionchanged:: 3.11
Creating members that are mixed with other data types
"""""""""""""""""""""""""""""""""""""""""""""""""""""
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::
>>> class MyEnum(IntEnum):
... example = '11', 16 # '11' will be interpreted as a hexadecimal
... # number
>>> MyEnum.example.value
>>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer
... example = '11', 16 # so x='11' and base=16
...
>>> MyEnum.example.value # and hex(11) is...
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`_
class below, those methods will show up in a :func:`dir` of the member and the
class. Attributes defined in an :func:`__init__` method will only show up in a
:func:`dir` of the member::
class below, those methods will show up in a :func:`dir` of the member,
but not of the class::
>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__init__', '__members__', '__module__', 'surface_gravity']
>>> dir(Planet.EARTH)
>>> dir(Planet) # doctest: +SKIP
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH) # doctest: +SKIP
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
@ -1025,19 +1021,10 @@ are comprised of a single bit::
... CYAN = GREEN | BLUE
...
>>> Color(3) # named combination
Color.YELLOW
<Color.YELLOW: 3>
>>> 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
""""""""""""""""""""""""""""""""
@ -1060,16 +1047,16 @@ the following are true:
- only canonical flags are returned during iteration::
>>> 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
corresponding positive integer value::
>>> Color.BLUE
Color.BLUE
<Color.BLUE: 4>
>>> ~Color.BLUE
Color.RED|Color.GREEN
<Color.RED|GREEN: 3>
- 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::
>>> Color.RED | Color.BLUE
Color.PURPLE
<Color.PURPLE: 5>
>>> Color(7) # or Color(-1)
Color.WHITE
<Color.WHITE: 7>
>>> Color(0)
Color.BLACK
<Color.BLACK: 0>
- membership / containment checking has changed slightly -- zero-valued flags
are never considered to be contained::
- membership / containment checking: zero-valued flags are always considered
to be contained::
>>> 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
True
>>> Color.GREEN in Color.PURPLE
False
There is a new boundary mechanism that controls how out-of-range / invalid
bits are handled: ``STRICT``, ``CONFORM``, ``EJECT``, and ``KEEP``:
@ -1181,7 +1172,7 @@ Using :class:`auto` would look like::
... GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN>
<Color.GREEN: 3>
Using :class:`object`
@ -1194,10 +1185,24 @@ Using :class:`object` would look like::
... GREEN = 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>
Using a descriptive string
""""""""""""""""""""""""""
@ -1209,9 +1214,7 @@ Using a string as the value would look like::
... BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
'go'
<Color.GREEN: 'go'>
Using a custom :meth:`__new__`
@ -1232,9 +1235,7 @@ Using an auto-numbering :meth:`__new__` would look like::
... BLUE = ()
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
2
<Color.GREEN: 2>
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!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN>
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
@ -1384,30 +1385,9 @@ An example to show the :attr:`_ignore_` attribute in use::
... Period['day_%d' % i] = i
...
>>> 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:]
[Period.day_365, Period.day_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
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
.. _enumtype-examples:

View File

@ -31,7 +31,7 @@ An enumeration:
* uses *call* syntax to return members by value
* 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::
>>> from enum import Enum
@ -45,7 +45,7 @@ using function-call syntax::
>>> # functional syntax
>>> 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
: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 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
:attr:`Color.RED` is ``RED``, the value of :attr:`Color.BLUE` is
``3``, etc.)
@ -110,15 +110,10 @@ Module Contents
:class:`StrEnum` defaults to the lower-cased version of the member name,
while other Enums default to 1 and increase from there.
:func:`global_enum`
:class:`Enum` class decorator to apply the appropriate global `__repr__`,
and export its members into the global name space.
:func:`.property`
:func:`property`
Allows :class:`Enum` members to have attributes without conflicting with
other members' names.
member names.
:func:`unique`
@ -131,7 +126,7 @@ Module Contents
.. 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>`
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)
Returns ``True`` if member belongs to the ``cls``::
@ -162,32 +162,31 @@ Data Types
.. method:: EnumType.__dir__(cls)
Returns ``['__class__', '__doc__', '__members__', '__module__']`` and the
names of the members in ``cls``. User-defined methods and methods from
mixin classes will also be included::
names of the members in *cls*::
>>> 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)
Returns the Enum member in *cls* matching *name*, or raises an :exc:`AttributeError`::
>>> Color.GREEN
Color.GREEN
<Color.GREEN: 2>
.. 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: 3>
.. method:: EnumType.__iter__(cls)
Returns each member in *cls* in definition order::
>>> list(Color)
[Color.RED, Color.GREEN, Color.BLUE]
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>]
.. method:: EnumType.__len__(cls)
@ -201,7 +200,7 @@ Data Types
Returns each member in *cls* in reverse definition order::
>>> list(reversed(Color))
[Color.BLUE, Color.GREEN, Color.RED]
[<Color.BLUE: 3>, <Color.GREEN: 2>, <Color.RED: 1>]
.. class:: Enum
@ -232,7 +231,7 @@ Data Types
.. attribute:: Enum._ignore_
``_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
names will also be removed from the completed enumeration. See
@ -261,7 +260,7 @@ Data Types
.. method:: Enum.__dir__(self)
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
>>> class Weekday(Enum):
@ -276,7 +275,7 @@ Data Types
... def today(cls):
... print('today is %s' % cls(date.today().isoweekday()).name)
>>> 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)
@ -298,6 +297,11 @@ Data Types
>>> PowersOfThree.SECOND.value
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)
A *classmethod* for looking up values not found in *cls*. By default it
@ -317,39 +321,52 @@ Data Types
>>> Build.DEBUG.value
'debug'
>>> Build('deBUG')
Build.DEBUG
<Build.DEBUG: 'debug'>
.. method:: Enum.__repr__(self)
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):
... RETRO = auto()
... OLD_SCHOOl = auto()
... YESTERYEAR = auto()
>>> class OtherStyle(Enum):
... ALTERNATE = auto()
... OTHER = auto()
... SOMETHING_ELSE = auto()
... def __repr__(self):
... cls_name = self.__class__.__name__
... return f'<{cls_name}.{self.name}: {self.value}>'
>>> OldStyle.RETRO
<OldStyle.RETRO: 1>
... return f'{cls_name}.{self.name}'
>>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}"
(OtherStyle.ALTERNATE, 'OtherStyle.ALTERNATE', 'OtherStyle.ALTERNATE')
.. method:: Enum.__str__(self)
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):
... RETRO = auto()
... OLD_SCHOOl = auto()
... YESTERYEAR = auto()
>>> class OtherStyle(Enum):
... ALTERNATE = auto()
... OTHER = auto()
... SOMETHING_ELSE = auto()
... def __str__(self):
... cls_name = self.__class__.__name__
... return f'{cls_name}.{self.name}'
>>> OldStyle.RETRO
OldStyle.RETRO
... return f'{self.name}'
>>> OtherStyle.ALTERNATE, str(OtherStyle.ALTERNATE), f"{OtherStyle.ALTERNATE}"
(<OtherStyle.ALTERNATE: 1>, 'ALTERNATE', 'ALTERNATE')
.. note::
.. 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::
Using :class:`auto` with :class:`Enum` results in integers of increasing value,
starting with ``1``.
@ -367,7 +384,7 @@ Data Types
... TWO = 2
... THREE = 3
>>> Numbers.THREE
Numbers.THREE
<Numbers.THREE: 3>
>>> Numbers.ONE + Numbers.TWO
3
>>> Numbers.THREE + 5
@ -375,10 +392,14 @@ Data Types
>>> Numbers.THREE == 3
True
.. note::
.. note::
Using :class:`auto` with :class:`IntEnum` results in integers of increasing value,
starting with ``1``.
Using :class:`auto` with :class:`IntEnum` results in integers of increasing
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
@ -392,13 +413,16 @@ Data Types
instead of ``isinstance(str, unknown)``), and in those locations you
will need to use ``str(StrEnum.member)``.
.. note::
.. note::
Using :class:`auto` with :class:`StrEnum` results in the lower-cased member
name as the value.
Using :class:`auto` with :class:`StrEnum` results in values of the member name,
lower-cased.
.. 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
.. class:: Flag
@ -431,9 +455,9 @@ Data Types
Returns all contained members::
>>> list(Color.RED)
[Color.RED]
[<Color.RED: 1>]
>>> list(purple)
[Color.RED, Color.BLUE]
[<Color.RED: 1>, <Color.BLUE: 4>]
.. method:: __len__(self):
@ -461,42 +485,52 @@ Data Types
Returns current flag binary or'ed with other::
>>> Color.RED | Color.GREEN
Color.RED|Color.GREEN
<Color.RED|GREEN: 3>
.. method:: __and__(self, other)
Returns current flag binary and'ed with other::
>>> purple & white
Color.RED|Color.BLUE
<Color.RED|BLUE: 5>
>>> purple & Color.GREEN
0x0
<Color: 0>
.. method:: __xor__(self, other)
Returns current flag binary xor'ed with other::
>>> purple ^ white
Color.GREEN
<Color.GREEN: 2>
>>> purple ^ Color.GREEN
Color.RED|Color.GREEN|Color.BLUE
<Color.RED|GREEN|BLUE: 7>
.. method:: __invert__(self):
Returns all the flags in *type(self)* that are not in self::
>>> ~white
0x0
<Color: 0>
>>> ~purple
Color.GREEN
<Color.GREEN: 2>
>>> ~Color.RED
Color.GREEN|Color.BLUE
<Color.GREEN|BLUE: 6>
.. note::
.. 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::
Using :class:`auto` with :class:`Flag` results in integers that are powers
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
@ -509,9 +543,9 @@ Data Types
... GREEN = auto()
... BLUE = auto()
>>> Color.RED & 2
0x0
<Color: 0>
>>> Color.RED | 2
Color.RED|Color.GREEN
<Color.RED|GREEN: 3>
If any integer operation is performed with an *IntFlag* member, the result is
not an *IntFlag*::
@ -524,15 +558,25 @@ Data Types
* the result is a valid *IntFlag*: an *IntFlag* is returned
* the result is not a valid *IntFlag*: the result depends on the *FlagBoundary* setting
.. note::
The *repr()* of unnamed zero-valued flags has changed. It is now:
>>> Color(0)
<Color: 0>
.. note::
Using :class:`auto` with :class:`IntFlag` results in integers that are powers
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
*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
@ -582,11 +626,11 @@ Data Types
...
ValueError: invalid Flag 'Color': aliases WHITE and NEON are missing combined values of 0x18 [use enum.show_flag_values(value) for details]
.. note::
.. note::
CONTINUOUS and NAMED_FLAGS are designed to work with integer-valued members.
.. versionadded:: 3.11
.. versionadded:: 3.11
.. class:: FlagBoundary
@ -606,7 +650,7 @@ Data Types
>>> StrictFlag(2**2 + 2**4)
Traceback (most recent call last):
...
ValueError: StrictFlag: invalid value: 20
ValueError: <flag 'StrictFlag'> invalid value 20
given 0b0 10100
allowed 0b0 00111
@ -621,7 +665,7 @@ Data Types
... GREEN = auto()
... BLUE = auto()
>>> ConformFlag(2**2 + 2**4)
ConformFlag.BLUE
<ConformFlag.BLUE: 4>
.. attribute:: EJECT
@ -647,12 +691,52 @@ Data Types
... GREEN = auto()
... BLUE = auto()
>>> KeepFlag(2**2 + 2**4)
KeepFlag.BLUE|0x10
<KeepFlag.BLUE|16: 20>
.. 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
------------------------
@ -668,15 +752,6 @@ Utilities and Decorators
``_generate_next_value_`` can be overridden to customize the values used by
*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
A decorator similar to the built-in *property*, but specifically for
@ -688,7 +763,7 @@ Utilities and Decorators
*Enum* class, and *Enum* subclasses can define members with the
names ``value`` and ``name``.
.. versionadded:: 3.11
.. versionadded:: 3.11
.. decorator:: unique
@ -714,7 +789,7 @@ Utilities and Decorators
:class:`EnumCheck` are used to specify which constraints should be checked
on the decorated enumeration.
.. versionadded:: 3.11
.. versionadded:: 3.11
---------------
@ -726,14 +801,20 @@ Notes
These three enum types are designed to be drop-in replacements for existing
integer- and string-based values; as such, they have extra limitations:
- ``format()`` will use the value of the enum member, unless ``__str__``
has been overridden
- ``__str__`` uses the value and not the name of the enum member
- ``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
class by mixing in the ``int`` or ``str`` type yourself::
If you do not need/want those limitations, you can either create your own
base class by mixing in the ``int`` or ``str`` type yourself::
>>> from enum import Enum
>>> class MyIntEnum(int, Enum):
... 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:
>>> ssl.create_default_context().verify_flags # doctest: +SKIP
ssl.VERIFY_X509_TRUSTED_FIRST
<VerifyFlags.VERIFY_X509_TRUSTED_FIRST: 32768>
.. 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:
>>> ssl.create_default_context().verify_mode
ssl.CERT_REQUIRED
<VerifyMode.CERT_REQUIRED: 2>
.. index:: single: certificates

View File

@ -1,16 +1,16 @@
import sys
import builtins as bltns
from types import MappingProxyType, DynamicClassAttribute
from operator import or_ as _or_
from functools import reduce
from builtins import property as _bltin_property, bin as _bltin_bin
__all__ = [
'EnumType', 'EnumMeta',
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag',
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum',
'auto', 'unique', 'property', 'verify',
'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',
]
@ -18,7 +18,7 @@ __all__ = [
# Dummy value for Enum and Flag as there are explicit checks for them
# before they have been created.
# 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):
"""
@ -116,9 +116,9 @@ def bin(num, max_bits=None):
ceiling = 2 ** (num).bit_length()
if num >= 0:
s = _bltin_bin(num + ceiling).replace('1', '0', 1)
s = bltns.bin(num + ceiling).replace('1', '0', 1)
else:
s = _bltin_bin(~num ^ (ceiling - 1) + ceiling)
s = bltns.bin(~num ^ (ceiling - 1) + ceiling)
sign = s[:3]
digits = s[3:]
if max_bits is not None:
@ -126,6 +126,19 @@ def bin(num, max_bits=None):
digits = (sign[-1] * max_bits + digits)[-max_bits:]
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()
class auto:
@ -149,22 +162,12 @@ class property(DynamicClassAttribute):
return ownerclass._member_map_[self.name]
except KeyError:
raise AttributeError(
'%s: no class attribute %r' % (ownerclass.__name__, self.name)
'%r has no attribute %r' % (ownerclass, self.name)
)
else:
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(
'%s: no instance attribute %r' % (ownerclass.__name__, self.name)
'%r member has no attribute %r' % (ownerclass, self.name)
)
else:
return self.fget(instance)
@ -172,7 +175,7 @@ class property(DynamicClassAttribute):
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError(
"%s: cannot set instance attribute %r" % (self.clsname, self.name)
"<enum %r> cannot set attribute %r" % (self.clsname, self.name)
)
else:
return self.fset(instance, value)
@ -180,7 +183,7 @@ class property(DynamicClassAttribute):
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError(
"%s: cannot delete instance attribute %r" % (self.clsname, self.name)
"<enum %r> cannot delete attribute %r" % (self.clsname, self.name)
)
else:
return self.fdel(instance)
@ -328,7 +331,7 @@ class _EnumDict(dict):
elif _is_sunder(key):
if key not in (
'_order_',
'_generate_next_value_', '_missing_', '_ignore_',
'_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_',
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_',
):
raise ValueError(
@ -358,13 +361,13 @@ class _EnumDict(dict):
key = '_order_'
elif key in self._member_names:
# 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:
pass
elif not _is_descriptor(value):
if key in self:
# 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 value.value == _auto_null:
value.value = self._generate_next_value(
@ -395,7 +398,7 @@ class EnumType(type):
@classmethod
def __prepare__(metacls, cls, bases, **kwds):
# 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
enum_dict = _EnumDict()
enum_dict._cls_name = cls
@ -413,9 +416,10 @@ class EnumType(type):
# inherited __new__ unless a new __new__ is defined (or the resulting
# class will fail).
#
# remove any keys listed in _ignore_
if _simple:
return super().__new__(metacls, cls, bases, classdict, **kwds)
#
# remove any keys listed in _ignore_
classdict.setdefault('_ignore_', []).append('_ignore_')
ignore = classdict['_ignore_']
for key in ignore:
@ -427,8 +431,8 @@ class EnumType(type):
# check for illegal enum names (any others?)
invalid_names = set(member_names) & {'mro', ''}
if invalid_names:
raise ValueError('Invalid enum member name: {0}'.format(
','.join(invalid_names)))
raise ValueError('invalid enum member name(s) '.format(
','.join(repr(n) for n in invalid_names)))
#
# adjust the sunders
_order_ = classdict.pop('_order_', None)
@ -458,6 +462,8 @@ class EnumType(type):
classdict['_value2member_map_'] = {}
classdict['_unhashable_values_'] = []
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
classdict['_boundary_'] = (
@ -467,10 +473,6 @@ class EnumType(type):
classdict['_flag_mask_'] = flag_mask
classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1
classdict['_inverted_'] = None
#
# create a default docstring if one has not been provided
if '__doc__' not in classdict:
classdict['__doc__'] = 'An enumeration.'
try:
exc = None
enum_class = super().__new__(metacls, cls, bases, classdict, **kwds)
@ -481,18 +483,140 @@ class EnumType(type):
if exc is not None:
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
# things break (such as pickle)
# however, if the method is defined in the Enum itself, don't replace
# 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__'):
if name in classdict:
continue
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)
if name not in classdict:
setattr(enum_class, name, getattr(first_enum, name))
#
# replace any other __new__ with our own (as long as Enum is not None,
# anyway) -- again, this is to support pickle
@ -563,13 +687,13 @@ class EnumType(type):
# _order_ step 4: verify that _order_ and _member_names_ match
if _order_ != enum_class._member_names_:
raise TypeError(
'member order does not match _order_:\n%r\n%r'
'member order does not match _order_:\n %r\n %r'
% (enum_class._member_names_, _order_)
)
#
return enum_class
def __bool__(self):
def __bool__(cls):
"""
classes/types should always be True.
"""
@ -614,6 +738,13 @@ class EnumType(type):
)
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):
import warnings
warnings.warn(
@ -631,60 +762,33 @@ class EnumType(type):
# nicer error message when someone tries to delete an attribute
# (see issue19025).
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)
def __dir__(self):
# Start off with the desired result for dir(Enum)
cls_dir = {'__class__', '__doc__', '__members__', '__module__'}
add_to_dir = cls_dir.add
mro = self.__mro__
this_module = globals().values()
is_from_this_module = lambda cls: any(cls is thing for thing in this_module)
first_enum_base = next(cls for cls in mro if is_from_this_module(cls))
enum_dict = Enum.__dict__
sentinel = object()
# special-case __new__
ignored = {'__new__', *filter(_is_sunder, enum_dict)}
add_to_ignored = ignored.add
# We want these added to __dir__
# if and only if they have been user-overridden
enum_dunders = set(filter(_is_dunder, enum_dict))
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.
def __dir__(cls):
# TODO: check for custom __init__, __new__, __format__, __repr__, __str__, __init_subclass__
# on object-based enums
if cls._member_type_ is object:
interesting = set(cls._member_names_)
if cls._new_member_ is not object.__new__:
interesting.add('__new__')
if cls.__init_subclass__ is not object.__init_subclass__:
interesting.add('__init_subclass__')
for method in ('__init__', '__format__', '__repr__', '__str__'):
if getattr(cls, method) not in (getattr(Enum, method), getattr(Flag, method)):
interesting.add(method)
return sorted(set([
'__class__', '__contains__', '__doc__', '__getitem__',
'__iter__', '__len__', '__members__', '__module__',
'__name__', '__qualname__',
]) | interesting
)
else:
add_to_dir(attr_name)
# sort the output before returning it, so that the result is deterministic.
return sorted(cls_dir)
# return whatever mixed-in data type has
return sorted(set(
dir(cls._member_type_)
+ cls._member_names_
))
def __getattr__(cls, name):
"""
@ -703,18 +807,24 @@ class EnumType(type):
raise AttributeError(name) from None
def __getitem__(cls, name):
"""
Return the member matching `name`.
"""
return cls._member_map_[name]
def __iter__(cls):
"""
Returns members in definition order.
Return members in definition order.
"""
return (cls._member_map_[name] for name in cls._member_names_)
def __len__(cls):
"""
Return the number of members (no aliases)
"""
return len(cls._member_names_)
@_bltin_property
@bltns.property
def __members__(cls):
"""
Returns a mapping of member name->value.
@ -732,7 +842,7 @@ class EnumType(type):
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_))
@ -746,7 +856,7 @@ class EnumType(type):
"""
member_map = cls.__dict__.get('_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)
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)
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
"""
@ -834,22 +943,25 @@ class EnumType(type):
tmp_cls = type(name, (object, ), body)
cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls)
cls.__reduce_ex__ = _reduce_ex_by_global_name
if as_global:
global_enum(cls)
else:
sys.modules[cls.__module__].__dict__.update(cls.__members__)
module_globals[name] = cls
return cls
@staticmethod
def _check_for_existing_members(class_name, bases):
@classmethod
def _check_for_existing_members_(mcls, class_name, bases):
for chain in bases:
for base in chain.__mro__:
if issubclass(base, Enum) and base._member_names_:
raise TypeError(
"%s: cannot extend enumeration %r"
% (class_name, base.__name__)
"<enum %r> cannot extend %r"
% (class_name, base)
)
@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
enum class.
@ -859,7 +971,33 @@ class EnumType(type):
if not bases:
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()
for chain in bases:
candidate = None
@ -878,24 +1016,14 @@ class EnumType(type):
else:
candidate = candidate or base
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:
return data_types.pop()
else:
return None
# 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)`")
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):
@classmethod
def _find_new_(mcls, classdict, member_type, first_enum):
"""
Returns the __new__ to be used for creating the enum members.
@ -943,9 +1071,42 @@ EnumMeta = 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):
@ -999,6 +1160,9 @@ class Enum(metaclass=EnumType):
exc = None
ve_exc = None
def __init__(self, *args, **kwds):
pass
def _generate_next_value_(name, start, count, last_values):
"""
Generate the next value when not given.
@ -1021,47 +1185,44 @@ class Enum(metaclass=EnumType):
return None
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):
return "%s" % (self._name_, )
return "%s.%s" % (self.__class__.__name__, self._name_, )
def __dir__(self):
"""
Returns all members and all public methods
"""
cls = type(self)
to_exclude = {'__members__', '__init__', '__new__', *cls._member_names_}
filtered_self_dict = (name for name in self.__dict__ if not name.startswith('_'))
return sorted({'name', 'value', *dir(cls), *filtered_self_dict} - to_exclude)
if self.__class__._member_type_ is object:
interesting = set(['__class__', '__doc__', '__eq__', '__hash__', '__module__', 'name', 'value'])
else:
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):
"""
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)
return str.__format__(str(self), format_spec)
def __hash__(self):
return hash(self._name_)
@ -1088,34 +1249,25 @@ class Enum(metaclass=EnumType):
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
"""
def __str__(self):
return "%s" % (self._name_, )
def __format__(self, format_spec):
"""
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):
class StrEnum(str, ReprEnum):
"""
Enum where members are also (and must be) strings
"""
def __new__(cls, *values):
"values must already be of type `str`"
if len(values) > 3:
raise TypeError('too many arguments for str(): %r' % (values, ))
if len(values) == 1:
@ -1135,10 +1287,6 @@ class StrEnum(str, Enum):
member._value_ = value
return member
__str__ = str.__str__
__format__ = str.__format__
def _generate_next_value_(name, start, count, last_values):
"""
Return the lower-cased version of the member name.
@ -1169,6 +1317,8 @@ class Flag(Enum, boundary=STRICT):
Support for flags
"""
_numeric_repr_ = repr
def _generate_next_value_(name, start, count, last_values):
"""
Generate the next value when not given.
@ -1184,7 +1334,7 @@ class Flag(Enum, boundary=STRICT):
try:
high_bit = _high_bit(last_value)
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)
@classmethod
@ -1232,8 +1382,8 @@ class Flag(Enum, boundary=STRICT):
if cls._boundary_ is STRICT:
max_bits = max(value.bit_length(), flag_mask.bit_length())
raise ValueError(
"%s: invalid value: %r\n given %s\n allowed %s" % (
cls.__name__, value, bin(value, max_bits), bin(flag_mask, max_bits),
"%r invalid value %r\n given %s\n allowed %s" % (
cls, value, bin(value, max_bits), bin(flag_mask, max_bits),
))
elif cls._boundary_ is CONFORM:
value = value & flag_mask
@ -1247,7 +1397,7 @@ class Flag(Enum, boundary=STRICT):
)
else:
raise ValueError(
'unknown flag boundary: %r' % (cls._boundary_, )
'%r unknown flag boundary %r' % (cls, cls._boundary_, )
)
if value < 0:
neg_value = value
@ -1274,7 +1424,7 @@ class Flag(Enum, boundary=STRICT):
m._name_ for m in cls._iter_member_(member_value)
])
if unknown:
pseudo_member._name_ += '|0x%x' % unknown
pseudo_member._name_ += '|%s' % cls._numeric_repr_(unknown)
else:
pseudo_member._name_ = None
# 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__):
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__))
if other._value_ == 0 or self._value_ == 0:
return False
return other._value_ & self._value_ == other._value_
def __iter__(self):
@ -1309,27 +1457,18 @@ class Flag(Enum, boundary=STRICT):
def __repr__(self):
cls_name = self.__class__.__name__
v_repr = self.__class__._value_repr_ or self._value_.__class__.__repr__
if self._name_ is None:
return "0x%x" % (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('|'))
return "<%s: %s>" % (cls_name, v_repr(self._value_))
else:
name = []
for n in self._name_.split('|'):
if n.startswith('0'):
name.append(n)
else:
name.append('%s.%s' % (cls_name, n))
return '|'.join(name)
return "<%s.%s: %s>" % (cls_name, self._name_, v_repr(self._value_))
def __str__(self):
cls = self.__class__
cls_name = self.__class__.__name__
if self._name_ is None:
return '%s(%x)' % (cls.__name__, self._value_)
return '%s(%r)' % (cls_name, self._value_)
else:
return self._name_
return "%s.%s" % (cls_name, self._name_)
def __bool__(self):
return bool(self._value_)
@ -1362,20 +1501,11 @@ class Flag(Enum, boundary=STRICT):
return self._inverted_
class IntFlag(int, Flag, boundary=EJECT):
class IntFlag(int, ReprEnum, Flag, boundary=EJECT):
"""
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):
if isinstance(other, self.__class__):
@ -1412,6 +1542,7 @@ class IntFlag(int, Flag, boundary=EJECT):
__rxor__ = __xor__
__invert__ = Flag.__invert__
def _high_bit(value):
"""
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]
cls_name = self.__class__.__name__
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):
return '%s.%s' % (module, self._name_)
if self._boundary_ is not FlagBoundary.KEEP:
@ -1464,14 +1595,22 @@ def global_flag_repr(self):
else:
name = []
for n in self._name_.split('|'):
if n.startswith('0'):
if n[0].isdigit():
name.append(n)
else:
name.append('%s.%s' % (module, n))
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
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
else:
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__)
return cls
@ -1522,6 +1663,7 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
body['_value2member_map_'] = value2member_map = {}
body['_unhashable_values_'] = []
body['_member_type_'] = member_type = etype._member_type_
body['_value_repr_'] = etype._value_repr_
if issubclass(etype, Flag):
body['_boundary_'] = boundary or etype._boundary_
body['_flag_mask_'] = None
@ -1543,13 +1685,8 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
# it
enum_class = type(cls_name, (etype, ), body, boundary=boundary, _simple=True)
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
if name in body:
continue
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)
if name not in body:
setattr(enum_class, name, getattr(etype, name))
gnv_last_values = []
if issubclass(enum_class, Flag):
# Flag / IntFlag
@ -1760,8 +1897,8 @@ def _test_simple_enum(checked_enum, simple_enum):
+ list(simple_enum._member_map_.keys())
)
for key in set(checked_keys + simple_keys):
if key in ('__module__', '_member_map_', '_value2member_map_'):
# keys known to be different
if key in ('__module__', '_member_map_', '_value2member_map_', '__doc__'):
# keys known to be different, or very long
continue
elif key in member_names:
# 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.__repr__ = global_enum_repr
return cls
_stdlib_enums = IntEnum, StrEnum, IntFlag

View File

@ -2567,15 +2567,21 @@ class _empty:
class _ParameterKind(enum.IntEnum):
POSITIONAL_ONLY = 0
POSITIONAL_OR_KEYWORD = 1
VAR_POSITIONAL = 2
KEYWORD_ONLY = 3
VAR_KEYWORD = 4
POSITIONAL_ONLY = 'positional-only'
POSITIONAL_OR_KEYWORD = 'positional or keyword'
VAR_POSITIONAL = 'variadic positional'
KEYWORD_ONLY = 'keyword-only'
VAR_KEYWORD = 'variadic keyword'
@property
def description(self):
return _PARAM_NAME_MAPPING[self]
def __new__(cls, description):
value = len(cls.__members__)
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_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD
@ -2583,14 +2589,6 @@ _VAR_POSITIONAL = _ParameterKind.VAR_POSITIONAL
_KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY
_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:
"""Represents a parameter in a function signature.

View File

@ -61,7 +61,8 @@ import struct
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:

View File

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

View File

@ -119,7 +119,6 @@ from _ssl import (
)
from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION
_IntEnum._convert_(
'_SSLMethod', __name__,
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
blocked = %r
blocked = %s
signum = signal.SIGALRM
# 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)
for family, type, _, _, _ in infos:
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(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)
for _, socktype, _, _, _ in infos:
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
# reprs.
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
self.assertEqual(str(s.family), 'AF_INET')
self.assertEqual(str(s.type), 'SOCK_STREAM')
self.assertEqual(repr(s.family), '<AddressFamily.AF_INET: 2>')
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):
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
# reprs.
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)
self.assertIs(ctx.protocol, proto)
@ -622,7 +623,7 @@ class BasicSocketTests(unittest.TestCase):
with self.assertWarns(DeprecationWarning) as cm:
ssl.SSLContext(protocol)
self.assertEqual(
f'{protocol!r} is deprecated',
f'ssl.{protocol.name} is deprecated',
str(cm.warning)
)
@ -631,8 +632,9 @@ class BasicSocketTests(unittest.TestCase):
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
with self.assertWarns(DeprecationWarning) as cm:
ctx.minimum_version = version
version_text = '%s.%s' % (version.__class__.__name__, version.name)
self.assertEqual(
f'ssl.{version!r} is deprecated',
f'ssl.{version_text} is deprecated',
str(cm.warning)
)

View File

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