web-dev-qa-db-fra.com

Dictée de pickling en python

Puis-je m'attendre à ce que la représentation sous forme de chaîne du même dict picklé soit cohérente sur différentes machines/exécutions pour la même version Python? Dans la portée d'une exécution sur la même machine?

par exemple.

# Python 2.7

import pickle
initial = pickle.dumps({'a': 1, 'b': 2})
for _ in xrange(1000**2):
    assert pickle.dumps({'a': 1, 'b': 2}) == initial

Cela dépend-il de la structure réelle de mon objet dict (valeurs imbriquées, etc.)?

UPD: Le problème est - je ne peux pas réellement faire échouer le code ci-dessus dans le cadre d'une exécution (Python 2.7), quelle que soit l'apparence de mon objet dict (quelles clés/valeurs, etc.)

6
d-d

Vous ne pouvez pas dans le cas général, pour les mêmes raisons vous ne pouvez pas compter sur l'ordre du dictionnaire dans d'autres scénarios ; le décapage n'est pas spécial ici. La représentation sous forme de chaîne d'un dictionnaire est fonction de l'ordre d'itération actuel du dictionnaire, quel que soit le mode de chargement.

Votre propre petit test est trop limité, car il ne provoque aucune mutation du dictionnaire de test et n'utilise pas de clés susceptibles de provoquer des collisions. Vous créez des dictionnaires avec exactement le même code source Python. Ils produiront donc le même ordre de sortie car l'historique d'édition des dictionnaires est exactement le même et deux clés à caractère unique qui utilisent des lettres consécutives du jeu de caractères ASCII. ne sont pas susceptibles de provoquer une collision.

Même si vous testez représentations de chaîne étant égaux, vous ne testez que si leur contenu est identique (deux dictionnaires dont la représentation de chaîne diffère peuvent toujours être égaux car les mêmes paires clé-valeur, soumises à une insertion différente ordre, peut produire un ordre de sortie différent du dictionnaire).

Ensuite, le facteur le plus important dans l’ordre des itérations du dictionnaire avant cPython 3.6 est la fonction de génération de clé de hachage, qui doit être stable pendant une seule durée de vie de l’exécutable Python (ou sinon vous feriez en sorte que tous les dictionnaires soient cassés). voir le changement d'ordre du dictionnaire sur la base de différents résultats de la fonction de hachage.

Actuellement, toutes les révisions du protocole de pickling stockent les données d'un dictionnaire sous la forme d'un flux de paires clé-valeur. lors du chargement, le flux est décodé et les paires clé-valeur sont réaffectées au dictionnaire dans l'ordre sur disque, de sorte que l'ordre d'insertion est au moins stable de ce point de vue. MAIS entre différentes versions de Python, architectures de machine et configuration locale, les résultats de la fonction de hachage seront absolument différents:

  • La variable d'environnement PYTHONHASHSEED , est utilisée dans la génération de hachages pour les clés str, bytes et datetime. Le paramètre est disponible à partir de Python 2.6.8 et 3.2.3. Il est activé et défini sur random par défaut à partir de Python 3.3. Par conséquent, le paramètre varie d'une version de Python à l'autre, et peut être défini sur quelque chose de différent localement.
  • La fonction de hachage génère un entier ssize_t, un type entier signé dépendant de la plate-forme. Ainsi, différentes architectures peuvent produire des hachages différents simplement parce qu'elles utilisent une définition de type ssize_t plus grande ou plus petite.

Avec différentes sorties de fonction de hachage d'une machine à l'autre et d'une exécution de Python à une exécution de Python, vous voyez des {volonté différentes représentations sous forme de chaîne d'un dictionnaire.

Et enfin, à partir de la version 3.6 de cPython, l’implémentation du type dict est passée à un format plus compact qui tient également arrive pour préserver l’ordre d’insertion. Depuis Python 3.7, la spécification du langage a été modifiée pour rendre ce comportement obligatoire. Par conséquent, les autres implémentations Python doivent implémenter la même sémantique. Par conséquent, le décapage et le découplage entre différentes implémentations ou versions de Python antérieures à Python 3.7 peuvent également entraîner un ordre de sortie du dictionnaire différent, même si tous les autres facteurs sont égaux.

4
Martijn Pieters

Non vous ne pouvez pas. Cela dépend de beaucoup de choses, y compris les valeurs de clé, l'état de l'interpréteur et la version de Python.

Si vous avez besoin d'une représentation cohérente, envisagez d'utiliser JSON avec une forme canonique.

MODIFIER

Je ne sais pas trop pourquoi les gens votent pour cela sans aucun commentaire, mais je vais clarifier.

pickle n'est pas destiné à produire des représentations fiables, son sérialiseur pur (non lisible par une machine).

La compatibilité ascendante/descendante de la version Python est une chose, mais elle s'applique uniquement à la possibilité de désérialiser l'objet identique inside de l'interpréteur - c'est-à-dire que lorsque vous dumpez une version et que vous en chargez une autre, il est garanti que le même comportement des mêmes interfaces publiques . Ni la représentation textuelle sérialisée ni la structure de la mémoire interne ne prétendaient être identiques (et IIRC, il ne l’a jamais fait).

