Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 435 – Python 표준 라이브러리에 Enum 형식 추가

Author:
Barry Warsaw <barry at python.org>, Eli Bendersky <eliben at gmail.com>, Ethan Furman <ethan at stoneleaf.us>
Status:
Final
Type:
Standards Track
Created:
23-Feb-2013
Python-Version:
3.4
Post-History:
23-Feb-2013, 02-May-2013
Replaces:
354
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 Python 표준 라이브러리에 열거형 형식을 추가할 것을 제안합니다.

열거형은 고유한 상수 값에 바인딩된 기호 이름의 집합입니다. 열거형 내에서는 값을 동일성으로 비교할 수 있으며, 열거형 자체를 순회할 수 있습니다.

논의 현황

Python에 enum 형식을 추가하자는 생각은 새로운 것이 아닙니다. - PEP 354는 2005년에 거부된 이전 시도입니다. 최근 python-ideas 메일링 리스트에서 새로운 논의가 시작되었습니다 [3]. 여러 스레드에서 많은 새로운 아이디어가 제안되었으며, 긴 논의 끝에 Guido는 flufl.enum를 표준 라이브러리에 추가할 것을 제안했습니다 [4]. PyCon 2013 언어 정상 회의에서 이 문제가 더 논의되었습니다. 많은 개발자가 int를 상속하는 enum을 원한다는 점이 분명해졌습니다. 이를 통해 하위 호환성을 포기하지 않고 표준 라이브러리의 많은 정수 상수를 친숙한 문자열 표현을 제공하는 enum으로 대체할 수 있습니다. 여러 관심 있는 핵심 개발자 사이의 추가 논의를 통해 IntEnumEnum의 특수한 경우로 두자는 제안이 나왔습니다.

EnumIntEnum을 가르는 핵심 쟁점은 정수와의 비교가 의미론적으로 유의미한지 여부입니다. 대부분의 열거형 사용 사례에서는 정수와의 비교를 거부하는 것이 기능입니다. 정수와 비교되는 enum은 추이성을 통해 서로 관련 없는 형식의 enum 간 비교로 이어지며, 이는 대부분의 경우 바람직하지 않습니다. 그러나 일부 사용 사례에서는 정수와의 더 높은 상호 운용성이 필요합니다. 예를 들어 기존 표준 라이브러리 상수(예: socket.AF_INET)를 열거형으로 대체하는 경우가 그렇습니다.

2013년 4월 말의 추가 논의를 통해 열거형 멤버는 해당 enum의 형식에 속해야 한다는 결론에 도달했습니다. 즉, type(Color.red) == Color입니다. Guido는 이 문제에 대해 결정했으며 [5], 열거형을 서브클래싱하지 못하게 하는 관련 문제에 대해서도 결정했습니다 [6]. 단, 열거형 멤버를 정의하지 않는 경우는 예외입니다 [7].

이 PEP는 2013년 5월 10일 Guido에 의해 승인되었습니다 [1].

동기

[부분적으로 동기를 바탕으로 한 PEP 354에 명시된 내용]

열거형의 속성은 의미론적 의미가 있을 수도 있고 없을 수도 있는, 변경 불가능하고 서로 관련된 상수 값 집합을 정의하는 데 유용합니다. 전형적인 예로는 요일(일요일부터 토요일까지)과 학교 평가 등급(‘A’부터 ‘D’까지 및 ‘F’)이 있습니다. 그 밖의 예로는 오류 상태 값과 정의된 프로세스 내의 상태가 있습니다.

intstr와 같은 다른 기본 형식의 값 시퀀스를 간단히 정의하여 불연속적인 임의의 값을 나타낼 수도 있습니다. 그러나 열거형은 그러한 값이 다른 모든 값, 특히 다른 열거형 내의 값과 구별되도록 보장하며, 이러한 값에 의미 없는 연산(“수요일 곱하기 2”)이 정의되지 않도록 합니다. 또한 값을 정의할 때 지루하게 반복해서 작성하지 않아도 enum 값의 편리한 출력 표현을 제공합니다(즉, GREEN = 'green'과 같이 작성하지 않아도 됩니다).

