10 điểm bởi ilotoki0804 2024-06-01 | 14 bình luận | Chia sẻ qua WhatsApp
  • fieldenum là enum có mang giá trị (có thể khởi tạo instance).
  • Hỗ trợ gọn gàng enum có trường của Rust.
  • Được xây dựng để cân bằng giữa tính thuần túy của lập trình hàm và tính thực dụng trong Python.
  • Mặc định hỗ trợ Option như một lựa chọn thay thế cho NoneBoundResult như một lựa chọn thay thế cho ngoại lệ.
  • Đã được kiểm thử đầy đủ.
  • Tài liệu tiếng Anh hiện vẫn còn hạn chế, nhưng có kế hoạch bổ sung dần.
  • Hoan nghênh mọi hình thức hỗ trợ như issue, PR, star, v.v.

14 bình luận

 
savvykang 2024-06-02

Tôi thấy kiểu union của dataclass có vẻ tốt hơn thì phải; ngoài việc câu lệnh khai báo ngắn hơn ra, tôi không thấy rõ ưu điểm lắm. fieldenum có điểm gì vượt trội hơn một cách đặc biệt không?

 
ilotoki0804 2024-06-03

Ưu điểm lớn nữa là phần khai báo ngắn gọn, súc tích và chỉ chứa những gì cần thiết.
Ví dụ,

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)  

Nếu triển khai fieldenum ở trên bằng dataclass thì sẽ phải viết như sau.

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  

Mã dài hơn, khó nhìn hơn, khả năng mắc lỗi cũng cao hơn, và rõ ràng không mang lại cảm giác mã nguồn gọn gàng, đúng không?

Tất nhiên, ngay cả khi viết như vậy thì bạn vẫn không thể nhận được nhiều tính năng khác mà fieldenum cung cấp (generic, repr, __fields__, ...).

Vì vậy, sẽ tiện lợi hơn rất nhiều nếu có fieldenum đã triển khai và gom sẵn toàn bộ những thứ này.

Ngoài ra, bạn cũng nên tham khảo nội dung trong phần ví dụ.

 
savvykang 2024-06-03
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 về cơ bản hỗ trợ triển khai repr mặc định
  2. dataclasses.fields cung cấp thông tin thời gian chạy về định nghĩa các field
  3. Generic được hỗ trợ từ 3.5 thông qua mô-đun typing, còn syntactic sugar được hỗ trợ từ 3.12
  4. Trong trường hợp không gian tên Messages, có thể triển khai bằng mô-đun

Dù vậy, việc không cần mã boilerplate để định nghĩa class, cùng với khả năng dùng enum và class dưới một giao diện thống nhất, có lẽ vẫn là những ưu điểm đáng giá. Cảm ơn vì phần giải thích chi tiết.

 
savvykang 2024-06-03

https://stackoverflow.com/a/47784683

Đã có nhiều nỗ lực nhằm biểu diễn cấu trúc theo kiểu này, nhưng rốt cuộc có lẽ đây có thể xem là một giới hạn và cũng là nhược điểm của Python. Tôi lần đầu tiếp xúc với ADT (algebraic data type) trong giờ học ở trường qua OCaml, nên cũng có chút tiếc nuối khi lúc đi làm lại chỉ có thể mô phỏng theo kiểu này.

Có lẽ thư viện do ilotoki tạo ra là ví dụ gần với ADT nhất. Sẽ thật tuyệt nếu một ngày nào đó nó được đưa vào thư viện chuẩn và được sử dụng rộng rãi.

 
ilotoki0804 2024-06-03

Nếu việc triển khai Message được thực hiện bằng Union thì không thể tận dụng kế thừa phương thức. Ví dụ:

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):  
        ...  

Nếu thêm phương thức .process như trên, thì có thể dùng phương thức .process() cho tất cả các variant.

# Có thể dùng phương thức Message.process() trên từng variant  
Message.Quit.process()  
Message.Move(x=123, y=456).process()  
Message.Write("hello, world").process()  
Message.ChangeColor(123, 000, 89).process()  

Ngoài ra, repr mà tôi giải thích là "repr với tư cách là variant của enum đó". Ví dụ, khi dùng fieldenum để bao bọc lời gọi repr, nó sẽ chạy như sau.

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

Nếu không có __repr__ tùy chỉnh, thì sẽ không thể hiện được việc nó là variant con của enum Message.

Quit là unit variant nên được dùng mà không cần gọi.

Message.Quit  # Có thể dùng mà không cần lời gọi riêng (ví dụ: `Message.Quit()`)  

Ngoài ra, với fieldless variant là loại variant cần dùng lời gọi, có thể kiểm tra bằng toán tử is như một singleton.

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

Dùng fieldenum sẽ giúp tự động xử lý nhiều chi tiết triển khai đa dạng rất dễ bị bỏ sót như thế này.

 
wyatt216 2024-06-02

Hay là anh/chị thử cân nhắc trình bày tại PyCon Korea xem sao. Tôi thấy nội dung này cực kỳ thú vị, nên rất muốn được trực tiếp nghe câu chuyện và phần giải thích về quá trình anh/chị tạo ra nó!

 
ilotoki0804 2024-06-02

Nếu có thể được trình bày tại PyCon thì thật sự sẽ là một vinh dự lớn. Dù không biết chỉ vì tôi muốn mà có thể làm được hay không(^^;), tôi sẽ thử suy nghĩ về việc này.

 
kayws426 2024-06-01

Và sẽ tốt hơn nếu ví dụ về Option cũng được giải thích trong README tiếng Anh.
Option sẽ dễ hiểu và tạo cảm giác quen thuộc để tiếp cận hơn. Tôi cũng nghĩ sẽ tốt hơn nếu giải thích Option trước trong thứ tự trình bày của tài liệu.

 
ilotoki0804 2024-06-01

Tài liệu tiếng Anh vẫn đang được chuẩn bị nên hiện còn hơi sơ sài... Khi tài liệu tiếng Hàn đủ hoàn thiện, tôi định sẽ dịch sang tiếng Anh. Hoặc cũng rất hoan nghênh các PR liên quan!
Tôi cũng thấy việc giới thiệu Option trước sẽ tốt hơn. Tôi sẽ sửa lại.

 
kayws426 2024-06-01

Ồ. Thú vị thật!!
Trong ví dụ mã ở tài liệu tiếng Hàn mà bạn gửi có một chỗ cần sửa.

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()) #!!!!! ở đây có vẻ phải là option chứ không phải value !!!!!#  
  
value = hello()  
print_hello(value)  
 
ilotoki0804 2024-06-01

Cảm ơn bạn đã báo. Tôi đã sửa lại!

 
ilotoki0804 2024-06-01

Đáng ra phải đăng bằng Show GN, nhưng tôi lại lỡ đăng ở mục thường mất rồi;;

 
moderator 2024-06-01

Tôi đã sửa lại rồi.

 
ilotoki0804 2024-06-01

Cảm ơn~