Monday, May 6, 2024
HomePythonConstruct Enumerations of Constants With Python's Enum – Actual Python

Construct Enumerations of Constants With Python’s Enum – Actual Python


Python’s enum module gives the Enum class, which lets you create enumeration varieties. To create your individual enumerations, you’ll be able to both subclass Enum or use its practical API. Each choices will allow you to outline a set of associated constants as enum members.

Within the following sections, you’ll discover ways to create enumerations in your code utilizing the Enum class. You’ll additionally discover ways to set routinely generated values in your enums and easy methods to create enumerations containing alias and distinctive values. To kick issues off, you’ll begin by studying easy methods to create an enumeration by subclassing Enum.

Creating Enumerations by Subclassing Enum

The enum module defines a general-purpose enumeration sort with iteration and comparability capabilities. You should utilize this kind to create units of named constants that you should use to interchange literals of widespread knowledge varieties, equivalent to numbers and strings.

A basic instance of when it is best to use an enumeration is when it’s worthwhile to create a set of enumerated constants representing the times of the week. Every day could have a symbolic title and a numeric worth between 1 and 7, inclusive.

Right here’s how one can create this enumeration through the use of Enum as your superclass or guardian class:

>>>

>>> from enum import Enum

>>> class Day(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...

>>> listing(Day)
[
    <Day.MONDAY: 1>,
    <Day.TUESDAY: 2>,
    <Day.WEDNESDAY: 3>,
    <Day.THURSDAY: 4>,
    <Day.FRIDAY: 5>,
    <Day.SATURDAY: 6>,
    <Day.SUNDAY: 7>
]

Your Day class is a subclass of Enum. So, you’ll be able to name Day an enumeration, or simply an enum. Day.MONDAY, Day.TUESDAY, and the like are enumeration members, also referred to as enum members, or simply members. Every member should have a worth, which must be fixed.

As a result of enumeration members have to be constants, Python doesn’t permit you to assign new values to enum members at runtime:

>>>

>>> Day.MONDAY = 0
Traceback (most up-to-date name final):
    ...
AttributeError: Can not reassign members.

>>> Day
<enum 'Day'>

>>> # Rebind Day
>>> Day = "Monday"
>>> Day
'Monday'

For those who attempt to change the worth of an enum member, you then get an AttributeError. In contrast to member names, the title containing the enumeration itself isn’t a relentless however a variable. So, it’s potential to rebind this title at any second throughout your program’s execution, however it is best to keep away from doing that.

Within the instance above, you’ve reassigned Day, which now holds a string relatively than the unique enumeration. By doing this, you’ve misplaced the reference to the enum itself.

Typically, the values mapped to members are consecutive integer numbers. Nevertheless, they are often of any sort, together with user-defined varieties. On this instance, the worth of Day.MONDAY is 1, the worth of Day.TUESDAY is 2, and so forth.

You possibly can consider enumerations as collections of constants. Like lists, tuples, or dictionaries, Python enumerations are additionally iterable. That’s why you should use listing() to show an enumeration right into a listing of enumeration members.

The members of a Python enumeration are cases of the container enumeration itself:

>>>

>>> from enum import Enum

>>> class Day(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...

>>> sort(Day.MONDAY)
<enum 'Day'>

>>> sort(Day.TUESDAY)
<enum 'Day'>

You shouldn’t confuse a customized enum class like Day with its members: Day.MONDAY, Day.TUESDAY, and so forth. On this instance, the Day enum sort is a hub for enumeration members, which occur to be of sort Day.

You too can use the idiom primarily based on vary() to construct enumerations:

>>>

>>> from enum import Enum

>>> class Season(Enum):
...     WINTER, SPRING, SUMMER, FALL = vary(1, 5)
...

>>> listing(Season)
[
    <Season.WINTER: 1>,
    <Season.SPRING: 2>,
    <Season.SUMMER: 3>,
    <Season.FALL: 4>
]

On this instance, you utilize vary() with the begin and cease offsets. The begin offset permits you to present the quantity that begins the vary, whereas the cease offset defines the quantity at which the vary will cease producing numbers.

Although you utilize the class syntax to create enumerations, they’re particular courses that differ from regular Python courses. In contrast to common courses, enums:

You need to take into account all these delicate variations whenever you begin creating and dealing with your individual enumerations in Python.

Typically, the members of an enumeration take consecutive integer values. Nevertheless, in Python, the values of members may be of any sort, together with user-defined varieties. For instance, right here’s an enumeration of college grades that makes use of non-consecutive numeric values in descending order:

>>>

>>> from enum import Enum

>>> class Grade(Enum):
...     A = 90
...     B = 80
...     C = 70
...     D = 60
...     F = 0
...

>>> listing(Grade)
[
    <Grade.A: 90>,
    <Grade.B: 80>,
    <Grade.C: 70>,
    <Grade.D: 60>,
    <Grade.F: 0>
]

This instance exhibits that Python enums are fairly versatile and permit you to use any significant worth for his or her members. You possibly can set the member values in accordance with the intent of your code.

You too can use string values in your enumeration members. Right here’s an instance of a Measurement enumeration that you should use in an internet retailer:

>>>

>>> from enum import Enum

>>> class Measurement(Enum):
...     S = "small"
...     M = "medium"
...     L = "giant"
...     XL = "additional giant"
...

>>> listing(Measurement)
[
    <Size.S: 'small'>,
    <Size.M: 'medium'>,
    <Size.L: 'large'>,
    <Size.XL: 'extra large'>
]

On this instance, the worth related to every measurement holds an outline that may show you how to and different builders perceive the which means of your code.

You too can create enumerations of Boolean values. On this case, the members of your enumeration could have solely two values:

>>>

>>> from enum import Enum

>>> class SwitchPosition(Enum):
...     ON = True
...     OFF = False
...

>>> listing(SwitchPosition)
[<SwitchPosition.ON: True>, <SwitchPosition.OFF: False>]

>>> class UserResponse(Enum):
...     YES = True
...     NO = False
...

>>> listing(UserResponse)
[<UserResponse.YES: True>, <UserResponse.NO: False>]

These two examples present how you should use enumerations so as to add additional context to your code. Within the first instance, anybody studying your code will know that the code emulates a swap object with two potential states. This extra data extremely improves your code’s readability.

You too can outline an enumeration with heterogeneous values:

>>>

>>> from enum import Enum

>>> class UserResponse(Enum):
...     YES = 1
...     NO = "No"
...

>>> UserResponse.NO
<UserResponse.NO: 'No'>

>>> UserResponse.YES
<UserResponse.YES: 1>

Nevertheless, this observe makes your code inconsistent from a sort security perspective. Due to this fact, it’s not advisable observe. Ideally, it could assist when you had values of the identical knowledge sort, which is in step with the concept of grouping comparable, associated constants in enumerations.

Lastly, you too can create empty enumerations:

>>>

>>> from enum import Enum

>>> class Empty(Enum):
...     go
...

>>> listing(Empty)
[]

>>> class Empty(Enum):
...     ...
...

>>> listing(Empty)
[]

>>> class Empty(Enum):
...     """Empty enumeration for such and such functions."""
...

>>> listing(Empty)
[]

On this instance, Empty represents an empty enumeration as a result of it doesn’t outline any member constants. Be aware that you should use the go assertion, the Ellipsis literal (...), or a class-level docstring to create empty enumerations. This final method will help you enhance the readability of your code by offering additional context within the docstring.

Now, why would it’s worthwhile to outline an empty enumeration anyway? Empty enumerations can come in useful when it’s worthwhile to construct a hierarchy of enum courses to reuse performance by means of inheritance.

Think about the next instance:

>>>

>>> from enum import Enum
>>> import string

>>> class BaseTextEnum(Enum):
...     def as_list(self):
...         attempt:
...             return listing(self.worth)
...         besides TypeError:
...             return [str(self.value)]
...

>>> class Alphabet(BaseTextEnum):
...     LOWERCASE = string.ascii_lowercase
...     UPPERCASE = string.ascii_uppercase
...

>>> Alphabet.LOWERCASE.as_list()
['a', 'b', 'c', 'd', ..., 'x', 'y', 'z']

On this instance, you create BaseTextEnum as an enumeration with no members. You possibly can solely subclass a customized enumeration if it doesn’t have members, so BaseTextEnum qualifies. The Alphabet class inherits out of your empty enumeration, which implies which you could entry the .as_list() methodology. This methodology converts the worth of a given member into a listing.

Creating Enumerations With the Purposeful API

The Enum class gives a practical API that you should use to create enumerations with out utilizing the standard class syntax. You’ll simply have to name Enum with acceptable arguments such as you’d do with a operate or some other callable.

This practical API resembles the way in which wherein the namedtuple() manufacturing facility operate works. Within the case of Enum, the practical signature has the next type:

Enum(
    worth,
    names,
    *,
    module=None,
    qualname=None,
    sort=None,
    begin=1
)

From this signature, you’ll be able to conclude that Enum wants two positional arguments, worth and names. It might probably additionally take as much as 4 non-compulsory and keyword-only arguments. These arguments are module, qualname, sort, and begin.

Right here’s a desk that summarizes the content material and which means of every argument within the signature of Enum:

Argument Description Required
worth Holds a string with the title of the brand new enumeration class Sure
names Supplies names for the enumeration members Sure
module Takes the title of the module that defines the enumeration class No
qualname Holds the situation of the module that defines the enumeration class No
sort Holds a category for use as the primary mixin class No
begin Takes the beginning worth from the enumeration values will start No

To offer the names argument, you should use the next objects:

  • A string containing member names separated both with areas or commas
  • An iterable of member names
  • An iterable of name-value pairs

The module and qualname arguments play an vital function when it’s worthwhile to pickle and unpickle your enumerations. If module isn’t set, then Python will try to search out the module. If it fails, then the category won’t be picklable. Equally, if qualname isn’t set, then Python will set it to the world scope, which can trigger your enumerations to fail unpickling in some conditions.

The sort argument is required whenever you wish to present a mixin class in your enumeration. Utilizing a mixin class can present your customized enum with new performance, equivalent to prolonged comparability capabilities, as you’ll be taught within the part about mixing enumerations with different knowledge varieties.

Lastly, the begin argument gives a technique to customise the preliminary worth of your enumerations. This argument defaults to 1 relatively than to 0. The explanation for this default worth is that 0 is fake in a Boolean sense, however enum members consider to True. Due to this fact, ranging from 0 would appear stunning and complicated.

More often than not, you’ll simply use the primary two arguments to Enum when creating your enumerations. Right here’s an instance of making an enumeration of widespread HTTP strategies:

>>>

>>> from enum import Enum

>>> HTTPMethod = Enum(
...     "HTTPMethod", ["GET", "POST", "PUSH", "PATCH", "DELETE"]
... )

>>> listing(HTTPMethod)
[
    <HTTPMethod.GET: 1>,
    <HTTPMethod.POST: 2>,
    <HTTPMethod.PUSH: 3>,
    <HTTPMethod.PATCH: 4>,
    <HTTPMethod.DELETE: 5>
]

This name to Enum returns a brand new enumeration known as HTTPMethod. To offer the member names, you utilize a listing of strings. Every string represents an HTTP methodology. Be aware that the member values are routinely set to consecutive integer numbers ranging from 1. You possibly can change this preliminary worth utilizing the begin argument.

Be aware that defining the above enumerations with the category syntax will produce the identical outcome:

>>>

>>> from enum import Enum

>>> class HTTPMethod(Enum):
...     GET = 1
...     POST = 2
...     PUSH = 3
...     PATCH = 4
...     DELETE = 5
...

>>> listing(HTTPMethod)
[
    <HTTPMethod.GET: 1>,
    <HTTPMethod.POST: 2>,
    <HTTPMethod.PUSH: 3>,
    <HTTPMethod.PATCH: 4>,
    <HTTPMethod.DELETE: 5>
]

Right here, you utilize the category syntax to outline the HTTPMethod enum. This instance is totally equal to the earlier one, as you’ll be able to conclude from the output of listing().

Utilizing both the category syntax or the practical API to create your enumeration is your determination and can largely rely in your style and concrete situations. Nevertheless, if you wish to create enumerations dynamically, then the practical API may be your solely choice.

Think about the next instance, the place you create an enum with user-provided members:

>>>

>>> from enum import Enum

>>> names = []
>>> whereas True:
...     title = enter("Member title: ")
...     if title in {"q", "Q"}:
...         break
...     names.append(title.higher())
...
Member title: YES
Member title: NO
Member title: q

>>> DynamicEnum = Enum("DynamicEnum", names)
>>> listing(DynamicEnum)
[<DynamicEnum.YES: 1>, <DynamicEnum.NO: 2>]

This instance is a little bit bit excessive as a result of creating any object out of your consumer’s enter is sort of a dangerous observe, contemplating which you could’t predict what the consumer will enter. Nevertheless, the instance is meant to indicate that the practical API is the way in which to go when it’s worthwhile to create enumerations dynamically.

Lastly, if it’s worthwhile to set customized values in your enum members, then you should use an iterable of name-value pairs as your names argument. Within the instance beneath, you utilize a listing of name-value tuples to initialize all of the enumeration members:

>>>

>>> from enum import Enum

>>> HTTPStatusCode = Enum(
...     worth="HTTPStatusCode",
...     names=[
...         ("OK", 200),
...         ("CREATED", 201),
...         ("BAD_REQUEST", 400),
...         ("NOT_FOUND", 404),
...         ("SERVER_ERROR", 500),
...     ],
... )

>>> listing(HTTPStatusCode)
[
    <HTTPStatusCode.OK: 200>,
    <HTTPStatusCode.CREATED: 201>,
    <HTTPStatusCode.BAD_REQUEST: 400>,
    <HTTPStatusCode.NOT_FOUND: 404>,
    <HTTPStatusCode.SERVER_ERROR: 500>
]

Offering a listing of name-value tuples such as you did above makes it potential to create the HTTPStatusCode enumeration with customized values for the members. On this instance, when you didn’t wish to use a listing of name-value tuples, then you would additionally use a dictionary that maps names to values.

Constructing Enumerations From Computerized Values

Python’s enum module gives a handy operate known as auto() that permits you to set computerized values in your enum members. This operate’s default conduct is to assign consecutive integer values to members.

Right here’s how auto() works:

>>>

>>> from enum import auto, Enum

>>> class Day(Enum):
...     MONDAY = auto()
...     TUESDAY = auto()
...     WEDNESDAY = 3
...     THURSDAY = auto()
...     FRIDAY = auto()
...     SATURDAY = auto()
...     SUNDAY = 7
...

>>> listing(Day)
[
    <Day.MONDAY: 1>,
    <Day.TUESDAY: 2>,
    <Day.WEDNESDAY: 3>,
    <Day.THURSDAY: 4>,
    <Day.FRIDAY: 5>,
    <Day.SATURDAY: 6>,
    <Day.SUNDAY: 7>
]

You must name auto() as soon as for every computerized worth that you just want. You too can mix auto() with concrete values, similar to you probably did with Day.WEDNESDAY and Day.SUNDAY on this instance.

By default, auto() assigns consecutive integer numbers to every goal member ranging from 1. You possibly can tweak this default conduct by overriding the ._generate_next_value_() methodology, which auto() makes use of below the hood to generate the automated values.

Right here’s an instance of how to do that:

>>>

>>> from enum import Enum, auto

>>> class CardinalDirection(Enum):
...     def _generate_next_value_(title, begin, rely, last_values):
...         return title[0]
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...

>>> listing(CardinalDirection)
[
    <CardinalDirection.NORTH: 'N'>,
    <CardinalDirection.SOUTH: 'S'>,
    <CardinalDirection.EAST: 'E'>,
    <CardinalDirection.WEST: 'W'>
]

On this instance, you create an enumeration of Earth’s cardinal instructions wherein values are routinely set to strings containing the primary character of every member’s title. Be aware that you will need to present your overridden model of ._generate_next_value_() earlier than defining any members. That’s as a result of the members will likely be constructed by calling the tactic.

Creating Enumerations With Aliases and Distinctive Values

You possibly can create enumerations wherein two or extra members have the identical fixed worth. The redundant members are often called aliases and may be helpful in some conditions. For instance, say that you’ve got an enum containing a set of working programs (OS), like within the following code:

>>>

>>> from enum import Enum

>>> class OperatingSystem(Enum):
...     UBUNTU = "linux"
...     MACOS = "darwin"
...     WINDOWS = "win"
...     DEBIAN = "linux"
...

>>> # Aliases aren't listed
>>> listing(OperatingSystem)
[
    <OperatingSystem.UBUNTU: 'linux'>,
    <OperatingSystem.MACOS: 'darwin'>,
    <OperatingSystem.WINDOWS: 'win'>
]

>>> # To entry aliases, use __members__
>>> listing(OperatingSystem.__members__.objects())
[
    ('UBUNTU', <OperatingSystem.UBUNTU: 'linux'>),
    ('MACOS', <OperatingSystem.MACOS: 'darwin'>),
    ('WINDOWS', <OperatingSystem.WINDOWS: 'win'>),
    ('DEBIAN', <OperatingSystem.UBUNTU: 'linux'>)
]

Linux distributions are thought of impartial working programs. So, Ubuntu and Debian are each impartial programs with completely different targets and goal audiences. Nevertheless, they share a standard kernel known as Linux.

The above enumeration maps working programs to their corresponding kernels. This relationship turns DEBIAN into an alias of UBUNTU, which can be helpful when you might have code that’s kernel-related together with code that’s particular to a given Linux distribution.

An vital piece of conduct to notice within the above instance is that whenever you iterate over the enumeration instantly, aliases aren’t thought of. For those who ever have to iterate over all of the members, together with aliases, then it’s worthwhile to use .__members__. You’ll be taught extra about iteration and the .__members__ attribute within the part about iterating by means of enumerations.

You even have the choice to utterly forbid aliases in your enumerations. To do that, you should use the @distinctive decorator from the enum module:

>>>

>>> from enum import Enum, distinctive

>>> @distinctive
... class OperatingSystem(Enum):
...     UBUNTU = "linux"
...     MACOS = "darwin"
...     WINDOWS = "win"
...     DEBIAN = "linux"
...
Traceback (most up-to-date name final):
    ...
ValueError: duplicate values in <enum 'OperatingSystem'>: DEBIAN -> UBUNTU

On this instance, you enhance OperatingSystem with @distinctive. If any member worth is duplicated, you then get a ValueError. Right here, the exception message factors out that DEBIAN and UBUNTU share the identical worth, which isn’t allowed.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments