[Python] Enum

gunny·2024년 3월 7일
0

Python

목록 보기
9/29

1. 글을 쓰게 된 배경

이전 회사에서는 AI 기반 서비스 api를 만들 때 구조를 혼자 생각하고,
혼자서 코드 짜고 그냥 돌아가면 굿. 그리고 코드 리뷰 해 줄 사람도 없어서
그냥 오류나면 예외처리하거나 그나마 추후에 들어온 팀원이랑 머리 싸매면서 수정해왔다.
이제 다른 새럼이 짜놓은, 그리고 구상한 structure에 기능을 추가하고 있는데
이게 생각보다 힘든..? 내 머리상 로직은 이게 아닌데 먼저 짜놓은 사람은 짜놓은 로직이 있고 그래서 내 기능을 넣어서 테스트 하는게 아니라 그냥 내가 따로 기능을 모듈화해서 만들어서 PR을 날리면 그쪽에서 자기가 생각하는 입맛대로 다시 수정해서 api가 수정중..(?)

그러면 일이 두 번 진행될 것 같긴한데
요새 컨디션이 악화라 작성한 프롬프트에서 etc를 ect로 쓰질않나
구분자인 delimiter를 delimeter로 쓰질 않나..
알아서 입맛대로 수정해주시면 ㄱㅅ합니다만은
혼자 다 하시다가 지치시겠는데요...
그리고 왠지 내 코드가 부정당하는 느낌이 들어서 살짝 슬프기도 하고..

일단은 모르겠고 구상한 스트럭처에 맞춰서 더 나은 구조가 아니면
맞춰가는게 맞는 것 같아서 코드 리뷰를 하던 중에 발견한 Enum 입니다.

2. python enum 공식 documents

공식문서로 보는 것이 국룰이긴 해서 첨부

https://docs.python.org/3/library/enum.html

하지만 남들이 쉽게 쓴 글을 한번 쓱 보고 이해한다음에
공식문서로 가는게 제 신상에 더 좋더라고요.

enum은

  • 고유한 값에 묶인 상징적인 이름(회원)의 집합이다.
  • 정의 순서대로 표준(즉, 비별칭) 멤버를 반환하기 위해 반복될 수 있다.
  • 호출 구문을 사용하여 값으로 멤버를 반환합니다.
  • 인덱스 구문을 사용하여 이름으로 멤버를 반환합니다.

Enum cookbook

https://docs.python.org/3/howto/enum.html#enum-cookbook

3. 이해하기 위해 참고한 사이트들

4. 이해를 위한 실습

(vs code 내의 jupyter 로 진행함)

4-(1) class 형태로 Enum 구현

enum의 Enum을 사용해서 Class 형태로 구현하거나,
함수 형태로 구현하는 방법이 있다.
함수 형태로 구현하는 경우에는 value, names 인자를 사용하든, 순서대로 쓰든 상관이 없는 듯 하다.

Number 라는 이름의 클래스를 가지고 Enum을 상속하고,
FRIST, SECOND, THIRD, FOURTH 를 각각 1,2,3,4로 할당한다.

여기서 FRIST, SECOND, THIRD, FOURTH는 '열거형 멤버', '열거형 상수' 라고 부른다.
여기서 열거형(Enum) 클래스의 고유한 식별자(identifier)로 사용되고, 각각 멤버들은 열거형 클래스의 고유한 값을 나타낸다.

functional로 생성해도 이와 같다.

4-(2) Enum의 iterable 동작 지원

Enum 클래스는 반복 가능(iterable)한 객체이므로
for 루프를 사용해서 Enum 클래스의 멤버를 순회할 수 있다.
내부적으로 Enum 클래스는 'iter()' 메서드가 있어 이러한 동작을 지원한다.

즉, Enum 클래스가 열거형 상수를 나타내는 객체들을 순회하거나, 특정한 처리를 수행할 때 편리하게 사용된다.

4-(3) Enum 클래스와 열거형 멤버 타입

이렇게 생성한 Enum 클래스에 대한 타입을 알아보면,

  • Number class 자체는 <class 'enum.EnumMeta'>
  • Number class의 열거형 멤버, 열거형 상수 중 하나인 Number.FIRST는 <enum 'Number'> 이다.

