4.6. Enum Recap

4.6.1. SetUp

>>> from enum import Enum, IntEnum, StrEnum, Flag, IntFlag, auto

4.6.2. Use Case - 1

>>> class Color(Enum):
...     AQUA = '#00FFFF'
...     BLACK = '#000000'
...     BLUE = '#0000FF'
...     FUCHSIA = '#FF00FF'
...     GRAY = '#808080'
...     GREEN = '#008000'
...     LIME = '#00FF00'
...     MAROON = '#800000'
...     NAVY = '#000080'
...     OLIVE = '#808000'
...     PINK = '#FF1A8C'
...     PURPLE = '#800080'
...     RED = '#FF0000'
...     SILVER = '#C0C0C0'
...     TEAL = '#008080'
...     WHITE = '#FFFFFF'
...     YELLOW = '#FFFF00'

4.6.3. Use Case - 2

>>> class Languages(Enum):
...     EN = 'English'
...     PL = 'Polish'
...     DE = 'German'

4.6.4. Use Case - 3

>>> class Currency(Enum):
...     PLN = 1
...     USD = 4.00
...     EUR = 4.20

4.6.5. Use Case - 4

>>> class CountryCode(Enum):
...     PL = '+48'
...     DE = '+49'
...     US = '+1'

4.6.6. Use Case - 5

>>> class EmailType(Enum):
...     PRIV = 1
...     WORK = 2

4.6.7. Use Case - 6

>>> class PhoneType(Enum):
...     HOME = 1
...     WORK = 2
...     ASSISTANT = 3

4.6.8. Use Case - 7

>>> class AddressType(Enum):
...     BILLING = 1
...     SHIPPING = 2

4.6.9. Use Case - 8

>>> class Gender(Enum):
...     FEMALE = 1
...     MALE = 2
...     OTHER = 3

4.6.10. Use Case - 9

>>> class Settings(Enum):
...     PROJECT_NAME = ...
...     BASE_DIRECTORY = ...
...     STATIC_ROOT = ...

4.6.11. Use Case - 10

>>> class ScreenResolution(Enum):
...     X_MIN = 0
...     X_MAX = 1920
...     Y_MIN = 0
...     Y_MAX = 1080

4.6.12. Use Case - 11

>>> class Polish(Enum):
...     PLEASE = 'proszę'
...     THANKS = 'dziękuję'
...     SORRY = 'przepraszam'
>>> class English(Enum):
...     PLEASE = 'please'
...     THANKS = 'thank you'
...     SORRY = 'I am sorry'

4.6.13. Use Case - 12

>>> from dataclasses import dataclass
>>>
>>>
>>> class Role(StrEnum):
...     PRODUCT_OWNER = 'PO'
...     SCRUM_MASTER = 'SM'
...     TEAM_MEMBER = 'TM'
>>>
>>>
>>> @dataclass
... class User:
...     firstname: str
...     lastname: str
...     role: Role
>>>
>>>
>>> alice = User('Alice', 'Apricot', role='not-existing')
>>> alice = User('Alice', 'Apricot', role=Role.TEAM_MEMBER)

4.6.14. Use Case - 13

>>> class Permissions(Flag):
...     READ = 0b100
...     WRITE = 0b010
...     EXECUTE = 0b001

4.6.15. Use Case - 14

>>> class IssueStatus(Enum):
...     TODO = 'todo'
...     IN_PROGRESS = 'in-progress'
...     IN_REVIEW = 'in-review'
...     IN_TEST = 'in-test'
...     DONE = 'done'
...     REJECTED = 'rejected'

4.6.16. Use Case - 15

>>>
... from django.db import models
...
... class HttpMethod(models.TextChoices):
...     GET = 'GET', _('GET')
...     POST = 'POST', _('POST')
...     PATCH = 'PATCH', _('PATCH')
...     PUT = 'PUT', _('PUT')
...     HEAD = 'HEAD', _('HEAD')
...     DELETE = 'DELETE', _('DELETE')
...     OPTIONS = 'OPTIONS', _('OPTIONS')
...     TRACE = 'TRACE', _('TRACE')
...     CONNECT = 'CONNECT', _('CONNECT')
...
...
... class Stage(models.TextChoices):
...     PRODUCTION = 'production', _('Production')
...     TEST = 'test', _('Test')

4.6.17. Use Case - 16

