[PYTHON] Présentation de pyserde, un framework de sérialisation qui utilise la classe de données

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.

Motivation pour commencer à faire

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.

Origine du nom

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

Installation

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.

Définition de classe

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.

Essayez de sérialiser et de désérialiser

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.

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

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.

J'ai comparé plusieurs autres bibliothèques, mais je ne les ai pas mises sur le graphique car elles étaient incomparablement lentes.

Autres fonctions utiles

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"}

Exemple d'application

Lire le fichier de paramètres

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.

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', '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

Recommended Posts

Présentation de pyserde, un framework de sérialisation qui utilise la classe de données
Présentation de JustPy, un framework Web de haut niveau qui ne nécessite pas de programmation frontale