여기서 혼동하지 않아야 하는 것이
Number.FIRST 로 열거형 멤버를 프린트하면 Number.FIRST 그 자체가 출력된다. Enum 클래스의 멤버들은 그 자체로 객체이다.
즉, Number.FIRST는 'Number' Enum의 클래스의 첫 번째 멤버인 'FIRST'를 나타내는 객체를 반환한다. 이 객체는 단순히 'Number.FIRST' 로써의 식별자를 가진다.

Enum 클래스는 각 멤버가 고유한 값을 가질 수 있지만, 이 값은 멤버 객체 자체에는 저장되지 않는다. 멤버 객체는 그 자체로 식별자의 역할을 하고, 해당 멤버를 나타내는 유일한 인스턴스이다.

이 객체를 통해서 아래 Number.FIRST.name을 멤버의 이름을,
Number.FIRST.value로 값들을 반환할 수 있다.

4-(4) Enum 클래스의 메서드

'generate_next_value' 메서드는 python의 Enum 클래스에서 제공하는 메서드 중 하나이다.
Enum 클래스의 하위 클래스에서 재정의할 수 있는데, 열거형 멤버의 값을 자동으로 생성하는 데 사용한다.

일반적으로 Enum 멤버는 사용자가 직접 값(value)를 할당하게 되는데, 이 때
'generate_next_value' 메서드를 사용하면 멤버의 이름을 기반으로 값이 자동으로 생성된다.

def _generate_next_value_(name, start, count, last_values) -> Any:
    # 값을 생성하고 반환하는 로직
  • name : 새로운 열거형 멤버의 이름
  • start : 첫 번째 멤버의 값. 기본값 1
  • count : 현재까지 생성된 멤버의 수
  • last_values : 이전에 생서된 멤버들의 값으로 순회 가능한 literable객체
  • 반환 값 : 새로운 멤버의 값으로 사용될 객체를 반환. 임의의 타입

기본적으로 'generate_next_value' 는 새로운 멤버의 이름을 그대로 반환하는데, 사용자가 원하는 로직에 따라 새로운 멤버의 값을 결정할 수 있다.
이 메서드를 재정의하면 Enum 클래스는 자동으로 멤버의 값을 할당하지 않고 이 메서드를 호출해서 멤버의 값을 생성한다. 코드의 유연성을 높일 수 있는 기능이다.

예를 들어보자면,

열거형 멤버가 일련 번호를 갖는다고 생각해 볼 때, 해당 멤버를 생성할 때마다 값이 1씩 증가하는 순차적인 일련번호를 할당하고 싶을 때

from enum import Enum, auto

class SequentialEnum(Enum):
    def _generate_next_value_(name, start, count, last_values):
        # 이전에 생성된 멤버들의 값들 중 가장 큰 값에 1을 더하여 반환
        if last_values:
            return last_values[-1] + 1
        else:
            return start

class Numbers(SequentialEnum):
    ONE = auto()
    TWO = auto()
    THREE = auto()

print(Numbers.ONE.value)    # 1
print(Numbers.TWO.value)    # 2
print(Numbers.THREE.value)  # 3

메서드는 마지막으로 생성된 멤버의 값 중 가장 큰 값에 1을 더하여 다음 멤버의 값으로 사용한다. 만약 처음 멤버가 생성되는 경우에는 start 값을 사용하여 초기값을 설정하면 된다. 열거형 멤버의 값이 자동으로 생성되므로, 일일이 값을 지정하지 않아도 되는 것이다.


아래는 Enum 모듈을 사용해 문자열 기반의 열거형을 정의하고 auto() 함수로 각 멤버에 대해 자동으로 값을 할당하는 코드이다.

from enum import Enum, auto

class StrEnum(str, Enum):
    
    def _generate_next_value(name, start, count, last_values) -> str:
        return name
    
    def __str__(self):
        return self.name
    
    def __repr__(self) -> str:
        return self.name
        