>>> Point = tuple[int,int]
>>>
>>> class Color(Enum):
...     RED = '#FF0000'
...     GREEN = '#00FF00'
...     BLUE = '#0000FF'
>>> def draw_line(A: Point, B: Point, color: Color):
...     if type(color) is not Color:
...         possible = [str(c) for c in Color]
...         raise TypeError(f'Invalid color, possible choices: {possible}')
...     print(f'Drawing line from {A} to {B} with color {color.value}')
>>> draw_line(A=(0,0), B=(3,5), color=Color.RED)
Drawing line from (0, 0) to (3, 5) with color #FF0000
>>> draw_line(A=(0,0), B=(3,5), color='red')
Traceback (most recent call last):
TypeError: Invalid color, possible choices: ['Color.RED', 'Color.GREEN', 'Color.BLUE']

4.6.18. Use Case - 17

>>> from dataclasses import dataclass
>>>
>>>
>>> class Role(Enum):
...     USER = 1
...     STAFF = 2
...     ADMIN = 3
>>>
>>>
>>> @dataclass
... class Account:
...     firstname: str
...     lastname: str
...     role: Role
>>>
>>>
>>> alice = Account('Alice', 'Apricot', role='not-existing')
>>> alice = Account('Alice', 'Apricot', role=Role.USER)

4.6.19. Use Case - 18

  • r - read

  • w - write

  • x - execute

  • rwx - read, write, execute; 0b111 == 0o7

  • rw- - read, write; 0b110 == 0o6

  • r-x - read, execute; 0b101 == 0o5

  • r-- - read only; 0b100 == 0o4

  • rwxr-xr-- - user=(read,write,execute); group=(read,execute); others=(read)

  • https://docs.python.org/3/library/os.html#os.stat

>>> from enum import Enum
>>> from pathlib import Path
>>>
>>>
>>> class Permission(Enum):
...     READ_WRITE_EXECUTE = 0b111
...     READ_WRITE = 0b110
...     READ_EXECUTE = 0b101
...     READ = 0b100
...     WRITE_EXECUTE = 0b011
...     WRITE = 0b010
...     EXECUTE = 0b001
...     NONE = 0b000
>>>
>>>
>>> file = Path('/tmp/myfile.txt')
>>> file.touch()
>>> file.stat()
os.stat_result(st_mode=33188, st_ino=98480473, st_dev=16777220,
               st_nlink=1, st_uid=501, st_gid=20, st_size=0,
               st_atime=1624458230, st_mtime=1624458230,
               st_ctime=1624458230)
>>>
>>> permissions = file.stat().st_mode
>>> decimal = int(permissions)
>>> octal = oct(permissions)
>>> binary = bin(permissions)
>>> print(f'{decimal=}, {octal=}, {binary}')
decimal=33188, octal='0o100644', 0b1000000110100100
>>>
>>> *_, user, group, others = oct(permissions)
>>> print(f'{user=} {group=} {others=}')
user='6' group='4' others='4'
>>>
>>> Permission(int(user))
<Permission.READ_WRITE: 6>
>>>
>>> Permission(int(group))
<Permission.READ: 4>
>>>
>>> Permission(int(others))
<Permission.READ: 4>
>>>
>>> file.unlink()

4.6.20. Use Case - 19

>>>
... from django.db import models
...
... class HttpMethod(models.TextChoices):
...     GET = 'GET', _('GET')
...     POST = 'POST', _('POST')
...     PATCH = 'PATCH', _('PATCH')
...     PUT = 'PUT', _('PUT')
...     HEAD = 'HEAD', _('HEAD')
...     DELETE = 'DELETE', _('DELETE')
...     OPTIONS = 'OPTIONS', _('OPTIONS')
...     TRACE = 'TRACE', _('TRACE')
...     CONNECT = 'CONNECT', _('CONNECT')
...
...
... class Stage(models.TextChoices):
...     PRODUCTION = 'production', _('Production')
...     TEST = 'test', _('Test')

4.6.21. Use Case - 20

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     VENUS   = (4.869e+24, 6.0518e6)
...     EARTH   = (5.976e+24, 6.37814e6)
...     MARS    = (6.421e+23, 3.3972e6)
...     JUPITER = (1.9e+27,   7.1492e7)
...     SATURN  = (5.688e+26, 6.0268e7)
...     URANUS  = (8.686e+25, 2.5559e7)
...     NEPTUNE = (1.024e+26, 2.4746e7)
...
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>>
>>> Planet.EARTH.surface_gravity
9.802652743337129

4.6.22. Use Case - 21

>>> Point = tuple[int,int]
>>>
>>> class Color(Enum):
...     RED = '#FF0000'
...     GREEN = '#00FF00'
...     BLUE = '#0000FF'
>>> def draw_line(A: Point, B: Point, color: Color):
...     if type(color) is not Color:
...         possible = [str(c) for c in Color]
...         raise TypeError(f'Invalid color, possible choices: {possible}')
...     print(f'Drawing line from {A} to {B} with color {color.value}')
>>> draw_line(A=(0,0), B=(3,5), color=Color.RED)
Drawing line from (0, 0) to (3, 5) with color #FF0000
>>> draw_line(A=(0,0), B=(3,5), color='red')
Traceback (most recent call last):
TypeError: Invalid color, possible choices: ['Color.RED', 'Color.GREEN', 'Color.BLUE']

