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.
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.
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
Mit pip installieren
pip install pyserde
Datenklassen wurden zu Python 3.7 hinzugefügt, aber 3.6 verwendet jetzt den Datenklassen-Backport in PyPI.
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.
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.
from serde.yaml import from_yaml, to_yaml
print(to_yaml(f))
print(from_yaml(Foo, s))
from serde.toml import from_toml, to_toml
print(to_toml(f))
print(from_toml(Foo, s))
from serde.msgpack import from_msgpack, to_msgpack
print(to_msgpack(f))
print(from_msgpack(Foo, s))
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.
raw
: Handschriftliche Serialisierung / Deserialisierung.Datenklasse
: Verwenden Sie ein Uuple, das von Datenklassen abhängig istpyserde
: Diese Bibliothek (Github 7 ⭐)dacite
:Simplecreationofdataclassesfromdictionaries.(Github447️⭐)mashumaro
:Fastandwelltestedserializationframeworkontopofdataclasses.(Github131️⭐)marshallow
:Alightweightlibraryforconvertingcomplexobjectstoandfromsimpledatatypes.(Github4668⭐)attrs
:PythonClassesWithoutBoilerplate.(Github3076⭐)cattrs
:Complexcustomclassconvertersforattrs.(Github214⭐)Ich habe mehrere andere Bibliotheken verglichen, aber ich habe sie nicht in das Diagramm aufgenommen, weil sie unvergleichlich langsam waren.
dataclass-json
:EasilyserializeDataClassestoandfromJSON(Github367⭐)
dataclasses_jsonschema
:JSONschemagenerationfromdataclasses(Github72⭐)
pavlova
:Apythondeserialisationlibrarybuiltontopofdataclasses(Github28⭐)
Die Anzahl der Github-Sterne ist ab dem 21. Mai 2020.
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"}
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.
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pyserde = "~=0.1"
flask = "~=1.1"
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