모듈 및 형식 이름

표준 라이브러리에 enum이라는 모듈을 추가할 것을 제안합니다. 이 모듈이 노출하는 주요 형식은 Enum입니다. 따라서 Enum 형식을 가져오려면 사용자 코드는 다음을 실행합니다.:

>>> from enum import Enum

새로운 열거형 형식에 대해 제안된 의미론

Enum 생성

열거형은 클래스 구문을 사용하여 생성하므로 읽고 쓰기 쉽습니다. 다른 생성 방법은 Functional API에 설명되어 있습니다. 열거형을 정의하려면 다음과 같이 Enum을 상속하십시오.:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3

용어에 관한 참고 사항: Color열거형 (또는 enum)이라고 하며, Color.red, Color.green열거형 멤버 (또는 enum 멤버)입니다. 열거형 멤버에는 도 있습니다 (Color.red의 값은 1등입니다).

열거형 멤버에는 사람이 읽을 수 있는 문자열 표현이 있습니다.:

>>> print(Color.red)
Color.red

…반면 repr에는 더 많은 정보가 있습니다.:

>>> print(repr(Color.red))
<Color.red: 1>

열거형 멤버의 타입은 해당 멤버가 속한 열거형입니다.:

>>> type(Color.red)
<Enum 'Color'>
>>> isinstance(Color.green, Color)
True
>>>

열거형에는 항목 이름만 포함하는 속성도 있습니다.:

>>> print(Color.red.name)
red

열거형은 정의된 순서대로 반복을 지원합니다.:

>>> class Shake(Enum):
...   vanilla = 7
...   chocolate = 4
...   cookies = 9
...   mint = 3
...
>>> for shake in Shake:
...   print(shake)
...
Shake.vanilla
Shake.chocolate
Shake.cookies
Shake.mint

열거형 멤버는 해시 가능하므로 딕셔너리와 집합에서 사용할 수 있습니다.:

>>> apples = {}
>>> apples[Color.red] = 'red delicious'
>>> apples[Color.green] = 'granny smith'
>>> apples
{<Color.red: 1>: 'red delicious', <Color.green: 2>: 'granny smith'}

열거형 멤버에 대한 프로그래밍 방식의 접근

때로는 열거형의 멤버에 프로그래밍 방식으로 접근하는 것이 유용합니다 (즉, 프로그램을 작성할 때 정확한 색을 알 수 없어 Color.red로는 처리할 수 없는 상황입니다). Enum은 이러한 접근을 허용합니다.:

>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>

열거형 멤버에 이름으로 접근하려면 항목 접근을 사용하십시오.:

>>> Color['red']
<Color.red: 1>
>>> Color['green']
<Color.green: 2>

열거형 멤버와 값의 중복

이름이 같은 두 열거형 멤버를 갖는 것은 유효하지 않습니다.:

>>> class Shape(Enum):
...   square = 2
...   square = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: square

그러나 두 열거형 멤버가 같은 값을 갖는 것은 허용됩니다. 같은 값을 갖는 두 멤버 A와 B가 있고 A가 먼저 정의되었다면, B는 A의 별칭입니다. A와 B의 값을 값으로 조회하면 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>

열거형의 멤버를 반복해도 별칭은 제공되지 않습니다.:

>>> list(Shape)
[<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]

특수 속성 __members__는 이름을 멤버에 매핑하는 순서가 있는 딕셔너리입니다. 이 속성에는 별칭을 포함하여 열거형에 정의된 모든 이름이 포함됩니다.:

>>> 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__속성은 열거형 멤버에 대한 세부적인 프로그래밍 방식의 접근에 사용할 수 있습니다. 예를 들어, 모든 별칭을 찾는 데 사용할 수 있습니다.:

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['alias_for_square']

