10 points par ilotoki0804 2024-06-01 | 14 commentaires | Partager sur WhatsApp
  • fieldenum est un enum avec valeurs (instanciable).
  • Il prend en charge proprement les enums avec champs de Rust.
  • Il cherche à trouver un équilibre entre la pureté de la programmation fonctionnelle et le pragmatisme de Python.
  • Il fournit par défaut Option, une alternative à None, ainsi que BoundResult, une alternative aux exceptions.
  • Il est entièrement testé.
  • La documentation en anglais est encore limitée, mais elle sera progressivement enrichie.
  • Tous les types de soutien sont les bienvenus, qu’il s’agisse d’issues, de PR, de stars, etc.

14 commentaires

 
savvykang 2024-06-02

Je me demande si un type union avec dataclass ne serait pas préférable ; à part une déclaration plus courte, je vois mal les avantages. Y a-t-il un point sur lequel fieldenum est particulièrement meilleur ?

 
ilotoki0804 2024-06-03

Le fait que la déclaration soit courte, concise et ne contienne que l’essentiel constitue aussi un grand avantage.
Par exemple,

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 l’on voulait implémenter le fieldenum ci-dessus avec dataclass, il faudrait écrire quelque chose comme ceci.

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  

Le code devient plus long, plus difficile à lire, le risque d’erreur augmente, et on n’a pas vraiment l’impression d’un code propre, n’est-ce pas ?

Bien sûr, même en l’écrivant ainsi, on ne peut pas bénéficier d’autres fonctionnalités fournies par fieldenum (génériques, repr, __fields__, ...).

Avoir fieldenum, qui implémente et regroupe tout cela, est donc bien plus pratique.

Par ailleurs, il peut aussi être utile de consulter le contenu de la section exemples.

 
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 prend en charge par défaut l’implémentation de repr
  2. dataclasses.fields fournit des informations à l’exécution sur la définition des champs
  3. Les génériques sont pris en charge depuis la 3.5 via le module typing, et la syntaxe simplifiée depuis la 3.12
  4. Dans le cas de l’espace de noms Messages, une implémentation sous forme de module est possible

Malgré cela, l’absence de code boilerplate nécessaire pour définir les classes, ainsi que la possibilité d’utiliser enum et class via une interface unique, peuvent clairement constituer des avantages. Merci pour cette explication détaillée.

 
savvykang 2024-06-03

https://stackoverflow.com/a/47784683

Il y a eu plusieurs tentatives pour représenter des structures de cette manière, mais au final, on peut sans doute y voir une limite, et même un point faible, de Python. J’ai découvert les ADT (algebraic data types) pour la première fois en cours, avec OCaml, et je trouve un peu dommage que, dans le travail, on doive se contenter de les imiter de cette façon.

La bibliothèque créée par ilotoki semble être le cas qui se rapproche le plus d’un ADT. Ce serait bien qu’elle soit un jour intégrée à la bibliothèque standard et largement utilisée.

 
ilotoki0804 2024-06-03

Si l’implémentation de Message se fait avec une Union, il n’est pas possible de tirer parti de l’héritage de méthodes. Par exemple,

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 l’on ajoute une méthode .process comme ci-dessus, on peut utiliser la méthode .process() sur tous les variants.

# La méthode Message.process() peut être utilisée sur chaque variant  
Message.Quit.process()  
Message.Move(x=123, y=456).process()  
Message.Write("hello, world").process()  
Message.ChangeColor(123, 000, 89).process()  

Par ailleurs, le repr dont j’ai parlé signifie le « repr en tant que variant de cet enum ». Par exemple, si l’on appelle repr en l’enveloppant avec fieldenum, cela s’exécute comme suit.

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

Sans __repr__ personnalisé, le fait qu’il s’agisse d’un sous-variant de l’enum Message n’est pas représenté.

Quit est un unit variant qui s’utilise sans appel.

Message.Quit  # utilisable sans appel distinct (par ex. `Message.Quit()`)  

De plus, dans le cas des fieldless variants, qui sont un type de variant nécessitant un appel, on peut les vérifier comme des singletons avec l’opérateur is.

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

L’utilisation de fieldenum aide ainsi à prendre automatiquement en charge divers détails d’implémentation faciles à manquer.

 
wyatt216 2024-06-02

Je me permets de vous proposer de faire une présentation à la PyCon Corée. J’ai trouvé ça vraiment passionnant, et j’aimerais beaucoup entendre directement votre récit et vos explications sur le processus de création !

 
ilotoki0804 2024-06-02

Ce serait vraiment un honneur de pouvoir faire une présentation à la PyCon. Je ne sais pas si le simple fait que j’en aie envie suffira à y parvenir(^^;), mais je vais y réfléchir.

 
kayws426 2024-06-01

Et il serait bien que l'exemple d'Option soit aussi expliqué dans le README en anglais.
Option est facile à comprendre et permet une approche plus familière. Je pense aussi qu'il serait peut-être préférable de présenter Option en premier dans l'ordre des explications de la documentation.

 
ilotoki0804 2024-06-01

La documentation en anglais n'est pas encore prête, donc elle reste un peu succincte... Je compte la traduire en anglais une fois que la documentation en coréen sera suffisamment aboutie. Ou bien les PR associées sont aussi les bienvenues !
Je suis aussi d'avis qu'il vaut mieux présenter Option en premier. Je vais corriger cela.

 
kayws426 2024-06-01

Oh oh. C’est étonnant !!
Il y a une correction à apporter dans le code d’exemple de la documentation coréenne liée.

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()) #!!!!! ici, il semble que cela devrait être option et non value !!!!!#  
  
value = hello()  
print_hello(value)  
 
ilotoki0804 2024-06-01

Merci de l’avoir signalé. C’est corrigé !

 
ilotoki0804 2024-06-01

J’aurais dû le publier sur Show GN, mais je l’ai mis par erreur dans la catégorie générale ;;

 
moderator 2024-06-01

J’ai effectué la correction.

 
ilotoki0804 2024-06-01

Merci ~