10 puntos por ilotoki0804 2024-06-01 | 14 comentarios | Compartir por WhatsApp
  • fieldenum es un enum con valores (que se puede instanciar).
  • Ofrece soporte limpio para enums con campos al estilo Rust.
  • Busca equilibrar la pureza de la programación funcional con la practicidad en Python.
  • Incluye por defecto Option como alternativa a None y BoundResult como alternativa a las excepciones.
  • Está completamente probado.
  • La documentación en inglés todavía es limitada, pero planean seguir ampliándola gradualmente.
  • Son bienvenidas todas las formas de apoyo, como issues, PRs, stars y más.

14 comentarios

 
savvykang 2024-06-02

Me pregunto si no sería mejor usar tipos union con dataclass; salvo que la declaración es más corta, no le veo muy claras las ventajas. ¿Hay algún punto en el que fieldenum sea especialmente mejor?

 
ilotoki0804 2024-06-03

La gran ventaja también es que la declaración es corta, concisa y solo incluye lo necesario.
Por ejemplo,

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)  

Si quisieras implementar el fieldenum de arriba con dataclass, tendrías que escribirlo así.

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  

El código se hace más largo y más difícil de leer, aumentan las posibilidades de equivocarse y tampoco da la impresión de ser un código limpio, ¿no?

Por supuesto, incluso si lo escribes así, no podrás obtener muchas de las otras funciones que ofrece fieldenum (genéricos, repr, __fields__, ...).

Por eso, tener un fieldenum que implemente y reúna todo esto resulta mucho más conveniente.

Además, también vale la pena revisar el contenido de la sección de ejemplos.

 
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 admite una implementación de repr de forma predeterminada.
  2. dataclasses.fields proporciona información en tiempo de ejecución sobre la definición de los campos.
  3. Los genéricos son compatibles desde 3.5 a través del módulo typing, y el azúcar sintáctico desde 3.12.
  4. En el caso del espacio de nombres Messages, se puede implementar con un módulo.

Aun así, el hecho de que no haya código boilerplate necesario para definir la clase, y que se puedan usar enum y class con una sola interfaz, sí podría ser una ventaja. Gracias por la explicación detallada.

 
savvykang 2024-06-03

https://stackoverflow.com/a/47784683

Ha habido varios intentos de expresar estructuras de esta manera, pero al final parece que puede verse como una limitación y una desventaja de Python. Conocí los ADT (algebraic data type) por primera vez en clases de la universidad con OCaml, y da un poco de lástima que en el trabajo solo se pueda imitar algo así de esta forma.

Creo que la biblioteca creada por ilotoki es probablemente el caso más cercano a un ADT. Ojalá algún día se incluya en la biblioteca estándar y llegue a usarse ampliamente.

 
ilotoki0804 2024-06-03

Si la implementación de Message se hiciera con un Union, no se podría aprovechar la herencia de métodos. Por ejemplo,

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

Si se agrega un método .process como arriba, se puede usar el método .process() en todas las variantes.

# El método Message.process() se puede usar en cada variante  
Message.Quit.process()  
Message.Move(x=123, y=456).process()  
Message.Write("hello, world").process()  
Message.ChangeColor(123, 000, 89).process()  

Además, el repr que expliqué se refiere al "repr como variante de ese enum".
Por ejemplo, si se llama a repr envolviendo fieldenum, se ejecuta así.

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

Si no hay un __repr__ personalizado, no se expresa el hecho de que sea una subvariante del enum Message.

Quit es una variante unitaria y se usa sin llamarla.

Message.Quit  # Se puede usar sin una llamada aparte (por ejemplo, `Message.Quit()`)  

Además, en el caso de las variantes sin campos que sí requieren llamada, se pueden comprobar como singleton con el operador is.

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

Usar fieldenum ayuda a encargarse automáticamente de varios detalles de implementación que son fáciles de pasar por alto.

 
wyatt216 2024-06-02

Quisiera sugerirles si podrían dar una charla en PyCon Korea. Me pareció interesantísimo y me gustaría escuchar directamente la historia y la explicación del proceso de creación.

 
ilotoki0804 2024-06-02

Sería realmente un honor poder presentarlo en PyCon. No sé si será algo que pueda lograr solo porque yo quiera hacerlo(^^;), pero lo voy a pensar.

 
kayws426 2024-06-01

Y también estaría bien que en el README en inglés se explicara el ejemplo de Option.
Option es fácil de entender y permite un acercamiento más familiar. Incluso podría ser mejor explicar primero Option en el orden de la documentación.

 
ilotoki0804 2024-06-01

La documentación en inglés todavía no está lista, así que está un poco incompleta... Planeo traducirla al inglés cuando la documentación en coreano esté lo suficientemente madura. ¡O también son bienvenidos los PR relacionados!
A mí también me parece mejor presentar primero Option. Lo corregiré.

 
kayws426 2024-06-01

Oh, oh. ¡Qué curioso!
Hay una corrección en el código de ejemplo de la documentación en coreano del enlace que compartiste.

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()) #!!!!! aquí parece que debería ser option y no value !!!!!#  
  
value = hello()  
print_hello(value)  
 
ilotoki0804 2024-06-01

¡Gracias por avisar! ¡Ya lo corregí!

 
ilotoki0804 2024-06-01

Debí publicarlo con Show GN, pero por error lo publiqué como uno normal;;

 
moderator 2024-06-01

Ya fue corregido.

 
ilotoki0804 2024-06-01

¡Gracias~