4.6.23. Use Case - 22

  • Status is "Full Health", when character has 100% health points

  • Status is "Injured", when character has 75%-99% health points

  • Status is "Badly Wounded", when character has 25-74% health points

  • Status is "Near Death", when character has 1-24% health points

  • Status is "Dead", when character has 0% health points

>>> from enum import Enum
>>>
>>>
>>> class Status(Enum):
...     FULL_HEALTH = 100
...     INJURED = range(75, 99)
...     BADLY_WOUNDED = range(25, 74)
...     NEAR_DEATH = range(1, 24)
...     DEAD = 0
>>> hit_points = 100
>>> Status(hit_points)
<Status.FULL_HEALTH: 100>
>>> hit_points = 0
>>> Status(hit_points)
<Status.DEAD: 0>

4.6.24. Use Case - 23

>>> class IndexDrives(IntEnum):
...     ControlWord = 0x6040
...     StatusWord = 0x6041
...     OperationMode = 0x6060

4.6.25. Use Case - 24

>>> from dataclasses import dataclass
>>>
>>>
>>> class Agency(Enum):
...     NASA = auto()
...     ESA = auto()
...     CSA = auto()
>>>
>>>
>>> @dataclass
... class Astronaut:
...     firstname: str
...     lastname: str
...     agency: Agency
>>>
>>>
>>> alice = Astronaut('Alice', 'Apricot', agency='not-existing')
>>> alice = Astronaut('Alice', 'Apricot', agency=Agency.NASA)

4.6.26. Use Case - 25

>>>
... from django.db import models
...
... class HttpMethod(models.TextChoices):
...     GET = 'GET', _('GET')
...     POST = 'POST', _('POST')
...     PATCH = 'PATCH', _('PATCH')
...     PUT = 'PUT', _('PUT')
...     HEAD = 'HEAD', _('HEAD')
...     DELETE = 'DELETE', _('DELETE')
...     OPTIONS = 'OPTIONS', _('OPTIONS')
...     TRACE = 'TRACE', _('TRACE')
...     CONNECT = 'CONNECT', _('CONNECT')
...
...
... class Stage(models.TextChoices):
...     PRODUCTION = 'production', _('Production')
...     TEST = 'test', _('Test')

4.6.27. Use Case - 26

>>> from enum import IntEnum
>>>
>>> class Permission(IntFlag):
...     READ = 0b100
...     WRITE = 0b010
...     EXECUTE = 0b001
>>> file = Path('/tmp/myfile.txt')
>>> file.touch()
>>>
>>> file.stat()
os.stat_result(st_mode=33188, st_ino=22078540, st_dev=16777231, st_nlink=1, st_uid=501, st_gid=0, st_size=138, st_atime=1718195813, st_mtime=1718195769, st_ctime=1718195769)
>>>
>>> permissions = file.stat().st_mode
>>>
>>> permissions
33188
>>> oct(permissions)
'0o100644'
>>>
>>> *_, user, group, others = oct(permissions)
>>>
>>> Permission(int(user))
<Permission.READ|WRITE: 6>
>>>
>>> Permission(int(group))
<Permission.READ: 4>
>>>
>>> Permission(int(others))
<Permission.READ: 4>

4.6.28. Use Case - 27

  • Pattern Matching

  • Since Python 3.10: PEP 636 -- Structural Pattern Matching: Tutorial

../../_images/enum-usecase-keycodes.png

Note, keycodes can vary depending on operating system and programming language used [mskeycodes], [jskeycodes].

>>> int('0x1B', base=16)
27
>>>
>>> hex(27)
'0x1b'
>>> class Key(Enum):
...     ESC = 27            # 0x1B
...     ARROW_LEFT = 37     # 0x25
...     ARROW_UP = 38       # 0x26
...     ARROW_RIGHT = 39    # 0x27
...     ARROW_DOWN = 40     # 0x28
>>>
>>>
>>>
... match keyboard.on_key_press():
...     case Key.ESC:          game.quit()
...     case Key.ARROW_LEFT:   game.move_left()
...     case Key.ARROW_UP:     game.move_up()
...     case Key.ARROW_RIGHT:  game.move_right()
...     case Key.ARROW_DOWN:   game.move_down()
...     case _: raise ValueError(f'Unrecognized key')

