Nous développons un cadre de sérialisation qui utilise une classe de données appelée pyserde. Comment lire est Paiselde.
TL;DR
Ajoutez les décorateurs @ serialize
, @ deserialize
à la classe de données normalement définie
@deserialize
@serialize
@dataclass
class Foo:
i: int
s: str
f: float
b: bool
Ensuite, sérialisez en JSON avec to_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}
Vous pouvez sérialiser de JSON en objet avec from_json
.
>> 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)
En plus de JSON, il prend en charge MsgPack, YAML et Toml. Il existe diverses autres fonctions.
J'ai lu Implementation parce que je pensais que la classe de données ajoutée à Python 3.7 était utile, et j'ai trouvé le décorateur @ dataclass
. Lorsque la classe attachée est chargée dans le module, utilisez la fonction exec pour [générer la méthode](https://github.com/python /cpython/blob/550f30c8f33a2ba844db2ce3da8a897b3e882c9a/Lib/dataclasses.py#L377-L401) J'ai trouvé. Ah, j'ai pensé que c'était intéressant, et quand j'ai mesuré les performances de \ _ \ _ init \ _ \ _ et \ _ \ _ repr \ _ \ _, \ _ \ _ eq \ _ \ _ , J'ai pensé qu'il serait possible de générer plus de méthodes en utilisant cela.
Exemple de définition d'une fonction lors de l'exécution avec exec:
#Définir une fonction avec une chaîne
s = '''
def func():
print("Hello world!")
'''
#Passer une chaîne à exec
exec(s)
#Les fonctions sont définies lors de l'exécution!
func()
Pour le mécanisme et les performances de la classe de données, reportez-vous à Explication here.
Rust a un framework de sérialisation appelé serde. Ce serde est de toute façon un dieu, et je pense personnellement qu'il représente environ 20% des raisons pour lesquelles Rust est génial.
Je voulais créer un framework pratique, performant et flexible comme serde en Python, alors je l'ai nommé pyserde.
Getting started
Installer avec pip
pip install pyserde
dataclasses a été ajouté à Python 3.7, mais 3.6 utilise désormais le backport des dataclasses dans PyPI.
Faisons une classe comme celle-ci. C'est juste une classe de données, mais elle a les décorateurs @ serialize
et @ deserialize
fournis par pyserde.
from serde import serialize, deserialize
from dataclasses import dataclass
@deserialize
@serialize
@dataclass
class Foo:
i: int
s: str
f: float
b: bool
Si vous ajoutez @ serialize
, pyserde générera une méthode de sérialisation, et si vous ajoutez @ deserialize
, une méthode de désérialisation sera générée. La génération de méthode n'est appelée qu'une seule fois lorsque la classe est chargée dans l'interpréteur Python (comportement pyserde ou décorateur), il n'y a donc pas de surcharge lors de l'utilisation réelle de la classe.
Maintenant, sérialisons et désérialisons. pyserde prend en charge JSON, Yaml, Toml, MsgPack à partir de la version 0.1.1. Les fonctions d'assistance pour chaque format sont dans le module serde. <Nom du format>
et ont les mêmes conventions de dénomination.
Par exemple, dans le cas de JSON, cela devient comme ça.
from serde.json import from_json, to_json
Appelez to_json
pour sérialiser l'objet Foo
en JSON
f = Foo(i=10, s='foo', f=100.0, b=True)
print(to_json(f))
Lors de la sérialisation avec, spécifiez simplement la chaîne de caractères JSON dans le deuxième argument de la classe Foo
dans le premier argument de from_json
.
s = '{"i": 10, "s": "foo", "f": 100.0, "b": true}'
print(from_json(Foo, s))
Dans le cas de Yaml, Toml, MsgPack, cela ressemble à ceci.
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))
Le temps d'exécution a été mesuré dans les conditions suivantes et comparé à d'autres bibliothèques de sérialisation. Si vous voulez voir le code pour la mesure, voir ici
Sérialiser / désérialiser en JSON 10000 fois chacun
Serialize | Deserialize |
---|---|
Conversion en Tuple / Dict 10000 fois chacun
astuple | asdict |
---|---|
Dans le graphique, l'axe horizontal est la cible de comparaison et l'axe vertical est la latence. Plus ce graphique à barres est bas, meilleures sont les performances. Comme vous pouvez le voir sur le graphique, les performances de pyserde sont en second lieu seulement par rapport à l'écriture manuscrite brute. Il semble que la différence de performances soit que le code de raw
a moins d'appels de fonction que pyserde.
Les objectifs de comparaison sont répertoriés ci-dessous.
raw
: sérialisation / désérialisation manuscrite.dataclass
: utiliser un astuple, comme un dictat de dataclassespyserde
: Cette bibliothèque (Github 7 ⭐)dacite
:Simplecreationofdataclassesfromdictionaries.(Github447️⭐)mashumaro
:Fastandwelltestedserializationframeworkontopofdataclasses.(Github131️⭐)marshallow
:Alightweightlibraryforconvertingcomplexobjectstoandfromsimpledatatypes.(Github4668⭐)attrs
:PythonClassesWithoutBoilerplate.(Github3076⭐)cattrs
:Complexcustomclassconvertersforattrs.(Github214⭐)J'ai comparé plusieurs autres bibliothèques, mais je ne les ai pas mises sur le graphique car elles étaient incomparablement lentes.
dataclass-json
:EasilyserializeDataClassestoandfromJSON(Github367⭐)
dataclasses_jsonschema
:JSONschemagenerationfromdataclasses(Github72⭐)
pavlova
:Apythondeserialisationlibrarybuiltontopofdataclasses(Github28⭐)
Le nombre d'étoiles Github est au 21 mai 2020.
C'est complètement une copie du serde original, mais il a les fonctions utiles suivantes.
Case Conversion
Convertissez snake_case
en camelCase
, kebab-case
, etc.
@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
est maintenant camelCase
.
'{"intField": 10, "strField": "foo"}'
Rename Field
Ceci est utile lorsque vous souhaitez utiliser des mots clés tels que «class» dans le nom du champ.
@serialize
@dataclass
class Foo:
class_name: str = field(metadata={'serde_rename': 'class'})
print(to_json(Foo(class_name='Foo')))
Le nom de champ de la classe est nom_classe
, mais JSON est maintenant classe
.
{"class": "Foo"}
Skip
Vous pouvez l'exclure de la sérialisation / désérialisation en ajoutant serde_skip
au champ.
@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))
Le champ «metadata» a été exclu.
[{"name": "Stack Overflow", "hash": "hash1"}, {"name": "GitHub", "hash": "hash2"}]
Conditional Skip
Si vous souhaitez exclure par la condition spécifiée, vous pouvez passer l'expression conditionnelle à serde_skip_if
.
@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))
Le champ buddy
ne sera désormais exclu que s'il est" Pikachu ".
{"player": "satoshi"}
{"player": "green", "buddy": "Charmander"}
Je pense que vous utilisez souvent Yaml ou Toml pour le fichier de configuration de votre application. Avec pyserde, vous pouvez facilement mapper de votre fichier de configuration à votre classe.
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
L'API Web JSON est assez facile à implémenter avec Flask, mais pyserde facilite la mise en correspondance avec vos propres types.
[[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', 'Jouer à 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": "Jouer à Holy Sword Legend 3", "done": false}]⏎
$ curl -X POST http://localhost:5000/todos -d '{"id": 1, "title": "Play games", "description": "Jouer à Holy Sword Legend 3", "done": false}'
A new ToDo ToDo(id=1, title='Play games', description='Jouer à Holy Sword Legend 3', done=False) successfully created.⏎
RPC
Malheureusement, je ne peux pas vous montrer le code, mais mon entreprise a son propre framework RPC, qui utilise pyserde pour sérialiser les messages dans MsgPack.
References