class ColorEnum(StrEnum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()
    BLACK = auto()      
  • StrEnum 클래스에서 str, Enum 클래스를 상속받아서 문자열처럼 동작하면서 열거형의 기능을 가지도록 한다.
    해당 클래스에서는 _generate_next_value로 열거형 멤버의 값을 자동으로 생성하는 메소드로, Enum 클래스에서 상속받아 각 멤버의 이름을 값으로 사용해 간단히 열거형 멤버의 값을 생성한다.
    __str__ 에서는 객체를 문자열로 변환하여, StrEnum 클래스의 객체를 문자열로 표현할 때 해당 객체의 이름을 반환한다.
    __repr__ 에서는 객체의 공식적인(representation) 문자열을 반환하고, 객체를 repr() 함수로 표현할 때 해당 객체의 이름을 반환한다.
  • ColorEnum 클래스에서 StrEnum을 상속받아 각 멤버들에 대해 auto() 함수로 자동으로 각 값을 할당한다. 이렇게 정의된 ColorEnum 클래스는 문자열 기반의 열거형으로 각 멤버는 해당 멤버의 이름과 동일한 문자열 값을 가진다.
print(ColorEnum.BLACK) #BLACK
print(type(ColorEnum.BLACK)) #<enum 'ColorEnum'>

print(str(ColorEnum.BLACK)) #BLACK
print(type(str(ColorEnum.BLACK)))  # <class 'str'>

print(repr(ColorEnum.BLACK)) #BLACK
print(type(repr(ColorEnum.BLACK))) # <class 'str'>
  • ColorEnum.BLACK은 ColorEnum 열거형의 멤버 BLACK을 나타내고, 이것은 해당 열거형 멤버 객체를 참조하는 것이다.

  • str(ColorEnum.BLACK)은 str() 함수를 사용해서 해당 열거형 멤버 객체를 문자열로 변환한다. 이 경우 열거형 멤버의 이름을 문자열로 반환한다.

  • 반면에 repr(ColorEnum.BLACK)은 ColorEnum.BLACK 객체의 "공식적인(representation)" 문자열을 반환한다. 이 메소드를 정의하지 않은 경우, 기본적으로 Enum 클래스에서 상속된 repr 메소드가 해당 멤버의 이름을 반환합니다. 그래서 repr(ColorEnum.BLACK)의 결과는 'BLACK'가 된다. 해당 열거형 멤버의 이름을 따옴표로 감싼 문자열로 반환한다.

  • 즉, ColorEnum.BLACK는 BLACK 열거형 멤버 객체를 나타내며, str(ColorEnum.BLACK)는 "BLACK" 문자열을 반환하고, repr(ColorEnum.BLACK)는 'BLACK' 문자열을 반환합니다.

일반적인 문자열 표현 vs 공식적인 문자열 표현

[일반적인 문자열 표현]

  • 객체를 사람이 읽기 쉬운 형태로 표현
  • 사용자 친화적인 형태
  • 정보의 축약이나 생략이 발생 할 수 있음
  • 숫자 객체의 경우 일반적인 문자열 표현은 해당 숫자의 값 자체

[공식적인 문자열 표현]

  • 객체의 공식적인 형태
  • 객체를 재생성하는데 필요한 최소한의 정보 포함
  • 객체의 타입, 식별자 또는 특정 속성에 대한 정보 포함
  • 리스트 객체의 경우 공식적인 문자열 표현은 해당 리스트의 요소를 포함하는 구조

[예시 코드]

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"
        
# 일반적인 문자열 표현
p1 = Point(3, 4)
print(str(p1))  # 출력: Point(3, 4)

# 공식적인 문자열 표현
print(repr(p1))  # 출력: Point(x=3, y=4)
  • 일반적인 문자열 표현은 주로 객체를 사용자에게 보여주기 위해 간결하고 읽기 쉽게 표현하고, 반면에 공식적인 문자열 표현은 객체의 상태나 속성을 더욱 정확하게 나타내는데 사용된다.

  • 이 예시에서는 Point 클래스를 정의하고, str 메소드와 repr 메소드를 구현하여 각각의 문자열 표현을 반환하도록 해봤다.

    • str(p1)은 Point 객체 p1의 일반적인 문자열 표현을 반환한다. 이는 사용자에게 보여지는 형태로, 간단하게 좌표를 표시한다.
    • repr(p1)은 Point 객체 p1의 공식적인 문자열 표현을 반환한다. 이는 파이썬 코드로 객체를 다시 생성할 수 있는 형태로, 좀 더 자세한 정보를 제공한다.

이렇게 보면 str()과 repr()의 결과가 서로 다른 형태를 가지는 것을 확인할 수 있다.

profile
꿈꾸는 것도 개발처럼 깊게

0개의 댓글