10P by ilotoki0804 3달전 | favorite | 댓글 14개
  • fieldenum은 값을 가지는 (인스턴스화할 수 있는) enum입니다.
  • 러스트의 필드 있는 enum을 깔끔하게 지원합니다.
  • 함수형 프로그래밍의 순수성과 파이썬에서의 실용성 사이의 균형을 잡기 위해 노력했습니다.
  • 기본으로 None의 대안인 Option이나 예외의 대안인 BoundResult을 지원합니다.
  • 완전히 테스트되어 있습니다.
  • 아직 영문 문서가 빈약하지만 점차 보강해 나갈 계획입니다.
  • 이슈, PR, star 등의 다양한 형태의 지원들을 모두 환영합니다.

dataclass의 union 타입이 더 낫지 않을까 하는데 선언문이 짧은거 빼고는 장점을 잘 모르겠네요. fieldenum이 특별히 나은점이 있을까요?

선언이 짧고 간결하며 필요한 부분만 있는 것 또한 큰 장점입니다.
예를 들어,

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)  

위에 있는 fieldenum을 dataclass로 구현하려면 다음과 같이 짜야 합니다.

from dataclasses import dataclass  
from typing import Self  
  
  
class Message:  
    Quit = Self  
    Move = Self  
    Write = Self  
    ChangeColor = Self  
  
  
class QuitMessageClass(Message, metaclass=ParamlessSingletonMeta):  
    pass  
  
QuitMessage = QuitMessageClass()  
  
  
@dataclass(frozen=True, kw_only=True)  
class MoveMessage(Message):  
    x: int  
    y: int  
  
  
@dataclass(frozen=True)  
class WriteMessage(Message):  
    _0: str  
  
  
@dataclass(frozen=True)  
class ChangeColorMessage(Message):  
    _0: int  
    _1: int  
    _2: int  
  
  
Message.Quit = QuitMessage  
Message.Move = MoveMessage  
Message.Write = WriteMessage  
Message.ChangeColor = ChangeColorMessage  

코드가 길어지고 보기도 어려워졌고, 실수할 가능성도 높으며, 코드가 깔끔하다고 느껴지지는 않죠?

물론 이렇게 짜더라도 fieldenum에서 제공하는 더 많은 다른 기능들(제너릭, repr, __fields__, ...)은 제공받을 수 없습니다.

따라서 이 모든 것들을 구현하고 모아놓은 fieldenum이 있으면 훨씬 편리합니다.

그 외에도 예시 파트에 있는 내용을 참고해 보시면 좋을 듯 합니다.

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을 사용하면 이렇게 놓치기 쉬운 다양한 구현 디테일을 자동으로 챙기는 데에 도움이 됩니다.

혹시 파이콘 한국에서 발표해주시면 어떨까 제안드려봅니다. 너무 재미있게 봐서, 만드시는 과정에 있으셨던 이야기와 설명을 직접 듣고 싶습니다!

파이콘에서 발표하게 되면 정말 영광일 것 같네요. 제가 하고 싶다고 해서 할 수 있을지는 모르겠지만(^^;) 고민해 보겠습니다.

그리고 영문 README 에 Option 예시도 설명되었으면 좋겠네요.
Option 은 쉽게 이해되고 친숙하게 접근할수 있을테니까요. 문서상 설명 순서에서 Option을 먼저 설명하면 더 좋을것 같기도 합니다.

영어 문서는 아직 준비 중이라 조금 빈약합니다... 한국어 문서가 충분히 무르익으면 영어로 번역하려고 합니다. 혹은 관련 PR도 환영합니다!
저도 Option을 먼저 소개하는 것이 더 나아 보이네요. 수정하겠습니다.

오오. 신기하네요!!
링크주신 한글 문서 예시 코드에 수정사항이 있습니다.

from fieldenum import fieldenum, Variant, Unit, unreachable  
from fieldenum.enums import Option  
  
def hello() -> Option:  # GOOD  
    return Option.Some("hello")  
  
def print_hello(option: Option):  # GOOD  
    print(value.unwrap()) #!!!!! 여기에 value가 아니라 option 이어야 할것 같아 보이네요 !!!!!#  
  
value = hello()  
print_hello(value)  

알려주셔서 감사합니다. 수정했습니다!

Show GN으로 올려야 하는데 실수로 일반으로 올렸네요;;

수정해두었습니다.