[PYTHON] Einführung von pyserde, einem Serialisierungsframework, das Datenklassen verwendet

Wir entwickeln ein Serialisierungsframework, das eine Datenklasse namens pyserde verwendet. Wie zu lesen ist Paiselde.

TL;DR

Fügen Sie der normal definierten Datenklasse die Dekoratoren "@ serialize", "@ deserialize" hinzu

@deserialize
@serialize
@dataclass
class Foo:
    i: int
    s: str
    f: float
    b: bool

Serialisieren Sie dann mit to_json nach JSON.

>> h = Foo(i=10, s='foo', f=100.0, b=True)
>> print(f"Into Json: {to_json(h)}")
Into Json: {"i": 10, "s": "foo", "f": 100.0, "b": true}

Sie können von JSON zu einem Objekt mit from_json serialisieren.

>> s = '{"i": 10, "s": "foo", "f": 100.0, "b": true}'
>> print(f"From Json: {from_json(Foo, s)}")
From Json: Foo(i=10, s='foo', f=100.0, b=True)

Zusätzlich zu JSON werden MsgPack, YAML und Toml unterstützt. Es gibt verschiedene andere Funktionen.

Motivation zu machen

Ich habe Implementierung gelesen, weil ich dachte, dass die zu Python 3.7 hinzugefügte Datenklasse nützlich wäre. Wenn die angehängte Klasse in das Modul geladen wird, verwenden Sie die exec-Funktion, um die Methode zu generieren /cpython/blob/550f30c8f33a2ba844db2ce3da8a897b3e882c9a/Lib/dataclasses.py#L377-L401) Ich habe gefunden. Ah, ich fand das interessant, und als ich die Leistung von \ _ \ _ init \ _ \ _, \ _ \ _ repr \ _ \ _, \ _ \ _ eq \ _ \ _ maß, war das Ergebnis fast das gleiche wie bei der handschriftlichen Klasse. Ich dachte, dass es möglich wäre, damit mehr Methoden zu generieren.

Beispiel für die Definition einer Funktion zur Laufzeit mit exec:

#Definieren Sie eine Funktion mit einer Zeichenfolge
s = '''
def func():
    print("Hello world!")
'''

#Übergeben Sie eine Zeichenfolge an exec
exec(s)

#Funktionen werden zur Laufzeit definiert!
func()

Informationen zum Mechanismus und zur Leistung der Datenklasse finden Sie unter Erläuterung hier.

Ursprung des Namens

Rust hat ein Serialisierungsframework namens serde. Dieser Serde ist sowieso ein Gott, und ich persönlich denke, dass er ungefähr 20% der Gründe ausmacht, warum Rust großartig ist.

Ich wollte ein praktisches, leistungsstarkes und flexibles Framework wie serde in Python erstellen, deshalb habe ich es pyserde genannt.

Getting started

Installation

Mit pip installieren

pip install pyserde

Datenklassen wurden zu Python 3.7 hinzugefügt, aber 3.6 verwendet jetzt den Datenklassen-Backport in PyPI.

Klassendefinition

Lassen Sie uns eine Klasse wie diese machen. Es ist nur eine Datenklasse, aber es hat den von pyserde bereitgestellten Dekorator "@ serialize" und "@ deserialize".

from serde import serialize, deserialize
from dataclasses import dataclass

@deserialize
@serialize
@dataclass
class Foo:
    i: int
    s: str
    f: float
    b: bool

Wenn Sie "@ serialize" hinzufügen, generiert pyserde eine Serialisierungsmethode, und wenn Sie "@ deserialize" hinzufügen, wird eine Deserialisierungsmethode generiert. Die Methodengenerierung wird nur einmal aufgerufen, wenn die Klasse in den Python-Interpreter geladen wird (Pyserde- oder Decorator-Verhalten), sodass bei der tatsächlichen Verwendung der Klasse kein Overhead entsteht.

Versuchen Sie zu serialisieren und zu deserialisieren

Lassen Sie uns nun tatsächlich serialisieren und deserialisieren. pyserde unterstützt JSON, Yaml, Toml, MsgPack ab 0.1.1. Hilfsfunktionen für jedes Format befinden sich im Modul serde. <Formatname> und haben dieselben Namenskonventionen.

Im Fall von JSON wird dies beispielsweise so.

from serde.json import from_json, to_json

Rufen Sie to_json auf, um das Foo-Objekt in JSON zu serialisieren

f = Foo(i=10, s='foo', f=100.0, b=True)
print(to_json(f))

Geben Sie beim Serialisieren mit einfach die JSON-Zeichenfolge im zweiten Argument der Klasse Foo im ersten Argument von from_json an.

s = '{"i": 10, "s": "foo", "f": 100.0, "b": true}'
print(from_json(Foo, s))

Im Fall von Yaml, Toml, MsgPack sieht es so aus.

Yaml
from serde.yaml import from_yaml, to_yaml
print(to_yaml(f))
print(from_yaml(Foo, s))
Toml
from serde.toml import from_toml, to_toml
print(to_toml(f))
print(from_toml(Foo, s))
MsgPack
from serde.msgpack import from_msgpack, to_msgpack
print(to_msgpack(f))
print(from_msgpack(Foo, s))

Performance

Die Ausführungszeit wurde unter den folgenden Bedingungen gemessen und mit anderen Serialisierungsbibliotheken verglichen. Informationen zum Messcode finden Sie unter hier.

Serialisieren / Deserialisieren Sie jeweils 10.000 Mal zu JSON

Serialize Deserialize