비교

열거형 멤버는 동일성으로 비교됩니다.:

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True

열거형 값 간의 순서 비교는 지원되지 않습니다. 열거형은 정수가 아닙니다 (단, 아래의 IntEnum을 참조하십시오).:

>>> Color.red < Color.blue
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Color() < Color()

그러나 동등성 비교는 정의되어 있습니다.:

>>> Color.blue == Color.red
False
>>> Color.blue != Color.red
True
>>> Color.blue == Color.blue
True

열거형이 아닌 값과의 비교는 항상 같지 않은 것으로 비교됩니다 (다시 말해, IntEnum은 다르게 동작하도록 명시적으로 설계되었습니다. 아래를 참조하십시오).:

>>> Color.blue == 2
False

열거형의 허용되는 멤버와 속성

위의 예제에서는 열거형 값으로 정수를 사용합니다. 정수를 사용하는 방식은 간결하고 편리하며 Functional API에서 기본적으로 제공되지만, 엄격하게 강제되지는 않습니다. 대부분의 사용 사례에서는 열거형의 실제 값이 무엇인지는 중요하지 않습니다. 그러나 값이 중요하다면, 열거형은 임의의 값을 가질 수 있습니다.

열거형은 Python 클래스이므로, 일반적인 경우와 마찬가지로 메서드와 특수 메서드를 가질 수 있습니다. 다음 열거형이 있다면:

class Mood(Enum):
  funky = 1
  happy = 3

  def describe(self):
    # self is the member here
    return self.name, self.value

  def __str__(self):
    return 'my custom str! {0}'.format(self.value)

  @classmethod
  def favorite_mood(cls):
    # cls here is the enumeration
    return cls.happy

그러면:

>>> Mood.favorite_mood()
<Mood.happy: 3>
>>> Mood.happy.describe()
('happy', 3)
>>> str(Mood.funky)
'my custom str! 1'

허용되는 항목에 관한 규칙은 다음과 같습니다. 열거형 내부에 정의된 모든 속성은 __dunder__ 이름과 디스크립터 [9]를 제외하고 이 열거형의 멤버가 됩니다. 메서드도 디스크립터입니다.

제한된 열거형 서브클래싱

열거형의 서브클래싱은 해당 열거형에 멤버가 정의되어 있지 않은 경우에만 허용됩니다. 따라서 다음은 허용되지 않습니다.:

>>> class MoreColor(Color):
...   pink = 17
...
TypeError: Cannot extend enumerations

그러나 다음은 허용됩니다.:

>>> class Foo(Enum):
...   def some_behavior(self):
...     pass
...
>>> class Bar(Foo):
...   happy = 1
...   sad = 2
...

이 결정의 근거는 Guido가 [6] 에서 제시했습니다. 멤버를 정의하는 열거형의 서브클래싱을 허용하면 타입과 인스턴스의 몇 가지 중요한 불변식을 위반하게 됩니다. 반면 열거형 그룹 간에 공통 동작을 일부 공유하도록 허용하는 것은 타당하며, 빈 열거형의 서브클래싱은 IntEnum을 구현하는 데에도 사용됩니다.

IntEnum

int의 서브클래스이기도 한 Enum의 변형이 제안되었습니다. 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]

대부분의 코드에서는 Enum을 강력히 권장합니다. IntEnum은 정수와 비교할 수 있고, 따라서 추이성에 의해 서로 관련되지 않은 다른 열거형과도 비교할 수 있으므로 열거형의 일부 의미론적 약속을 깨뜨리기 때문입니다. 다른 선택지가 없는 특수한 경우에만 사용해야 합니다. 예를 들어 정수 상수를 열거형으로 대체하면서 여전히 정수를 기대하는 코드와의 하위 호환성이 필요한 경우입니다.

기타 파생 열거형