4.6.29. Use Case - 28

../../_images/enum-usecase-biedronka.png
>>> class PTU(Enum):
...     A = 1.23   # VAT 23%
...     B = 1.08   # VAT 8%
...     C = 1.05   # VAT 5%
...     D = 1.00   # VAT 0%
...     E = None   # VAT Exempt
>>>
>>> PLN = 1
>>> shopping_cart = [
...     {'name': 'Bread',   'price': 3.99*PLN, 'ptu': PTU.C},
...     {'name': 'Butter',  'price': 2.69*PLN, 'ptu': PTU.B},
...     {'name': 'Ham',     'price': 5.99*PLN, 'ptu': PTU.A},
...     {'name': 'Cheese',  'price': 4.19*PLN, 'ptu': PTU.B},
... ]
>>> total = sum(product['price'] * product['ptu'].value
...             for product in shopping_cart)
>>> print(f'Total is: {total:.2f} PLN')
Total is: 18.99 PLN

4.6.30. References

4.6.31. Assignments

# %% About
# - Name: Enum Recap Currency
# - Difficulty: easy
# - Lines: 4
# - Minutes: 2

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% English
# 1. Define enum `Currency`:
# - name: PLN, value: 1.00
# - name: EUR, value: 4.28
# - name: USD, value: 3.70
# 2. Use `Enum`
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj enum `Currency`:
# - nazwa: PLN, wartość: 1.00
# - nazwa: EUR, wartość: 4.28
# - nazwa: USD, wartość: 3.70
# 2. Użyj `Enum`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> assert Enum in Currency.mro(), \
'Currency must be an Enum'

>>> assert len(Currency) == 3, \
'Currency must have 3 elements'

>>> assert hasattr(Currency, 'PLN')
>>> assert hasattr(Currency, 'EUR')
>>> assert hasattr(Currency, 'USD')

>>> assert Currency.PLN.value == 1.00
>>> assert Currency.EUR.value == 4.28
>>> assert Currency.USD.value == 3.70
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`

# %% Imports
from enum import Enum

# %% Types
Currency: type[Enum]

# %% Data

# %% Result

# %% About
# - Name: Enum Recap Language
# - Difficulty: easy
# - Lines: 4
# - Minutes: 2

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% English
# 1. Define enum `Language`:
# - name: PL, value: 'polish'
# - name: EN, value: 'english'
# - name: DE, value: 'german'
# 2. Use `Enum`
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj enum `Language`:
# - nazwa: PL, wartość: 'polish'
# - nazwa: EN, wartość: 'english'
# - nazwa: DE, wartość: 'german'
# 2. Użyj `Enum`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> assert Enum in Language.mro(), \
'Language must be an Enum'

>>> assert len(Language) == 3, \
'Language must have 3 elements'

>>> assert hasattr(Language, 'PL')
>>> assert hasattr(Language, 'EN')
>>> assert hasattr(Language, 'DE')

>>> assert Language.PL.value == 'polish'

>>> assert Language.EN.value == 'english'

>>> assert Language.DE.value == 'german'
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`

# %% Imports
from enum import Enum

# %% Types
Language: type[Enum]

# %% Data

# %% Result

# %% About
# - Name: Enum Recap HttpStatus
# - Difficulty: easy
# - Lines: 4
# - Minutes: 2

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% English
# 1. Define enum `HttpStatus`:
# - name: OK, value: 200
# - name: NOT_FOUND, value: 404
# - name: BAD_REQUEST, value: 500
# 2. Use `Enum`
# 3. Run doctests - all must succeed

# %% Polish
# 1. Zdefiniuj enum `HttpStatus`:
# - nazwa: OK, wartość: 200
# - nazwa: NOT_FOUND, wartość: 404
# - nazwa: BAD_REQUEST, wartość: 500
# 2. Użyj `Enum`
# 3. Uruchom doctesty - wszystkie muszą się powieść

# %% Doctests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> assert Enum in HttpStatus.mro(), \
'HttpStatus must be an Enum'

>>> assert len(HttpStatus) == 3, \
'HttpStatus must have 3 elements'

>>> assert hasattr(HttpStatus, 'OK')
>>> assert hasattr(HttpStatus, 'NOT_FOUND')
>>> assert hasattr(HttpStatus, 'BAD_REQUEST')

>>> assert HttpStatus.OK.value == 200
>>> assert HttpStatus.NOT_FOUND.value == 404
>>> assert HttpStatus.BAD_REQUEST.value == 500
"""

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -f -v myfile.py`

# %% Imports
from enum import Enum

# %% Types
HttpStatus: type[Enum]

# %% Data

# %% Result