from dataclasses import dataclass  
  
@dataclass(frozen=True) # repr True by default  
class QuitMessage:  
    pass  
  
@dataclass(frozen=True, kw_only=True) # repr True by default  
class MoveMessage:  
    x: int  
    y: int  
  
@dataclass(frozen=True) # repr True by default  
class WriteMessage:  
    _0: str  
  
@dataclass(frozen=True) # repr True by default  
class ChangeColorMessage:  
    _0: int  
    _1: int  
    _2: int  
  
Message = QuitMessage | MoveMessage | WriteMessage | ChangeColorMessage  
  1. dataclass는 기본적으로 repr 구현을 지원합니다
  2. dataclasses.fields는 필드 정의에 대한 실행시간 정보를 제공합니다
  3. 지네릭은 typing 모듈에 의해 3.5부터, syntactic sugar는 3.12부터 지원합니다
  4. Messages 이름 공간의 경우 모듈로 구현 가능합니다

그럼에도 불구하고 class 정의에 필요한 보일러플레이트 코드가 없다는 점, enum과 class를 한가지 인터페이스로 사용할 수 있는 점이 장점이 될 수 있겠네요. 상세한 설명 감사합니다

https://stackoverflow.com/a/47784683

이런 식으로 구조체를 표현하고자 하는 시도들이 여러가지가 있어왔는데, 결국에는 파이썬의 한계이자 단점으로 볼 수 있을 것 같습니다. ADT(algebraic data type)를 학교 수업때 ocaml로 처음 접했었는데 일할때는 이런 식으로 흉내만 내야 한다는 게 좀 안타깝기도 하네요

ilotoki님께서 만드신 라이브러리가 가장 ADT에 근접한 사례로 볼 수 있을 것 같습니다. 언젠가 표준 라이브러리에 포함되고 널리 쓰이게 된다면 좋을 것 같습니다

Message의 구현은 Union으로 하게 된다면 메서드 상속을 이용할 수 없습니다. 예를 들어

from fieldenum import fieldenum, Variant, Unit  
  
  
@fieldenum  
class Message:  
    Quit = Unit  
    Move = Variant(x=int, y=int)  
    Write = Variant(str)  
    ChangeColor = Variant(int, int, int)  
  
    def process(self):  
        ...  

위와 같이 .process 메서드를 추가하면 모든 배리언트들에 대해 .process() 메서드를 사용할 수 있습니다.

# Message.process() 메서드를 각 배리언트에서 사용 가능  
Message.Quit.process()  
Message.Move(x=123, y=456).process()  
Message.Write("hello, world").process()  
Message.ChangeColor(123, 000, 89).process()  

또한 제가 설명드린 repr는 '해당 enum의 배리언트로서의 repr'를 의미한 것입니다.
예를 들어 fieldenum을 repr를 감싸 호출하면 다음과 같이 실행됩니다.

print(repr(Message.Move(x=123, y=456)))  # Message.Move(x=123, y=456)  

커스텀 __repr__가 없으면 Message enum의 하위 배리언트라는 사실이 표현되지 않습니다.

Quit은 유닛 배리언트로 호출 없이 사용합니다.

Message.Quit  # 별도의 호출 (예: `Message.Quit()`) 없이 사용 가능  

또한 호출을 사용해야 하는 배리언트 종류인 fieldless 배리언트의 경우에는 싱글톤으로서 is 연산자로 확인할 수 있습니다.

from fieldenum import fieldenum, Variant, Unit  
  
class WithFieldless:  
    Fieldless = Variant()  
  
assert WithFieldless.Fieldless() is WithFieldless.Fieldless()  

fieldenum을 사용하면 이렇게 놓치기 쉬운 다양한 구현 디테일을 자동으로 챙기는 데에 도움이 됩니다.