IntEnumenum 모듈의 일부가 됩니다. 그러나 이를 독립적으로 구현하는 것은 매우 간단합니다.:

class IntEnum(int, Enum):
    pass

이는 유사한 파생 열거형을 정의하는 방법을 보여 줍니다. 예를 들어 int 대신 str을 혼합하는 StrEnum을 정의할 수 있습니다.

몇 가지 규칙은 다음과 같습니다.

  1. Enum을 서브클래싱할 때는 위의 IntEnum 예제와 같이 베이스 순서에서 믹스인 타입이 Enum 자체보다 앞에 와야 합니다.
  2. Enum은 어떤 타입의 멤버든 가질 수 있지만, 추가 타입을 하나 혼합하면 모든 멤버가 해당 타입의 값을 가져야 합니다. 위의 int가 그 예입니다. 이 제한은 메서드만 추가하고 intstr과 같은 다른 데이터 타입을 지정하지 않는 믹스인에는 적용되지 않습니다.

피클링

열거형은 피클링하고 언피클링할 수 있습니다:

>>> from enum.tests.fruit import Fruit
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True

피클링에 대한 일반적인 제약이 적용됩니다: 피클 가능한 열거형은 모듈의 최상위 레벨에서 정의되어야 하는데, 이는 언피클링을 하려면 해당 모듈에서 임포트할 수 있어야 하기 때문입니다.

함수형 API

Enum 클래스는 호출 가능 객체이며, 다음과 같은 함수형 API를 제공합니다:

>>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animal
<Enum 'Animal'>
>>> Animal.ant
<Animal.ant: 1>
>>> Animal.ant.value
1
>>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]

이 API의 의미론은 namedtuple과 유사합니다. Enum 호출의 첫 번째 인자는 열거형의 이름입니다. 함수형 API로 생성한 열거형을 피클링하는 것은 CPython과 PyPy에서는 동작하지만, IronPython과 Jython에서는 다음과 같이 모듈 이름을 명시적으로 지정해야 할 수도 있습니다:

>>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__)

두 번째 인자는 열거형 멤버 이름의 원본입니다. 이는 공백으로 구분된 이름 문자열, 이름의 시퀀스, 키/값 쌍으로 이루어진 2-튜플의 시퀀스, 또는 이름을 값에 대응시키는 매핑(예: 딕셔너리)일 수 있습니다. 마지막 두 가지 옵션은 열거형에 임의의 값을 할당할 수 있게 해주며, 나머지는 1부터 시작하여 증가하는 정수를 자동으로 할당합니다. Enum에서 파생된 새 클래스가 반환됩니다. 다시 말해, 위의 Animal에 대한 대입은 다음과 동등합니다:

>>> class Animals(Enum):
...   ant = 1
...   bee = 2
...   cat = 3
...   dog = 4

시작 번호를 0이 아니라 1로 기본 설정하는 이유는, 불리언 의미에서 0False이지만 열거형 멤버는 모두 True로 평가되기 때문입니다.

제안된 변형들

메일링 리스트 토론 중에 몇 가지 변형이 제안되었습니다. 그중 더 인기 있었던 것들을 몇 가지 소개합니다.

flufl.enum

flufl.enum은 이 PEP가 원래 기반으로 했던 참조 구현이었습니다. 결국 flufl.enum의 포함은 채택하지 않기로 결정되었는데, 그 설계가 열거형 멤버를 열거형으로부터 분리하고 있어서, 전자가 후자의 인스턴스가 아니었기 때문입니다. 또한 그 설계는 열거형을 서브클래싱하여 더 많은 멤버로 확장하는 것을 명시적으로 허용합니다(멤버와 열거형이 분리되어 있기 때문에, flufl.enum에서는 이러한 방식으로도 타입 불변식이 위반되지 않습니다).

열거형에 값을 지정하지 않아도 되도록 하기

Michael Foord는 이를 가능하게 하는 메타클래스 마법을 사용할 것을 제안했으며(그리고 Tim Delaney가 개념 증명 구현을 제공했습니다):

