Python:enum
열거형(enumeration)은 고유한 상숫값에 연결된 기호 이름(멤버)의 집합입니다. 열거형 내에서, 멤버를 아이덴티티로 비교할 수 있고, 열거형 자체는 이터레이트 될 수 있습니다.
파이썬은 버전 3.4부터 다른 언어들 처럼 enum(enumeration, 이넘) 타입을 지원하고 있습니다. enum은 일반적으로 서로 관련이 있는 여러 개의 상수의 집합을 정의할 때 사용되는데요. enum 클래스를 사용하면 인스턴스의 종류를 제한할 수 있기 때문에 견고한 프로그램을 작성하는데 도움이 됩니다.
멤버 이름 리스트
from enum import Enum
from typing import List
class Command(Enum):
unknown = 0
core = 1
task = 2
ctrl = 3
daemon = 4
def get_available_commands() -> List[str]:
return [n for n, _ in Command.__members__.items() if n != Command.unknown.name]
클래스 타입 정의
간단한 예로, 웹에서 사용되는 대표적인 3개의 기술(HTML, CSS, JS)을 나타내는 enum
클래스를 작성해보겠습니다. enum
내장 모듈로 부터 불러온 Enum
클래스를 확장하여 Skill
타입을 만들었습니다.
enum 타입의 상수 인스턴스는 기본적으로 이름(name
)과 값(value
)을 속성을 가집니다.
enum 타입은 순회가 가능하기 때문에 for
문으로 모든 상수를 쉽게 확인할 수 있습니다.
열거형 멤버와 그들의 어트리뷰트에 프로그래밍 방식으로 액세스하기
때로는 프로그래밍 방식으로 열거형의 멤버에 액세스하는 것이 유용합니다 (즉, 프로그램 작성 시간에 정확한 색상을 알 수 없어서 Color.RED
를 쓸 수 없는 상황). Enum는 그런 액세스를 허용합니다:
이름(name)으로 열거형 멤버에 액세스하려면, 항목 액세스를 사용하십시오:
열거형 멤버가 있고 name이나 value가 필요하면:
함수형 타입 정의
자주 쓰이는 방법은 아니지만 Enum
클래스를 확장하는 대신에 일반 함수처럼 호출을 해서 enum 타입을 정의할 수도 있습니다.
>>> Skill = Enum("Skill", "HTML CSS JS")
>>> list(Skill)
[<Skill.HTML: 1>, <Skill.CSS: 2>, <Skill.JS: 3>]
값 자동 할당
enum
을 사용할 때, 많은 경우 값(value
)이 무언인지는 크게 중요하지 않습니다. 이럴 때는 enum
모듈의 auto()
helper 함수를 사용하면, 첫번째 상수에 1, 두번째 상수에 2, 이렇게 1씩 증가시키면서 모든 상수에 유일한 숫자를 값으로 할당해줍니다.
auto()
함수를 사용하면 기존 상수에 어떤 숫자가 할당해놨었는지 구애 받지 않고 새로운 상수를 추가할 수 있는 장점도 있습니다.
뿐만 아니라, Enum
클래스의 _generate_next_value_()
메서드를 오버라이드(override)하면 숫자가 아닌 다른 값을 자동 할당할 수 있습니다. 예를 들어, 상수의 이름과 동일한 문자열을 상수의 값으로 자동 할당할 수 있습니다.
from enum import Enum, auto
class Skill(Enum):
def _generate_next_value_(name, start, count, last_values):
return name
HTML = auto()
CSS = auto()
JS = auto()
enum mixin
enum 타입을 사용할 때 한 가지 불편할 수 있는 점은 상수의 이름이나 값에 접근할 때 name
이나 value
속성을 사용해야 한다는 것입니다.
>>> Skill.HTML.name == 'HTML'
True
>>> Skill.HTML.value == 1
True
>>> Skill.HTML == 'HTML'
False
>>> type(Skill.HTML)
<enum 'Skill'>
왜냐하면 모든 상수는 결국 해당 enum 타입의 인스턴스이기 때문입니다. 하지만 enum 타입을 사용해서 코딩을 하다보면, 매번 name
이나 value
를 사용하는 것이 귀찮고 까먹기도 싶습니다.
이럴 때는 enum mixin(믹스인) 기법을 활용하여 str
을 확장하는 enum 클래스를 작성합니다.
class StrEnum(str, Enum):
def _generate_next_value_(name, start, count, last_values):
return name
def __repr__(self):
return self.name
def __str__(self):
return self.name
그 다음, 이 클래스를 확장하여 enum 클래스를 정의하면 됩니다.
이제 Skill
타입이 담고 있는 상수는 완벽하게 문자열로 취급기 때문에 좀 더 편하게 사용할 수 있습니다.
Enum 확장
Enum
클래스는 다른 일반 클래스처럼 다양하게 확장해서 활용할 수 있습니다.
from enum import Enum
class Skill(Enum):
HTML = ("HTML", "Hypertext Markup Language")
CSS = ("CSS", "Cascading Style Sheets")
JS = ("JS", "JavaScript")
def __init__(self, title, description):
self.title = title
self.description = description
@classmethod
def get_most_popular(cls):
return cls.JS
def lower_title(self):
return self.title.lower()
>>> Skill.HTML.value
('HTML', 'Hypertext Markup Language')
>>> Skill.HTML.title
'HTML'
>>> Skill.HTML.description
'Hypertext Markup Language'
>>> Skill.get_most_popular()
<Skill.JS: ('JS', 'JavaScript')>
>>> Skill.CSS.lower_title()
'css'
StrEnum
Python 3.11 버전 부터 사용 가능한 StrEnum 을 이전 버전에서도 사용하고 싶다면:
# -*- coding: utf-8 -*-
import sys
from enum import Enum
class _StrEnum(str, Enum):
def __new__(cls, *values):
if len(values) > 3:
raise TypeError("too many arguments for str(): %r" % (values,))
if len(values) == 1:
# it must be a string
if not isinstance(values[0], str):
raise TypeError("%r is not a string" % (values[0],))
if len(values) >= 2:
# check that encoding argument is a string
if not isinstance(values[1], str):
raise TypeError("encoding must be a string, not %r" % (values[1],))
if len(values) == 3:
# check that errors argument is a string
if not isinstance(values[2], str):
raise TypeError("errors must be a string, not %r" % (values[2]))
value = str(*values)
member = str.__new__(cls, value)
member._value_ = value
return member
# noinspection PyMethodParameters
def _generate_next_value_(name, start, count, last_values):
return name.lower()
if sys.version_info[:2] >= (3, 11):
from enum import StrEnum # noqa
else:
StrEnum = _StrEnum
Utilities
몇 가지 유틸리티 함수를 소개한다.
# -*- coding: utf-8 -*-
from enum import Enum
from typing import Dict, Type, TypeVar
_ASSERT_MSG_NOT_ENUM_FORMAT = "The `{cls}` type must inherit from Enum class."
_T = TypeVar("_T")
와 같을 때...
문자열을 열거형으로 변환
def string_to_enum(name: str, cls: Type[_T]) -> _T:
assert issubclass(cls, Enum), _ASSERT_MSG_NOT_ENUM_FORMAT.format(cls=cls.__name__)
enums = [s for s in dir(cls) if not s.startswith("_")]
if name in enums:
return getattr(cls, name)
raise KeyError(f"Not found `{name}` enum in {cls.__name__}")
문자열 키와 열거형 값으로 맵핑된 사전형 반환
def string_to_enum_map(cls: Type[_T]) -> Dict[str, _T]:
assert issubclass(cls, Enum), _ASSERT_MSG_NOT_ENUM_FORMAT.format(cls=cls.__name__)
enums = [s for s in dir(cls) if not s.startswith("_")]
return {e: getattr(cls, e) for e in enums}
문자열(소문자) 키와 열거형 값으로 맵핑된 사전형 반환
def lower_string_to_enum_map(cls: Type[_T]) -> Dict[str, _T]:
assert issubclass(cls, Enum), _ASSERT_MSG_NOT_ENUM_FORMAT.format(cls=cls.__name__)
enums = [s for s in dir(cls) if not s.startswith("_")]
return {e.lower(): getattr(cls, e) for e in enums}
See also
- Python
- enum