10 Punkte von ilotoki0804 2024-06-01 | 14 Kommentare | Auf WhatsApp teilen
  • fieldenum ist ein Enum mit Werten (das instanziiert werden kann).
  • Unterstützt auf elegante Weise Rust-Enums mit Feldern.
  • Es versucht, ein Gleichgewicht zwischen der Reinheit der funktionalen Programmierung und der Praxistauglichkeit in Python zu finden.
  • Standardmäßig werden Option als Alternative zu None und BoundResult als Alternative zu Ausnahmen unterstützt.
  • Es ist vollständig getestet.
  • Die englische Dokumentation ist noch dürftig, soll aber nach und nach ausgebaut werden.
  • Unterstützung in verschiedenster Form wie Issues, PRs und Stars ist jederzeit willkommen.

14 Kommentare

 
savvykang 2024-06-02

Ich frage mich, ob ein Union-Typ mit dataclass nicht besser wäre; abgesehen von der kürzeren Deklaration sehe ich ehrlich gesagt kaum Vorteile. Gibt es Punkte, in denen fieldenum besonders überlegen ist?

 
ilotoki0804 2024-06-03

Dass die Deklaration kurz, prägnant und auf das Nötige beschränkt ist, ist ebenfalls ein großer Vorteil.
Zum Beispiel:

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)  

Wenn man das obige fieldenum mit dataclass implementieren möchte, muss man es folgendermaßen schreiben.

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  

Der Code wird länger, schwerer lesbar, fehleranfälliger und wirkt auch nicht besonders sauber, oder?

Selbst wenn man es so schreibt, stehen zudem viele weitere Funktionen von fieldenum (Generics, repr, __fields__, ...) nicht zur Verfügung.

Daher ist ein fieldenum, das all das bereits implementiert und bündelt, deutlich praktischer.

Außerdem lohnt sich ein Blick in den Abschnitt Beispiele.

 
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 unterstützt standardmäßig eine repr-Implementierung.
  2. dataclasses.fields stellt Laufzeitinformationen über Felddefinitionen bereit.
  3. Generics werden seit 3.5 durch das Modul typing unterstützt, syntaktischer Zucker seit 3.12.
  4. Im Fall des Namensraums Messages ist eine Implementierung als Modul möglich.

Trotzdem kann es ein Vorteil sein, dass kein Boilerplate-Code für die Klassendefinitionen nötig ist und dass sich Enum und Klasse über eine einheitliche Schnittstelle verwenden lassen. Vielen Dank für die ausführliche Erklärung.

 
savvykang 2024-06-03

https://stackoverflow.com/a/47784683

Es hat bereits verschiedene Versuche gegeben, Strukturen auf diese Weise auszudrücken, aber letztlich scheint das eine Grenze und zugleich ein Nachteil von Python zu sein. Mit ADT (algebraic data type) bin ich im Studium erstmals in einer OCaml-Vorlesung in Berührung gekommen; ein wenig schade ist es schon, dass man so etwas bei der Arbeit nur auf diese Weise nachahmen kann.

Die von ilotoki entwickelte Bibliothek dürfte wohl das Beispiel sein, das einem ADT am nächsten kommt. Es wäre schön, wenn sie irgendwann in die Standardbibliothek aufgenommen und breit genutzt würde.

 
ilotoki0804 2024-06-03

Wenn die Implementierung von Message als Union erfolgt, kann man keine Methodenvererbung nutzen. Zum Beispiel

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

Wenn man wie oben die Methode .process hinzufügt, kann die Methode .process() für alle Varianten verwendet werden.

# Die Methode Message.process() ist in jeder Variante verfügbar  
Message.Quit.process()  
Message.Move(x=123, y=456).process()  
Message.Write("hello, world").process()  
Message.ChangeColor(123, 000, 89).process()  

Außerdem meinte ich mit dem von mir beschriebenen repr das „repr als Variante dieses Enums“.
Wenn man zum Beispiel repr auf fieldenum-Weise umschlossen aufruft, wird es wie folgt ausgeführt.

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

Ohne ein benutzerdefiniertes __repr__ wird nicht dargestellt, dass es sich um eine Untervariante des Message-Enums handelt.

Quit ist eine Unit-Variante und wird ohne Aufruf verwendet.

Message.Quit  # kann ohne separaten Aufruf (z. B. `Message.Quit()`) verwendet werden  

Außerdem kann man bei feldlosen Varianten, also Variantentypen, die per Aufruf verwendet werden müssen, als Singleton mit dem Operator is prüfen.

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

Mit fieldenum lassen sich so verschiedene leicht zu übersehende Implementierungsdetails automatisch mit abdecken.

 
wyatt216 2024-06-02

Darf ich vielleicht vorschlagen, dass Sie dazu auf der PyCon Korea einen Vortrag halten? Ich fand das unglaublich spannend und würde die Geschichten und Erklärungen aus dem Entstehungsprozess gern direkt von Ihnen hören!

 
ilotoki0804 2024-06-02

Es wäre wirklich eine Ehre, auf der PyCon einen Vortrag halten zu dürfen. Ich weiß allerdings nicht, ob das allein deshalb möglich ist, weil ich es gerne möchte (^^;), aber ich werde darüber nachdenken.

 
kayws426 2024-06-01

Außerdem wäre es schön, wenn im englischen README auch ein Option-Beispiel erklärt würde.
Option ist leicht verständlich und dürfte einen vertrauten, niedrigschwelligen Einstieg ermöglichen. Vielleicht wäre es in der Reihenfolge der Dokumentation sogar besser, Option zuerst zu erklären.

 
ilotoki0804 2024-06-01

Die englische Dokumentation ist noch nicht fertig und daher etwas dürftig ... Sobald die koreanische Dokumentation ausreichend ausgereift ist, möchte ich sie ins Englische übersetzen. Oder entsprechende PRs sind ebenfalls willkommen!
Auch für mich wirkt es besser, zuerst Option einzuführen. Ich werde das anpassen.

 
kayws426 2024-06-01

Oh, interessant!!
Im Beispielcode der verlinkten koreanischen Dokumentation gibt es eine Korrektur.

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()) #!!!!! Hier sollte es offenbar nicht value, sondern option heißen !!!!!#  
  
value = hello()  
print_hello(value)  
 
ilotoki0804 2024-06-01

Vielen Dank für den Hinweis. Ich habe es korrigiert!

 
ilotoki0804 2024-06-01

Das hätte ich als Show GN posten sollen, aber ich habe es aus Versehen als normalen Beitrag eingestellt;;

 
moderator 2024-06-01

Ich habe es korrigiert.

 
ilotoki0804 2024-06-01

Vielen Dank~