Enum HOWTO¶
한 Enum 은 고유한 값에 바인딩된 기호 이름의 집합입니다. 이들은 전역 변수와 유사하지만, 더 유용한 repr(), 그룹화, 타입 안정성 및 몇 가지 다른 기능을 제공합니다.
이들은 제한된 선택 사항 중 하나를 가질 수 있는 변수가 있을 때 가장 유용합니다. 예를 들어, 요일의 경우 다음과 같습니다:
>>> from enum import Enum
>>> class Weekday(Enum):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 3
... THURSDAY = 4
... FRIDAY = 5
... SATURDAY = 6
... SUNDAY = 7
아니면 RGB 기본색이 있을 수도 있습니다:
>>> from enum import Enum
>>> class Color(Enum):
... RED = 1
... GREEN = 2
... BLUE = 3
보시는 바와 같이, Enum 을 생성하는 것은 Enum 자체를 상속받는 클래스를 작성하는 것만큼 간단합니다.
참고
Enum 멤버의 사례
Enum은 상수를 나타내는 데 사용되므로, mixin-class 메서드/어트리뷰트와 enum 이름 사이의 이름 충돌 문제를 방지하기 위해 멤버에 대문자(UPPER_CASE) 이름을 사용할 것을 강력히 권장하며, 예제에서도 이 스타일을 사용합니다.
enum의 성격에 따라 멤버의 값이 중요할 수도 있고 그렇지 않을 수도 있지만, 어느 경우든 해당 값을 사용하여 해당하는 멤버를 가져올 수 있습니다:
>>> Weekday(3)
<Weekday.WEDNESDAY: 3>
보시는 바와 같이, 멤버의 repr() 는 enum 이름, 멤버 이름, 그리고 값을 보여줍니다. 멤버의 str() 는 enum 이름과 멤버 이름만 표시합니다:
>>> print(Weekday.THURSDAY)
Weekday.THURSDAY
enum 멤버의 형(type) 은 해당 멤버가 속한 enum입니다:
>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True
Enum 멤버는 그들의 name 만 포함하는 어트리뷰트를 가집니다:
>>> print(Weekday.TUESDAY.name)
TUESDAY
마찬가지로, 그들은 value 를 위한 어트리뷰트를 가집니다:
>>> Weekday.WEDNESDAY.value
3
enum을 단순히 이름/값 쌍으로만 취급하는 많은 언어들과 달리, Python Enum은 동작을 추가할 수 있습니다. 예를 들어, datetime.date 는 요일을 반환하는 두 가지 메서드인 weekday() 와 isoweekday() 를 가지고 있습니다. 차이점은 하나는 0-6으로 세고 다른 하나는 1-7로 센다는 것입니다. 이를 직접 추적하는 대신, 우리는 Weekday enum에 메서드를 추가하여 date 인스턴스로부터 요일을 추출하고 일치하는 enum 멤버를 반환하게 할 수 있습니다:
@classmethod
def from_date(cls, date):
return cls(date.isoweekday())
완성된 Weekday enum은 이제 다음과 같습니다:
>>> class Weekday(Enum):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 3
... THURSDAY = 4
... FRIDAY = 5
... SATURDAY = 6
... SUNDAY = 7
... #
... @classmethod
... def from_date(cls, date):
... return cls(date.isoweekday())
이제 오늘이 무슨 요일인지 알아낼 수 있습니다! 확인해 보세요:
>>> import datetime as dt
>>> Weekday.from_date(dt.date.today())
<Weekday.TUESDAY: 2>
물론, 이 글을 다른 날에 읽고 있다면 그날의 결과가 표시될 것입니다.
이 Weekday enum은 변수가 하나의 요일만 필요할 때 아주 유용하지만, 여러 개가 필요한 경우는 어떠한가요? 예를 들어 한 주 동안의 집안일을 기록하는 함수를 작성 중인데 list 를 사용하고 싶지 않다면, 다른 유형의 Enum 을 사용할 수 있습니다:
>>> from enum import Flag
>>> class Weekday(Flag):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 4
... THURSDAY = 8
... FRIDAY = 16
... SATURDAY = 32
... SUNDAY = 64
두 가지를 변경했습니다. 바로 Flag 를 상속받았으며, 모든 값은 2의 거듭제곱입니다.
위의 원래 Weekday enum과 마찬가지로, 단일 선택을 할 수 있습니다:
>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>
하지만 Flag 는 여러 멤버를 하나의 변수로 결합할 수도 있게 해줍니다:
>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>
심지어 Flag 변수를 반복(iterate)할 수도 있습니다:
>>> for day in weekend:
... print(day)
Weekday.SATURDAY
Weekday.SUNDAY
좋습니다, 집안일 목록을 설정해 봅시다:
>>> chores_for_ethan = {
... 'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
... 'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
... 'answer SO questions': Weekday.SATURDAY,
... }
그리고 주어진 날짜의 집안일을 표시하는 함수를 만듭니다:
>>> def show_chores(chores, day):
... for chore, days in chores.items():
... if day in days:
... print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions
멤버의 실제 값이 중요하지 않은 경우, 작업량을 줄이기 위해 값에 auto() 를 사용할 수 있습니다:
>>> from enum import auto
>>> class Weekday(Flag):
... MONDAY = auto()
... TUESDAY = auto()
... WEDNESDAY = auto()
... THURSDAY = auto()
... FRIDAY = auto()
... SATURDAY = auto()
... SUNDAY = auto()
... WEEKEND = SATURDAY | SUNDAY
enum 멤버와 그들의 어트리뷰트에 대한 프로그래밍 방식의 접근¶
때로는 프로그래밍 방식으로 enum 멤버에 접근하는 것이 유용합니다(예를 들어, 프로그램 작성 시점에 정확한 색상을 알 수 없어 Color.RED 를 사용할 수 없는 경우). Enum 은 그러한 접근을 허용합니다:
>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>
enum 멤버에 이름 으로 접근하려면 아이템 접근(item access)을 사용하십시오:
>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>
enum 멤버를 가지고 있으며 그 멤버의 name`이나 :attr:!value`가 필요한 경우:
>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1
enum 멤버와 값 중복하기¶
동일한 이름을 가진 두 개의 enum 멤버를 갖는 것은 유효하지 않습니다:
>>> class Shape(Enum):
... SQUARE = 2
... SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2
그러나, 하나의 enum 멤버는 다른 이름들과 연결될 수 있습니다. 동일한 값을 가진 두 항목 A 와 B 가 주어지고(A 가 먼저 정의됨), B 는 멤버 A 의 별칭이 됩니다. A 의 값에 의한 조회는 멤버 A 를 반환합니다. A 의 이름에 의한 조회도 멤버 A 를 반환합니다. B 의 이름에 의한 조회 또한 멤버 A 를 반환합니다:
>>> class Shape(Enum):
... SQUARE = 2
... DIAMOND = 1
... CIRCLE = 3
... ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>
참고
이미 정의된 어트리뷰트(다른 멤버, 메서드 등)와 동일한 이름을 가진 멤버를 생성하려고 하거나, 멤버와 동일한 이름을 가진 어트리뷰트를 생성하려고 하는 것은 허용되지 않습니다.
고유한 열거형 값 보장¶
기본적으로, enum은 동일한 값에 대해 여러 개의 이름을 별칭으로 허용합니다. 이 동작을 원하지 않을 때는 @unique 데코레이터를 사용할 수 있습니다:
>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
... ONE = 1
... TWO = 2
... THREE = 3
... FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE
자동 값 사용하기¶
정확한 값이 중요하지 않다면 auto 를 사용할 수 있습니다:
>>> from enum import Enum, auto
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]
값은 _generate_next_value_() 에 의해 선택되며, 이를 재정의할 수 있습니다:
>>> class AutoName(Enum):
... @staticmethod
... def _generate_next_value_(name, start, count, last_values):
... return name
...
>>> class Ordinal(AutoName):
... NORTH = auto()
... SOUTH = auto()
... EAST = auto()
... WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']
참고
_generate_next_value_() 메서드는 모든 멤버보다 먼저 정의되어야 합니다.
반복(Iteration)¶
enum의 멤버를 반복할 때 별칭은 제공되지 않습니다:
>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]
별칭 Shape.ALIAS_FOR_SQUARE 와 Weekday.WEEKEND 가 표시되지 않음에 유의하십시오.
특별한 어트리뷰트 __members__ 는 이름에서 멤버로 가는 읽기 전용 순서 있는 매핑입니다. 이는 별칭을 포함하여 enum에 정의된 모든 이름을 포함합니다:
>>> 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>)
__members__ 어트리뷰트는 enum 멤버에 대한 상세한 프로그래밍 방식의 접근에 사용될 수 있습니다. 예를 들어, 모든 별칭을 찾는 경우:
>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']
참고
플래그(flag)의 별칭에는 3 과 같이 여러 플래그가 설정된 값이나 0 과 같이 아무것도 설정되지 않은 값이 포함될 수 있습니다.
비교¶
enum 멤버들은 아이덴티티(identity)에 의해 비교됩니다:
>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True
enum 값 간의 순서 기반 비교는 지원되지 않습니다. Enum 멤버는 정수가 아닙니다 (아래의 IntEnum 참고):
>>> Color.RED < Color.BLUE
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'
동등성 비교(Equality)는 정의되어 있습니다:
>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True
enum이 아닌 값과 비교할 때는 항상 False 를 반환합니다 (다시 강조하지만, IntEnum 은 다르게 동작하도록 명확하게 설계되었습니다. 아래 참조):
>>> Color.BLUE == 2
False
경고
모듈을 다시 로드하는 것이 가능하며 – 재로드된 모듈에 enum이 포함되어 있다면 이들은 다시 생성되며, 새 멤버는 원본 멤버와 동일하거나 같지 않을 수 있습니다.
enum의 허용되는 멤버와 어트리뷰트¶
위의 대부분 예제는 enum 값으로 정수를 사용합니다. 정수를 사용하는 것은 짧고 편리하지만(또한 Functional API 에 의해 기본으로 제공되지만), 엄격하게 강제되지는 않습니다. 대다수의 경우, 우리는 enum의 실제 값이 무엇인지 신경 쓰지 않습니다. 하지만 그 값이 중요하다면, enum은 임의의 값을 가질 수 있습니다.
enum은 Python 클래스이며, 평소와 같이 메서드 및 특수 메서드를 가질 수 있습니다. 다음과 같은 enum이 있다고 가정해 봅시다:
>>> class Mood(Enum):
... FUNKY = 1
... HAPPY = 3
...
... def describe(self):
... # 여기서 self는 멤버입니다
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # 여기서 cls는 enum입니다
... return cls.HAPPY
...
그다음:
>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'
허용되는 규칙은 다음과 같습니다: 단일 밑줄로 시작하고 끝나는 이름은 enum에 의해 예약되어 있어 사용할 수 없습니다. 이를 제외하고 enum 내에 정의된 모든 다른 어트리뷰트는 이 enum의 멤버가 되며, 특수 메서드(__str__(), __add__() 등), 디스크립터(메서드도 디스크립터입니다), 그리고 _ignore_ 에 나열된 변수 이름이 제외됩니다.
참고: 여러분의 enum이 __new__() 및/또는 __init__() 를 정의하는 경우, enum 멤버에 전달되는 모든 값이 해당 메서드들로 전달됩니다. 예시는 Planet 을 참조하십시오.
참고
정의된 경우 __new__() 메서드는 Enum 멤버 생성 중에 사용되며; 그 후 클래스 생성 이후 기존 멤버를 조회하기 위해 사용되는 Enum의 __new__() 로 대체됩니다. 자세한 내용은 __init__() 중 언제 사용하는지 을 참조하십시오.
제한된 Enum 서브 클래싱¶
새로운 Enum 클래스는 하나의 베이스 enum 클래스, 최대 하나의 구체적인 데이터 타입, 그리고 필요한 만큼의 object 기반 mixin 클래스를 가져야 합니다. 이 베이스 클래스들의 순서는 다음과 같습니다:
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
또한, enum이 아무런 멤버도 정의하지 않은 경우에만 enum을 서브 클래싱하는 것이 허용됩니다. 따라서 다음은 금지됩니다:
>>> class MoreColor(Color):
... PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>
하지만 이것은 허용됩니다:
>>> class Foo(Enum):
... def some_behavior(self):
... pass
...
>>> class Bar(Foo):
... HAPPY = 1
... SAD = 2
...
멤버를 정의하는 enum의 서브 클래싱을 허용하면 타입과 인스턴스의 몇 가지 중요한 불변성을 위반하게 됩니다. 반면에, 여러 enum 간에 공통된 동작을 공유하는 것을 허용하는 것은 타당합니다. (예시로 OrderedEnum 를 참조하십시오.)
Dataclass 지원¶
어느 dataclass 로부터 상속받을 때, __repr__() 는 상속된 클래스의 이름을 생략합니다. 예를 들어:
>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
... size: str
... legs: int
... tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
... BEETLE = 'small', 6
... DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>
표준 repr() 를 사용하려면 dataclass() 인자에 repr=False 를 사용하십시오.
버전 3.12에서 변경: 값 영역에는 dataclass의 필드만 표시되며, dataclass의 이름은 표시되지 않습니다.
참고
Enum 및 그 하위 클래스에 @~dataclasses.dataclass 데코레이터를 추가하는 것은 지원되지 않습니다. 오류가 발생하지는 않지만, 런타임에서 멤버들이 서로 동일하게 처리되는 등 매우 이상한 결과가 나타날 수 있습니다:
>>> @dataclass # 하지 마세요: 아무런 의미가 없습니다
... class Color(Enum):
... RED = 1
... BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE # 문제가 발생한 지점: 이들은 서로 같아서는 안 됩니다
True
피클링¶
열거형은 피클(pickle) 및 역피클(unpickle)이 가능합니다:
>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True
피클링에 대한 일반적인 제한 사항이 적용됩니다. 역피클을 하려면 해당 모듈에서 임포트할 수 있어야 하므로, 피클 가능한 열거형은 모듈의 최상위 수준에서 정의해야 합니다.
참고
피클 프로토콜 버전 4를 사용하면 다른 클래스 내에 중첩된 열거형을 쉽게 피클할 수 있습니다.
열거형 클래스에 __reduce_ex__() 를 정의하여 열거형 멤버가 피클/역피클되는 방식을 수정할 수 있습니다. 기본 방식은 값(by-value) 기반이지만, 복잡한 값을 가진 열거형은 이름(by-name) 기반을 사용하고 싶을 수 있습니다:
>>> import enum
>>> class MyEnum(enum.Enum):
... __reduce_ex__ = enum.pickle_by_enum_name
참고
이름 없는 별칭이 역피클되지 않으므로 플래그에 대해 이름(by-name) 방식을 사용하는 것은 권장되지 않습니다.
기능형 API¶
Enum 클래스는 호출 가능하며, 다음과 같은 함수형 API를 제공합니다:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]
이 API의 의미론은 namedtuple 과 유사합니다. Enum 호출의 첫 번째 인자는 열거형의 이름입니다.
두 번째 인자는 열거형 멤버 이름의 소스 입니다. 공백으로 구분된 이름 문자열, 이름 시퀀스, 키/값 쌍이 포함된 2-튜플 시퀀스, 또는 이름과 값을 매핑하는 매핑(예: 딕셔너리)일 수 있습니다. 마지막 두 옵션은 열거형에 임의의 값을 할당할 수 있게 하며, 다른 옵션들은 1부터 시작하여 증가하는 정수를 자동 할당합니다(다른 시작 값을 지정하려면 start 매개변수를 사용하십시오). Enum 에서 파생된 새 클래스가 반환됩니다. 즉, 위에서 설명한 Animal 에 대한 할당은 다음과 동일하게 처리됩니다:
>>> class Animal(Enum):
... ANT = 1
... BEE = 2
... CAT = 3
... DOG = 4
...
시작 번호를 0 이 아닌 1 로 기본 설정하는 이유는 0 이 불리언 의미에서 False 이지만, 기본적으로 열거형 멤버는 모두 True 로 평가되기 때문입니다.
함수형 API로 생성된 열거형을 피클하는 것은 까다로울 수 있는데, 이는 프레임 스택 구현 세부 사항을 사용하여 열거형이 생성되는 모듈을 파악하려고 시도하기 때문입니다(예: 별도의 모듈에 있는 유틸리티 함수를 사용하면 실패할 수 있으며, IronPython이나 Jython에서 작동하지 않을 수 있습니다). 해결책은 다음과 같이 모듈 이름을 명시적으로 지정하는 것입니다:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
경고
module 이 제공되지 않고 Enum이 이를 파악할 수 없는 경우, 새 Enum 멤버는 역피클 가능하지 않습니다. 오류를 소스에 가깝게 유지하기 위해 피클링은 비활성화됩니다.
새로운 피클 프로토콜 4는 일부 상황에서 피클이 클래스를 찾을 수 있는 위치로 __qualname__ 이 설정되어 있음에 의존합니다. 예를 들어, 클래스가 전역 범위의 SomeData 클래스 내에서 사용 가능한 경우:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')
전체 시그니처는 다음과 같습니다:
Enum(
value='NewEnumName',
names=<...>,
*,
module='...',
qualname='...',
type=<mixed-in class>,
start=1,
)
value: 새 Enum 클래스가 이름으로 기록할 내용.
names: Enum 멤버. 이는 공백이나 쉼표로 구분된 문자열일 수 있으며, 별도로 지정하지 않는 한 값이 1부터 시작합니다:
'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
또는 이름의 이터레이터:
['RED', 'GREEN', 'BLUE']
또는 (이름, 값) 쌍의 이터레이터:
[('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
또는 매핑:
{'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
module: 새 Enum 클래스를 찾을 수 있는 모듈의 이름.
qualname: 모듈 내에서 새로운 Enum 클래스를 찾을 수 있는 위치.
type: 새 Enum 클래스에 믹스인(mix-in)할 형.
start: 이름만 전달될 때 숫자를 세기 시작할 지점.
버전 3.5에서 변경: start 매개변수가 추가되었습니다.
파생된 열거형¶
IntEnum¶
제공되는 첫 번째 Enum 변형은 int 의 하위 클래스이기도 합니다. IntEnum 의 멤버는 정수와 비교할 수 있으며, 확장하여 서로 다른 유형의 정수 열거형끼리도 비교할 수 있습니다:
>>> from enum import IntEnum
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Request(IntEnum):
... POST = 1
... GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True
그러나 이들은 여전히 표준 Enum 열거형과 비교될 수 없습니다:
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Color(Enum):
... RED = 1
... GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False
IntEnum 값은 사용자가 예상하는 다른 방식에서도 정수처럼 동작합니다:
>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]
StrEnum¶
제공되는 두 번째 Enum 변형은 str 의 하위 클래스이기도 합니다. StrEnum 의 멤버는 문자열과 비교할 수 있으며, 확장하여 서로 다른 유형의 문자열 열거형끼리도 비교할 수 있습니다.
Added in version 3.11.
IntFlag¶
제공되는 다음 Enum 변형인 IntFlag 도 int 를 기반으로 합니다. 차이점은 IntFlag 멤버가 비트 연산자(&, |, ^, ~)를 사용하여 결합될 수 있으며, 가능한 경우 결과도 여전히 IntFlag 멤버라는 점입니다. IntEnum 과 마찬가지로, IntFlag 멤버 또한 정수이며 int 가 사용되는 모든 곳에 사용할 수 있습니다.
참고
비트 연산 이외의 작업을 IntFlag 멤버에 수행하면 IntFlag 소속을 잃게 됩니다.
유효하지 않은 IntFlag 값을 생성하는 비트 연산은 IntFlag 소속을 잃게 만듭니다. 자세한 내용은 FlagBoundary 를 참조하십시오.
Added in version 3.6.
버전 3.11에서 변경.
IntFlag 클래스 예제:
>>> from enum import IntFlag
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True
조합에 이름을 붙이는 것도 가능합니다:
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
... RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>
참고
이름이 있는 조합은 별칭으로 간주됩니다. 별칭은 반복 시에는 나타나지 않지만, 값 기반 조회에서는 반환될 수 있습니다.
버전 3.11에서 변경.
IntFlag 와 Enum 의 또 다른 중요한 차이점은 어떤 플래그도 설정되지 않았을 때(값이 0인 경우) 불리언 평가 결과가 False 라는 점입니다:
>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False
IntFlag 멤버도 int 의 하위 클래스이므로 정수와 결합할 수 있습니다(그러나 IntFlag 소속을 잃을 수 있습니다:
>>> Perm.X | 4
<Perm.R|X: 5>
>>> Perm.X + 8
9
IntFlag 멤버도 순회할 수 있습니다:
>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]
Added in version 3.11.
플래그¶
마지막 변형은 Flag 입니다. IntFlag 와 마찬가지로, Flag 멤버는 비트 연산자(&, |, ^, ~)를 사용하여 결합할 수 있습니다. IntFlag 와 달리, 다른 Flag 열거형이나 int 와 결합하거나 비교할 수 없습니다. 값을 직접 지정하는 것도 가능하지만, 값으로 auto 를 사용하고 Flag 가 적절한 값을 선택하도록 하는 것이 권장됩니다.
Added in version 3.6.
IntFlag 와 마찬가지로, Flag 멤버의 조합이 어떤 플래그도 설정하지 않은 결과(값이 0)를 내면 불리언 평가가 False 입니다:
>>> from enum import Flag, auto
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False
개별 플래그는 2의 거듭제곱(1, 2, 4, 8, …) 값을 가져야 하며, 플래그의 조합은 그러하지 않습니다:
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
... WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>
“플래그가 설정되지 않음” 상태에 이름을 부여해도 불리언 값이 변하지는 않습니다:
>>> class Color(Flag):
... BLACK = 0
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False
Flag 멤버도 순회할 수 있습니다:
>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]
Added in version 3.11.
기타¶
IntEnum 은 enum 모듈의 일부이지만, 독자적으로 구현하는 것도 매우 간단합니다:
class IntEnum(int, ReprEnum): # 또는 ReprEnum 대신 Enum 사용
pass
이는 유사한 파생 열거형을 정의하는 방법을 보여줍니다. 예를 들어, int 대신 float`를 믹스인(mix-in)하는 :class:!FloatEnum`이 있습니다.
일부 규칙:
Enum을 서브 클래싱할 때, 위IntEnum예제와 같이 베이스 시퀀스에서 믹스인 유형이Enum클래스보다 앞에 위치해야 합니다.믹스인 유형은 서브 클래싱이 가능해야 합니다. 예를 들어,
bool과range는 서브 클래싱이 불가능하므로 믹스인 유형으로 사용할 경우 열거형 생성 시 오류가 발생합니다.Enum은 어떤 형의 멤버도 가질 수 있지만, 다른 형을 믹스인하면 모든 멤버는 해당 형의 값을 가져야 합니다(예: 위에서 언급한int). 이 제한은 메서드만 추가하고 다른 형을 명시하지 않는 믹스인에는 적용되지 않습니다.다른 데이터 형이 믹스인될 때,
value속성은 열거형 멤버 자체와 동일하지 않지만, 등가이며 비교 시 동일하게 처리됩니다.%-스타일 포매팅:
%s와%r은 각각Enum클래스의__str__()및__repr__()을 호출하며, 다른 코드(IntEnum의 경우%i또는%h등)는 열거형 멤버를 혼합된 타입으로 처리합니다.포맷 문자열 리터럴,
str.format(), 그리고format()은 열거형의__str__()메서드를 사용합니다.
__init__() 중 언제 사용하는지¶
Enum 멤버의 실제 값을 사용자 정의하려면 반드시 __new__() 를 사용해야 합니다. 그 외의 수정 사항은 __new__() 또는 __init__() 에 포함할 수 있으며, __init__() 을 사용하는 것이 권장됩니다.
예를 들어, 생성자에 여러 항목을 전달하지만 그중 하나만 값으로 사용하고 싶은 경우:
>>> class Coordinate(bytes, Enum):
... """
... 정수 코드로 인덱싱할 수 있는 바이너리 코드를 가진 Coordinate.
... """
... def __new__(cls, value, label, unit):
... obj = bytes.__new__(cls, [value])
... obj._value_ = value
...
... obj.label = label
... obj.unit = unit
... return obj
... PX = (0, 'P.X', 'km')
... PY = (1, 'P.Y', 'km')
... VX = (2, 'V.X', 'km/s')
... VY = (3, 'V.Y', 'km/s')
...
>>> print(Coordinate['PY'])
Coordinate.PY
>>> print(Coordinate(3))
Coordinate.VY
경고
조회 전용 __new__ 가 발견되므로 super().__new__() 를 호출하지 말고 대신 데이터 타입을 직접 사용하십시오.
세부 사항¶
지원되는 __dunder__ 및 _sunder__ 이름¶
지원되는 __dunder__ 및 _sunder__ 이름은 Enum API 문서 에서 확인할 수 있습니다.
_Private__이름¶
개인 이름 은 열거형 멤버로 변환되지 않고 일반 속성으로 유지됩니다.
버전 3.11에서 변경.
Enum 멤버 타입¶
Enum 멤버는 해당 Enum 클래스의 인스턴스이며, 일반적으로 EnumClass.member 로 접근합니다. 커스텀 열거형 동작을 작성하는 등의 특정 상황에서는 한 멤버에서 다른 멤버로 직접 액세스할 수 있는 기능이 유용하며 지원됩니다. 하지만 멤버 이름과 혼합된 클래스의 속성/메서드 간의 이름 충돌을 방지하기 위해 대문자 이름을 사용하는 것을 강력히 권장합니다.
버전 3.5에서 변경.
다른 데이터 타입과 혼합된 멤버 생성¶
int 나 str 와 같은 다른 데이터 타입을 기반으로 하여 Enum 을 서브 클래스로 구현할 때, = 뒤의 모든 값이 해당 데이터 타입의 생성자로 전달됩니다. 예를 들어:
>>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> 정수
... example = '11', 16 # 따라서 x='11'이고 base=16
...
>>> MyEnum.example.value # 그리고 hex(11)은...
17
Enum 클래스 및 멤버의 불리언 값¶
비 Enum 타입(가령 int, str 등)과 혼합된 Enum 클래스는 해당 혼합형 타입의 규칙에 따라 평가되며, 그렇지 않은 경우 모든 멤버는 True 로 평가됩니다. 사용자 정의 Enum의 불리언 평가를 멤버의 값에 따르게 하려면 클래스에 다음을 추가하십시오:
def __bool__(self):
return bool(self.value)
메서드가 포함된 Enum 클래스¶
아래 Planet 클래스처럼 Enum 서브 클래스에 추가 메서드를 제공하면, 해당 메서드는 멤버의 dir() 에는 나타나지만 클래스에서는 나타나지 않습니다:
>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
Flag 멤버 결합¶
Flag 멤버의 조합을 반복하면 단일 비트로 구성된 멤버만 반환됩니다:
>>> class Color(Flag):
... RED = auto()
... GREEN = auto()
... BLUE = auto()
... MAGENTA = RED | BLUE
... YELLOW = RED | GREEN
... CYAN = GREEN | BLUE
...
>>> Color(3) # 이름이 있는 조합
<Color.YELLOW: 3>
>>> Color(7) # 이름이 없는 조합
<Color.RED|GREEN|BLUE: 7>
Flag 및 IntFlag 세부 사항¶
예제에 다음 스니펫을 사용합니다:
>>> class Color(IntFlag):
... BLACK = 0
... RED = 1
... GREEN = 2
... BLUE = 4
... PURPLE = RED | BLUE
... WHITE = RED | GREEN | BLUE
...
다음 사항이 참입니다:
단일 비트 플래그가 표준(canonical)입니다.
다중 비트 및 제로 비트 플래그는 별칭입니다.
반복 시에는 표준 플래그만 반환됩니다:
>>> list(Color.WHITE) [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
플래그 또는 플래그 집합을 부정하면 해당하는 양의 정수 값을 가진 새로운 플래그/플래그 집합이 반환됩니다:
>>> Color.BLUE <Color.BLUE: 4> >>> ~Color.BLUE <Color.RED|GREEN: 3>
가짜(pseudo) 플래그의 이름은 멤버의 이름으로부터 구성됩니다:
>>> (Color.RED | Color.GREEN).name 'RED|GREEN' >>> class Perm(IntFlag): ... R = 4 ... W = 2 ... X = 1 ... >>> (Perm.R & Perm.W).name is None # 사실상 Perm(0) True
별칭인 다중 비트 플래그가 연산 결과로 반환될 수 있습니다:
>>> Color.RED | Color.BLUE <Color.PURPLE: 5> >>> Color(7) # 또는 Color(-1) <Color.WHITE: 7> >>> Color(0) <Color.BLACK: 0>
멤버십 / 포함 여부 확인: 값이 0인 플래그는 항상 포함된 것으로 간주됩니다:
>>> Color.BLACK in Color.WHITE True
그 외의 경우, 한 플래그의 모든 비트가 다른 플래그에 포함되어 있을 때만 True를 반환합니다:
>>> Color.PURPLE in Color.WHITE True >>> Color.GREEN in Color.PURPLE False
범위 밖 또는 잘못된 비트 처리를 제어하는 새로운 경계 메커니즘이 있습니다: STRICT, CONFORM, EJECT, KEEP
STRICT –> 잘못된 값이 제시될 때 예외를 발생시킴
CONFORM –> 모든 잘못된 비트를 버림
EJECT –> Flag 상태를 잃고 주어진 값의 일반 정수(int)가 됨
KEEP –> 추가 비트를 유지함
Flag 상태와 추가 비트를 모두 유지함
반복 시에는 추가 비트가 나타나지 않음
repr() 및 str()에서는 추가 비트가 표시됨
Flag의 기본값은 STRICT 이며, IntFlag의 기본값은 EJECT 이고, _convert_ 의 기본값은 KEEP 입니다(KEEP 이 필요한 경우의 예는 ssl.Options 를 참조하십시오).
Enum과 Flag은 어떻게 다릅니까?¶
열거형은 파생된 Enum 클래스와 그 인스턴스(멤버)의 여러 측면에 영향을 주는 사용자 정의 메타 클래스를 갖습니다::`
Enum 클래스¶
EnumType 메타 클래스는 list(Color)\나 some_enum_var in Color와 같은 일반적인 클래스에서 실패하는 연산을 Enum 클래스로 할 수 있도록 하는 __contains__(), __dir__(), __iter__() 및 기타 메서드를 제공할 책임이 있습니다. EnumType\는 최종 Enum 클래스의 다양한 다른 메서드(가령 __new__(), __getnewargs__(), __str__() 및 __repr__())가 올바른지 확인하는 책임도 있습니다::`
Flag 클래스¶
플래그는 별칭에 대한 확장된 뷰를 갖습니다: 표준화되려면 플래그의 값이 2의 거듭제곱 값이어야 하며, 중복되는 이름이 아니어야 합니다. 따라서 Enum 정의의 별칭 외에도 값이 없는 플래그(즉 0) 또는 하나 이상의 2의 거듭제곱 값을 가진 플래그(예: 3)는 별칭으로 간주됩니다::`
Enum 멤버(인스턴스)¶
Enum 멤버의 가장 흥미로운 점은 이들이 싱글톤이라는 것입니다. EnumType 은 Enum 클래스를 생성하는 동안 모든 멤버를 생성하며, 그 후 사용자 정의 __new__() 을 배치하여 기존 멤버 인스턴스만 반환함으로써 새로운 인스턴스가 절대 생성되지 않도록 보장합니다.
Flag 멤버¶
Flag 멤버는 Flag 클래스와 마찬가지로 반복할 수 있으며, 이때 표준(canonical) 멤버만 반환됩니다. 예를 들어:
>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
(참고: BLACK, PURPLE, WHITE 는 표시되지 않습니다.)
플래그 멤버를 반전시키면 음수 대신 해당 양의 값이 반환됩니다. 예를 들어:
>>> ~Color.RED
<Color.GREEN|BLUE: 6>
Flag 멤버는 포함된 2의 거듭제곱 값의 수에 해당하는 길이를 가집니다. 예를 들어:
>>> len(Color.PURPLE)
2
Enum 요리법¶
Enum, IntEnum, StrEnum, Flag, 그리고 IntFlag 가 대부분의 사용 사례를 충족할 것으로 예상되지만, 모든 경우를 해결할 수는 없습니다. 여기에는 직접 사용할 수 있는 몇 가지 다른 유형의 열거형 또는 자신만의 것을 만들기 위한 예시로 활용할 수 있는 조리법이 있습니다.
값 생략¶
많은 경우에 열거형의 실제 값이 무엇인지는 중요하지 않습니다. 이러한 유형의 간단한 열거형을 정의하는 몇 가지 방법이 있습니다:
이러한 방법 중 하나를 사용하는 것은 사용자에게 해당 값이 중요하지 않음을 의미하며, 나머지 멤버의 번호를 다시 매길 필요 없이 멤버를 추가, 제거 또는 재정렬할 수 있게 해줍니다.
auto 사용¶
auto 를 사용하면 다음과 같은 형태가 됩니다:
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>
object 사용¶
object 를 사용하면 다음과 같은 형태가 됩니다:
>>> class Color(Enum):
... RED = object()
... GREEN = object()
... BLUE = object()
...
>>> Color.GREEN
<Color.GREEN: <object object at 0x...>>
이는 왜 자신만의 __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>
설명적인 문자열을 사용합니다¶
값을 문자열로 사용하는 경우의 형태는 다음과 같습니다:
>>> class Color(Enum):
... RED = 'stop'
... GREEN = 'go'
... BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>
사용자 정의 __new__() 사용하기¶
자동 번호 매기기 __new__() 를 사용하는 경우의 형태는 다음과 같습니다:
>>> class AutoNumber(Enum):
... def __new__(cls):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
>>> class Color(AutoNumber):
... RED = ()
... GREEN = ()
... BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>
더 범용적인 AutoNumber 를 만들려면 서명에 *args 를 추가합니다:
>>> class AutoNumber(Enum):
... def __new__(cls, *args): # 위에서 변경된 유일한 부분
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
그 후 AutoNumber 를 상속할 때 추가 인자를 처리하기 위해 자체 __init__ 를 작성할 수 있습니다:
>>> class Swatch(AutoNumber):
... def __init__(self, pantone='unknown'):
... self.pantone = pantone
... AUBURN = '3497'
... SEA_GREEN = '1246'
... BLEACHED_CORAL = () # 새 색상, 아직 Pantone 코드가 없음!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'
참고
정의된 경우, __new__() 메서드는 Enum 멤버 생성 중에 사용되며, 이후에는 기존 멤버 조회를 위해 클래스 생성 후에 호출되는 Enum의 __new__() 로 대체됩니다.
경고
조회 전용(lookup-only) __new__ 가 선택되므로 super().__new__() 를 호출하지 마십시오. 대신 데이터 타입을 직접 사용하십시오. 예:
obj = int.__new__(cls, value)
OrderedEnum¶
IntEnum 을 기반으로 하지 않으므로 일반적인 Enum 의 불변성(다른 열거형과 비교할 수 없는 속성 등)을 유지하는 순서 있는 열거형입니다:
>>> class OrderedEnum(Enum):
... def __ge__(self, other):
... if self.__class__ is other.__class__:
... return self.value >= other.value
... return NotImplemented
... def __gt__(self, other):
... if self.__class__ is other.__class__:
... return self.value > other.value
... return NotImplemented
... def __le__(self, other):
... if self.__class__ is other.__class__:
... return self.value <= other.value
... return NotImplemented
... def __lt__(self, other):
... if self.__class__ is other.__class__:
... return self.value < other.value
... return NotImplemented
...
>>> class Grade(OrderedEnum):
... A = 5
... B = 4
... C = 3
... D = 2
... F = 1
...
>>> Grade.C < Grade.A
True
DuplicateFreeEnum¶
중복된 멤버 값이 발견될 경우 별칭을 생성하는 대신 오류를 발생시킵니다:
>>> class DuplicateFreeEnum(Enum):
... def __init__(self, *args):
... cls = self.__class__
... if any(self.value == e.value for e in cls):
... a = self.name
... e = cls(self.value).name
... raise ValueError(
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
... % (a, e))
...
>>> class Color(DuplicateFreeEnum):
... RED = 1
... GREEN = 2
... BLUE = 3
... GRENE = 2
...
Traceback (most recent call last):
...
ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN'
참고
이는 별칭을 금지할 뿐만 아니라 다른 동작을 추가하거나 변경하기 위해 Enum을 서브클래싱하는 유용한 예시입니다. 만약 별칭을 금지하는 것이 유일한 목적이라면 대신 unique() 데코레이터를 사용할 수 있습니다.
MultiValueEnum¶
멤버당 하나 이상의 값을 가지는 것을 지원합니다:
>>> class MultiValueEnum(Enum):
... def __new__(cls, value, *values):
... self = object.__new__(cls)
... self._value_ = value
... for v in values:
... self._add_value_alias_(v)
... return self
...
>>> class DType(MultiValueEnum):
... float32 = 'f', 8
... double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>
Planet¶
__new__() 또는 __init__() 이 정의된 경우, 열거형 멤버의 값이 해당 메서드로 전달됩니다:
>>> 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 # 킬로그램(kg) 단위
... self.radius = radius # 미터(m) 단위
... @property
... def surface_gravity(self):
... # 만유인력 상수 (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
TimePeriod¶
_ignore_ 속성이 사용되는 예시:
>>> import datetime as dt
>>> class Period(dt.timedelta, Enum):
... "different lengths of time"
... _ignore_ = 'Period i'
... Period = vars()
... for i in range(367):
... Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
EnumType 서브클래싱¶
대부분의 열거형 요구 사항은 클래스 데코레이터나 사용자 정의 함수를 통해 Enum 을 서브클래싱하여 충족할 수 있지만, EnumType 을 서브클래싱하여 다른 방식의 열거형 경험을 제공할 수도 있습니다.