Le moyen le plus simple de vérifier cela consiste à exporter les mêmes données dans des versions avec des différences significatives en termes de traitement de structure et/ou de traitement des semences tout en maintenant vos clés hors de la plage mise en cache (pas d'entiers courts ni de chaînes):

Python 3.5.6 (default, Oct 26 2018, 11:00:52) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> d = {'first_string_key': 1, 'second_key_string': 2}
>>> pickle.dump
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x11\x00\x00\x00second_key_stringq\x01K\x02X\x10\x00\x00\x00first_string_keyq\x02K\x01u.'

Python 3.6.7 (default, Oct 26 2018, 11:02:59) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> d = {'first_string_key': 1, 'second_key_string': 2}
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x10\x00\x00\x00first_string_keyq\x01K\x01X\x11\x00\x00\x00second_key_stringq\x02K\x02u.'
2
Slam

Les dictionnaires Python2 ne sont pas ordonnés; l'ordre dépend des valeurs de hachage des clés comme expliqué dans cet excellent answer de Martijn Pieters. Je ne pense pas que vous puissiez utiliser un dict ici, mais vous pouvez utiliser un OrderedDict (requiert Python 2.7 ou supérieur) qui conserve l'ordre des clés. Par exemple,

from collections import OrderedDict

data = [('b', 0), ('a', 0)]
d = dict(data)
od = OrderedDict(data)

print(d)
print(od)

#{'a': 0, 'b': 0}
#OrderedDict([('b', 0), ('a', 0)])

Vous pouvez pickler un OrderedDict comme vous le feriez pour un dict, mais l'ordre serait préservé et la chaîne résultante serait la même lors du pickling des mêmes objets. 

from collections import OrderedDict
import pickle

data = [('a', 1), ('b', 2)]
od = OrderedDict(data)
s = pickle.dumps(od)
print(s)

Notez que vous ne devriez pas passer un dict dans le constructeur de OrderedDict car les clés seraient déjà placées. Si vous avez un dictionnaire, vous devez d’abord le convertir en tuples avec l’ordre souhaité. OrderedDict est une sous-classe de dict et dispose de toutes les méthodes dict. Vous pouvez donc créer un objet vide et attribuer de nouvelles clés. 

Votre test n'échoue pas parce que vous utilisez la même version Python et les mêmes conditions: l'ordre du dictionnaire ne changera pas de façon aléatoire entre les itérations de boucle. Mais nous pouvons démontrer que votre code ne produit pas de chaînes différentes lorsque nous modifions l'ordre des clés dans le dictionnaire. 

import pickle

initial = pickle.dumps({'a': 1, 'b': 2})
assert pickle.dumps({'b': 2, 'a': 1}) != initial

La chaîne résultante devrait être différente quand on met la clé 'b' en premier (ce serait différent en Python> = 3.6), mais en Python2, c'est la même chose car la clé 'a' est placée avant la clé 'b'. 

Pour répondre à votre question principale, les dictionnaires Python2 ne sont pas ordonnés, mais un dictionnaire est susceptible d'avoir le même ordre s'il utilise le même code et la même version Python. Toutefois, cet ordre peut différer de celui dans lequel vous avez placé les éléments dans le dictionnaire. Si la commande est importante, il est préférable d’utiliser un OrderedDict ou de mettre à jour votre version de Python.

1
t.m.adam

Comme avec un nombre de choses frustrant en Python, la réponse est "en quelque sorte". Directement des docs, 

Le format de sérialisation de pickle garantit la compatibilité avec les versions antérieures de Python.

C'est potentiellement très légèrement différent de ce que vous demandez. Si c'est un dictionnaire picklé valide maintenant, ce sera toujours un dictionnaire picklé valide et il sera toujours désérialisé dans le dictionnaire correct. Cela laisse non dit quelques propriétés auxquelles vous pourriez vous attendre et qui ne doivent pas contenir:

  • Le pickling ne doit pas nécessairement être déterministe, même pour le même objet dans la même instance Python sur la même plate-forme. Le même dictionnaire pourrait avoir une infinité de représentations possibles marinées (non pas que nous nous attendions à ce que le format soit suffisamment inefficace pour supporter des degrés arbitrairement élevés de remplissage supplémentaire). Comme le soulignent les autres réponses, les dictionnaires n’ont pas d’ordre de tri défini, ce qui peut donner au moins n! représentations sous forme de chaîne d'un dictionnaire avec n éléments.
  • Pour aller plus loin avec le dernier point, il n'est pas garanti que le pickle soit cohérent, même dans une seule instance Python. En pratique, ces modifications ne se produisent pas actuellement, mais ce comportement ne sera pas garanti dans les futures versions de Python.
  • Les futures versions de Python n'ont pas besoin de sérialiser les dictionnaires d'une manière compatible avec les versions actuelles. La seule promesse que nous avons est qu’ils seront capables de désérialiser correctement nos dictionnaires. Actuellement, les dictionnaires sont supportés de la même manière dans tous les formats Pickle, mais cela ne doit pas rester le cas indéfiniment (pas que je suppose que cela changerait un jour).
1
Hans Musgrave

Si vous ne modifiez pas le dict, sa représentation sous forme de chaîne ne changera pas au cours d'une exécution donnée du programme et sa méthode .keys renverra les clés dans le même ordre. Cependant, la commande can change d’une exécution à l’autre (avant Python 3.6).

De même, il n'est pas garanti que deux objets dict différents ayant des paires clé-valeur identiques utilisent le même ordre (avant Python 3.6).


En passant, ce n’est pas une bonne idée d’observer un nom de module avec vos propres variables, comme vous le faites avec ce lambda. Cela rend le code plus difficile à lire, et conduira à des messages d'erreur confus si vous oubliez que vous avez masqué le module et essayez d'accéder à un autre nom plus tard dans le programme.

0
PM 2Ring