class Color(Enum):
    red, green, blue

값은 처음 조회될 때에야 실제로 할당됩니다.

장점: 매우 흔한 작업(값에 신경 쓰지 않고 열거형 이름만 나열하는 것)에 필요한 타이핑을 줄여주는 더 깔끔한 구문입니다.

단점: 구현에 많은 마법이 관여하여, 이러한 열거형의 정의조차 처음 볼 때는 당혹스럽게 만듭니다. 게다가, 명시적인 것이 암묵적인 것보다 낫습니다.

특수한 이름이나 형식을 사용하여 열거형 값을 자동 할당하기

열거형 값을 지정하지 않기 위한 또 다른 접근법은 특수한 이름이나 형식을 사용하여 자동으로 할당하는 것입니다. 예를 들면:

class Color(Enum):
    red = None          # auto-assigned to 0
    green = None        # auto-assigned to 1
    blue = None         # auto-assigned to 2

더 유연하게는:

class Color(Enum):
    red = 7
    green = None        # auto-assigned to 8
    blue = 19
    purple = None       # auto-assigned to 20

이 주제에 대한 몇 가지 변형은 다음과 같습니다:

  1. enum 패키지에서 임포트한 특수한 이름 auto입니다.
  2. Georg Brandl은 같은 효과를 얻기 위해 None 대신 줄임표(...)를 제안했습니다.

장점: 값을 수동으로 입력할 필요가 없습니다. 특히 대규모 열거형의 경우, 열거형을 변경하고 확장하기가 더 쉬워집니다.

단점: 실제로 많은 단순한 경우에는 입력이 더 길어집니다. 명시적 대 암묵적 논쟁은 여기에도 적용됩니다.

표준 라이브러리에서의 사용 사례

Python 표준 라이브러리에는 열거형을 사용하는 것이 현재 그것들을 표현하는 데 사용되는 다른 관용구를 대체하기에 유익할 곳이 많이 있습니다. 이러한 용도는 사용자 코드에 노출되는 상수와 내부 상수, 두 범주로 나눌 수 있습니다.

os.SEEK_*, socket 모듈 상수, 십진수 반올림 모드, HTML 오류 코드와 같이 사용자 코드에 노출되는 상수는 사용자 코드가 정수를 기대할 수 있으므로 하위 호환성이 필요할 수 있습니다. 위에서 설명한 IntEnum은 필요한 의미론을 제공합니다. int의 서브클래스이므로 정수를 기대하는 사용자 코드에 영향을 주지 않으면서도, 다른 한편으로는 열거형 값에 대해 출력 가능한 표현을 허용합니다.:

>>> import socket
>>> family = socket.AF_INET
>>> family == 2
True
>>> print(family)
SocketFamily.AF_INET

내부 상수는 사용자 코드에 보이지 않지만 표준 라이브러리 모듈 내부에서 사용됩니다. 이것들은 Enum으로 구현할 수 있습니다. 표준 라이브러리를 아주 부분적으로 훑어보는 것만으로도 발견된 예로는 binhex, imaplib, http/client, urllib/robotparser, idlelib, concurrent.futures, turtledemo가 있습니다.

또한 Twisted 라이브러리의 코드를 살펴보면, 내부 상태 상수를 열거형으로 대체할 만한 사용 사례가 많이 있습니다. 많은 네트워킹 코드(특히 프로토콜 구현)에 대해서도 같은 말을 할 수 있으며, Tulip 라이브러리로 작성된 테스트 프로토콜에서도 이를 볼 수 있습니다.

감사의 말

이 PEP는 초기에는 Barry Warsaw의 flufl.enum 패키지 [8]를 표준 라이브러리에 포함시킬 것을 제안했으며, 대부분 이 패키지에서 영감을 받았습니다. Ben Finney는 이전 열거형 제안인 PEP 354의 저자입니다.

참고 문헌