Konvertierung in Tupel / Diktat jeweils 10.000 Mal

astuple asdict

Im Diagramm ist die horizontale Achse das Vergleichsziel und die vertikale Achse die Latenz. Je niedriger dieses Balkendiagramm ist, desto besser ist die Leistung. Wie Sie der Tabelle entnehmen können, ist die Leistung von pyserde nach handgeschriebenem Rohmaterial an zweiter Stelle. Es scheint, dass der Unterschied in der Leistung darin besteht, dass der Code von "raw" weniger Funktionsaufrufe als pyserde hat.

Die Vergleichsziele sind unten aufgeführt.

Ich habe mehrere andere Bibliotheken verglichen, aber ich habe sie nicht in das Diagramm aufgenommen, weil sie unvergleichlich langsam waren.

Andere nützliche Funktionen

Es ist vollständig eine Kopie des Original-Serdes, hat aber die folgenden nützlichen Funktionen.

Case Conversion

Konvertieren Sie snake_case in camelCase, kebab-case usw.

@serialize(rename_all = 'camelcase')
@dataclass
class Foo:
    int_field: int
    str_field: str

f = Foo(int_field=10, str_field='foo')
print(to_json(f))

snake_case ist jetzt camelCase.

'{"intField": 10, "strField": "foo"}'

Rename Field

Dies ist nützlich, wenn Sie Schlüsselwörter wie "Klasse" im Feldnamen verwenden möchten.

@serialize
@dataclass
class Foo:
    class_name: str = field(metadata={'serde_rename': 'class'})

print(to_json(Foo(class_name='Foo')))

Der Feldname der Klasse lautet "Klassenname", aber JSON ist jetzt "Klasse".

{"class": "Foo"}

Skip

Sie können es von der Serialisierung / Deserialisierung ausschließen, indem Sie dem Feld "serde_skip" hinzufügen.

@serialize
@dataclass
class Resource:
    name: str
    hash: str
    metadata: Dict[str, str] = field(default_factory=dict, metadata={'serde_skip': True})

resources = [
    Resource("Stack Overflow", "hash1"),
    Resource("GitHub", "hash2", metadata={"headquarters": "San Francisco"}) ]
print(to_json(resources))

Das Feld "Metadaten" wurde ausgeschlossen.

[{"name": "Stack Overflow", "hash": "hash1"}, {"name": "GitHub", "hash": "hash2"}]

Conditional Skip

Wenn Sie durch die angegebene Bedingung ausschließen möchten, können Sie den bedingten Ausdruck an "serde_skip_if" übergeben.

@serialize
@dataclass
class World:
    player: str
    buddy: str = field(default='', metadata={'serde_skip_if': lambda v: v == 'Pikachu'})

world = World('satoshi', 'Pikachu')
print(to_json(world))

world = World('green', 'Charmander')
print(to_json(world))

Das Feld "Kumpel" wird jetzt nur ausgeschlossen, wenn es "Pikachu" ist.

{"player": "satoshi"}
{"player": "green", "buddy": "Charmander"}

Anwendungsbeispiel

Einstellungsdatei lesen

Ich denke, Sie verwenden häufig Yaml oder Toml für die Konfigurationsdatei Ihrer Anwendung. Mit pyserde können Sie ganz einfach Ihre Konfigurationsdatei Ihrer Klasse zuordnen.

from dataclasses import dataclass
from serde import deserialize
from serde.yaml import from_yaml


@deserialize
@dataclass
class App:
    addr: str
    port: int
    secret: str
    workers: int


def main():
    with open('app.yml') as f:
        yml = f.read()
    cfg = from_yaml(App, yml)
    print(cfg)

JSON WebAPI

Die JSON-Web-API ist mit Flask recht einfach zu implementieren, aber mit pyserde ist es einfach, sie Ihren eigenen Typen zuzuordnen.

Pipfile
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[packages]
pyserde = "~=0.1"
flask = "~=1.1"
app.py
from dataclasses import dataclass
from flask import Flask, request, Response
from serde import serialize, deserialize
from serde.json import to_json, from_json

@deserialize
@serialize
@dataclass
class ToDo:
    id: int
    title: str
    description: str
    done: bool

app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/todos', methods=['GET', 'POST'])
def todos():
    print(request.method)
    if request.method == 'GET':
        body = to_json([ToDo(1, 'Play games', 'Spielen Sie Holy Sword Legend 3', False)])
        return Response(body, mimetype='application/json')
    else:
        todo = from_json(ToDo, request.get_data())
        return f'A new ToDo {todo} successfully created.'

if __name__ == '__main__':
    app.run(debug=True)

pipenv install
pipenv run python app.py
$ curl http://localhost:5000/todos
[{"id": 1, "title": "Play games", "description": "Spielen Sie Holy Sword Legend 3", "done": false}]⏎
$ curl -X POST http://localhost:5000/todos -d '{"id": 1, "title": "Play games", "description": "Spielen Sie Holy Sword Legend 3", "done": false}'
A new ToDo ToDo(id=1, title='Play games', description='Spielen Sie Holy Sword Legend 3', done=False) successfully created.⏎

RPC

Leider kann ich Ihnen den Code nicht anzeigen, aber mein Unternehmen verfügt über ein eigenes RPC-Framework und verwendet Pyserde, um ihn in ein MsgPack für Nachrichten zu serialisieren.

References

Recommended Posts

Einführung von pyserde, einem Serialisierungsframework, das Datenklassen verwendet
Einführung von JustPy, einem High-Level-Webframework, für das keine Front-End-Programmierung erforderlich ist