diff options
Diffstat (limited to 'Doc/howto/enum.rst')
-rw-r--r-- | Doc/howto/enum.rst | 272 |
1 files changed, 146 insertions, 126 deletions
diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index fa0e2283eb..6c09b9925c 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -2,10 +2,15 @@ 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. @@ -23,14 +28,6 @@ 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. @@ -44,14 +41,13 @@ important, but either way that value can be used to get the corresponding member:: >>> Weekday(3) - <Weekday.WEDNESDAY: 3> + Weekday.WEDNESDAY -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:: +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:: >>> print(Weekday.THURSDAY) - Weekday.THURSDAY + THURSDAY The *type* of an enumeration member is the enum it belongs to:: @@ -101,8 +97,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()) # doctest: +SKIP - <Weekday.TUESDAY: 2> + >>> Weekday.from_date(date.today()) + Weekday.TUESDAY Of course, if you're reading this on some other day, you'll see that day instead. @@ -128,21 +124,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: 1> + Weekday.MONDAY But :class:`Flag` also allows us to combine several members into a single variable:: >>> weekend = Weekday.SATURDAY | Weekday.SUNDAY >>> weekend - <Weekday.SATURDAY|SUNDAY: 96> + Weekday.SATURDAY|Weekday.SUNDAY You can even iterate over a :class:`Flag` variable:: >>> for day in weekend: ... print(day) - Weekday.SATURDAY - Weekday.SUNDAY + SATURDAY + SUNDAY Okay, let's get some chores set up:: @@ -177,7 +173,6 @@ yourself some work and use :func:`auto()` for the values:: .. _enum-advanced-tutorial: - Programmatic access to enumeration members and their attributes --------------------------------------------------------------- @@ -186,16 +181,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: 1> + Color.RED >>> Color(3) - <Color.BLUE: 3> + Color.BLUE If you want to access enum members by *name*, use item access:: >>> Color['RED'] - <Color.RED: 1> + Color.RED >>> Color['GREEN'] - <Color.GREEN: 2> + Color.GREEN If you have an enum member and need its :attr:`name` or :attr:`value`:: @@ -217,7 +212,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`` @@ -232,11 +227,11 @@ By-name lookup of ``B`` will also return the member ``A``:: ... ALIAS_FOR_SQUARE = 2 ... >>> Shape.SQUARE - <Shape.SQUARE: 2> + Shape.SQUARE >>> Shape.ALIAS_FOR_SQUARE - <Shape.SQUARE: 2> + Shape.SQUARE >>> Shape(2) - <Shape.SQUARE: 2> + Shape.SQUARE .. note:: @@ -304,7 +299,7 @@ Iteration Iterating over the members of an enum does not provide the aliases:: >>> list(Shape) - [<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>] + [Shape.SQUARE, Shape.DIAMOND, Shape.CIRCLE] The special attribute ``__members__`` is a read-only ordered mapping of names to members. It includes all names defined in the enumeration, including the @@ -313,10 +308,10 @@ aliases:: >>> for name, member in Shape.__members__.items(): ... name, member ... - ('SQUARE', <Shape.SQUARE: 2>) - ('DIAMOND', <Shape.DIAMOND: 1>) - ('CIRCLE', <Shape.CIRCLE: 3>) - ('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>) + ('SQUARE', Shape.SQUARE) + ('DIAMOND', Shape.DIAMOND) + ('CIRCLE', Shape.CIRCLE) + ('ALIAS_FOR_SQUARE', Shape.SQUARE) The ``__members__`` attribute can be used for detailed programmatic access to the enumeration members. For example, finding all the aliases:: @@ -365,8 +360,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. @@ -394,7 +389,7 @@ usual. If we have this enumeration:: Then:: >>> Mood.favorite_mood() - <Mood.HAPPY: 3> + Mood.HAPPY >>> Mood.HAPPY.describe() ('HAPPY', 3) >>> str(Mood.FUNKY) @@ -430,7 +425,7 @@ any members. So this is forbidden:: ... Traceback (most recent call last): ... - TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'> + TypeError: MoreColor: cannot extend enumeration 'Color' But this is allowed:: @@ -481,9 +476,11 @@ The :class:`Enum` class is callable, providing the following functional API:: >>> Animal <enum 'Animal'> >>> Animal.ANT - <Animal.ANT: 1> + Animal.ANT + >>> Animal.ANT.value + 1 >>> list(Animal) - [<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>] + [Animal.ANT, Animal.BEE, Animal.CAT, Animal.DOG] The semantics of this API resemble :class:`~collections.namedtuple`. The first argument of the call to :class:`Enum` is the name of the enumeration. @@ -628,7 +625,16 @@ 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. +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',)``. .. versionadded:: 3.11 @@ -639,8 +645,9 @@ 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. Like :class:`IntEnum`, :class:`IntFlag` -members are also integers and can be used wherever an :class:`int` is used. +: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. .. note:: @@ -663,7 +670,7 @@ Sample :class:`IntFlag` class:: ... X = 1 ... >>> Perm.R | Perm.W - <Perm.R|W: 6> + Perm.R|Perm.W >>> Perm.R + Perm.W 6 >>> RW = Perm.R | Perm.W @@ -678,11 +685,11 @@ It is also possible to name the combinations:: ... X = 1 ... RWX = 7 >>> Perm.RWX - <Perm.RWX: 7> + Perm.RWX >>> ~Perm.RWX - <Perm: 0> + Perm(0) >>> Perm(7) - <Perm.RWX: 7> + Perm.RWX .. note:: @@ -695,7 +702,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 @@ -703,7 +710,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|X: 5> + Perm.R|Perm.X >>> Perm.X | 8 9 @@ -719,7 +726,7 @@ be combined with them (but may lose :class:`IntFlag` membership:: :class:`IntFlag` members can also be iterated over:: >>> list(RW) - [<Perm.R: 4>, <Perm.W: 2>] + [Perm.R, Perm.W] .. versionadded:: 3.11 @@ -746,7 +753,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 @@ -760,7 +767,7 @@ while combinations of flags won't:: ... WHITE = RED | BLUE | GREEN ... >>> Color.WHITE - <Color.WHITE: 7> + Color.WHITE Giving a name to the "no flags set" condition does not change its boolean value:: @@ -772,7 +779,7 @@ value:: ... GREEN = auto() ... >>> Color.BLACK - <Color.BLACK: 0> + Color.BLACK >>> bool(Color.BLACK) False @@ -780,7 +787,7 @@ value:: >>> purple = Color.RED | Color.BLUE >>> list(purple) - [<Color.RED: 1>, <Color.BLUE: 2>] + [Color.RED, Color.BLUE] .. versionadded:: 3.11 @@ -805,16 +812,16 @@ simple to implement independently:: pass This demonstrates how similar derived enumerations can be defined; for example -a :class:`FloatEnum` that mixes in :class:`float` instead of :class:`int`. +a :class:`StrEnum` that mixes in :class:`str` 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 @@ -822,18 +829,15 @@ 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 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. + 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. When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------ @@ -862,10 +866,10 @@ want one of them to be the value:: ... >>> print(Coordinate['PY']) - Coordinate.PY + PY >>> print(Coordinate(3)) - Coordinate.VY + VY Finer Points @@ -923,8 +927,8 @@ and raise an error if the two do not match:: Traceback (most recent call last): ... TypeError: member order does not match _order_: - ['RED', 'BLUE', 'GREEN'] - ['RED', 'GREEN', 'BLUE'] + ['RED', 'BLUE', 'GREEN'] + ['RED', 'GREEN', 'BLUE'] .. note:: @@ -945,36 +949,35 @@ 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.10`` you could access -members from other members -- this practice was discouraged, and in ``3.11`` -:class:`Enum` returns to not allowing it:: +``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`:: >>> class FieldTypes(Enum): ... name = 0 ... value = 1 ... size = 2 ... - >>> FieldTypes.value.size - Traceback (most recent call last): - ... - AttributeError: <enum 'FieldTypes'> member has no attribute 'size' - + >>> FieldTypes.value.size # doctest: +SKIP + DeprecationWarning: accessing one member from another is not supported, + and will be disabled in 3.12 + <FieldTypes.size: 2> .. 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): # help(int) -> int(x, base=10) -> integer - ... example = '11', 16 # so x='11' and base=16 - ... - >>> MyEnum.example.value # and hex(11) is... + >>> class MyEnum(IntEnum): + ... example = '11', 16 # '11' will be interpreted as a hexadecimal + ... # number + >>> MyEnum.example.value 17 @@ -997,12 +1000,13 @@ 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, -but not of the class:: +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:: - >>> dir(Planet) # doctest: +SKIP - ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__'] - >>> dir(Planet.EARTH) # doctest: +SKIP + >>> dir(Planet) + ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__init__', '__members__', '__module__', 'surface_gravity'] + >>> dir(Planet.EARTH) ['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value'] @@ -1021,10 +1025,19 @@ are comprised of a single bit:: ... CYAN = GREEN | BLUE ... >>> Color(3) # named combination - <Color.YELLOW: 3> + Color.YELLOW >>> Color(7) # not named combination - <Color.RED|GREEN|BLUE: 7> + Color.RED|Color.GREEN|Color.BLUE +``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 """""""""""""""""""""""""""""""" @@ -1047,16 +1060,16 @@ the following are true: - only canonical flags are returned during iteration:: >>> list(Color.WHITE) - [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>] + [Color.RED, Color.GREEN, Color.BLUE] - negating a flag or flag set returns a new flag/flag set with the corresponding positive integer value:: >>> Color.BLUE - <Color.BLUE: 4> + Color.BLUE >>> ~Color.BLUE - <Color.RED|GREEN: 3> + Color.RED|Color.GREEN - names of pseudo-flags are constructed from their members' names:: @@ -1066,29 +1079,25 @@ the following are true: - multi-bit flags, aka aliases, can be returned from operations:: >>> Color.RED | Color.BLUE - <Color.PURPLE: 5> + Color.PURPLE >>> Color(7) # or Color(-1) - <Color.WHITE: 7> + Color.WHITE >>> Color(0) - <Color.BLACK: 0> + Color.BLACK -- membership / containment checking: zero-valued flags are always considered - to be contained:: +- membership / containment checking has changed slightly -- zero-valued flags + are never considered to be contained:: >>> Color.BLACK in Color.WHITE - True + False - otherwise, only if all bits of one flag are in the other flag will True - be returned:: + otherwise, if all bits of one flag are in the other flag, True is 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``: @@ -1172,7 +1181,7 @@ Using :class:`auto` would look like:: ... GREEN = auto() ... >>> Color.GREEN - <Color.GREEN: 3> + <Color.GREEN> Using :class:`object` @@ -1185,24 +1194,10 @@ 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 """""""""""""""""""""""""" @@ -1214,7 +1209,9 @@ Using a string as the value would look like:: ... BLUE = 'too fast!' ... >>> Color.GREEN - <Color.GREEN: 'go'> + <Color.GREEN> + >>> Color.GREEN.value + 'go' Using a custom :meth:`__new__` @@ -1235,7 +1232,9 @@ Using an auto-numbering :meth:`__new__` would look like:: ... BLUE = () ... >>> Color.GREEN - <Color.GREEN: 2> + <Color.GREEN> + >>> Color.GREEN.value + 2 To make a more general purpose ``AutoNumber``, add ``*args`` to the signature:: @@ -1258,7 +1257,7 @@ to handle any extra arguments:: ... BLEACHED_CORAL = () # New color, no Pantone code yet! ... >>> Swatch.SEA_GREEN - <Swatch.SEA_GREEN: 2> + <Swatch.SEA_GREEN> >>> Swatch.SEA_GREEN.pantone '1246' >>> Swatch.BLEACHED_CORAL.pantone @@ -1385,9 +1384,30 @@ An example to show the :attr:`_ignore_` attribute in use:: ... Period['day_%d' % i] = i ... >>> list(Period)[:2] - [<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>] + [Period.day_0, Period.day_1] >>> list(Period)[-2:] - [<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>] + [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 .. _enumtype-examples: |