Vérifier la mutabilité en Python?
Considérer ce code:
a = {...} # a is an dict with arbitrary contents
b = a.copy()
- Quel rôle joue la mutabilité dans les clés et les valeurs des dicts?
- Comment puis-je m'assurer que les modifications apportées aux clés ou aux valeurs d'un dict ne sont pas reflétées dans l'autre?
- Comment cela se rapporte-t-il à la contrainte hashable des clés dict?
- y a-t-il des différences de comportement entre Python 2.x et Python 3.x?
Comment puis-je vérifier si un type est mutable en Python?
6 réponses
1) Les clés ne doivent pas être mutables, sauf si vous avez une classe définie par l'utilisateur qui est hashable mais également mutable. C'est tout ce qui est forcé sur vous. cependant, utiliser un objet hashable et mutable comme clé dict pourrait être une mauvaise idée.
2) En ne partageant pas les valeurs entre les deux dicts. C'est OK de partager les clés, car elles doivent être immuables. Copier le dictionnaire, dans le sens du module copy
, est définitivement sûr. Appeler le constructeur dict ici fonctionne aussi: b = dict(a)
. Vous pourriez utilisez également des valeurs immuables.
3) Tous les types immuables intégrés sont hashable. Tous les types mutables intégrés ne sont pas hashable. Pour qu'un objet soit hachable, il doit avoir le même hachage sur toute sa durée de vie, même s'il est muté.
4) pas que je sache; je décris 2.X.
Un type est mutable s'il n'est pas immuable. Un type est immuable si c'est un construit-dans immuable type: str
, int
, long
, bool
, float
, tuple
, et probablement quelques autres que j'oublie. Les types définis par l'utilisateur sont toujours modifiables.
Un objet est mutable s'il n'est pas immuable. Un objet est immuable s'il consiste, de manière récursive, uniquement en sous-objets typés immuables. Ainsi, un tuple de listes est mutable; vous ne pouvez pas remplacer les éléments du tuple, mais vous pouvez les modifier via l'interface de liste, en changeant les données globales.
Il n'y a pas vraiment de mutabilité ou d'immuabilité au niveau du langage en Python. Certains objets fournissent aucun moyen de les changer (par exemple. strings et tuples), et sont donc effectivement immuables, mais c'est purement conceptuel; il n'y a aucune propriété au niveau du langage indiquant cela, ni à votre code ni à Python lui-même.
L'immutabilité n'est pas réellement pertinente pour les dicts; Il est parfaitement correct d'utiliser des valeurs mutables comme clés. Ce qui compte c'est la comparaison et la hachage: l'objet doit toujours rester égal à lui-même. Par exemple:
class example(object):
def __init__(self, a):
self.value = a
def __eq__(self, rhs):
return self.value == rhs.value
def __hash__(self):
return hash(self.value)
a = example(1)
d = {a: "first"}
a.data = 2
print d[example(1)]
Ici example
est pas immuable; nous sommes en le modifiant avec a.data = 2
. Pourtant, nous l'utilisons comme une clé de hachage, sans aucun problème. Pourquoi? La propriété que nous modifions n'a aucun effet sur l'égalité: le hachage est inchangé, et example(1)
est toujours égal à example(1)
, en ignorant toutes les autres propriétés.
L'utilisation la plus courante de ceci est la mise en cache et la mémoization: avoir une propriété mise en cache ou non ne change pas logiquement l'objet, et n'a généralement aucun effet sur l'égalité.
(je vais m'arrêter ici -- ne posez pas cinq questions à la fois.)
Il n'y a vraiment aucune garantie qu'un type qui est hashable est également immuable, mais au moins, implémenter correctement __hash__
nécessite que le type soit immuable, par rapport à son propre hachage, et à l'égalité. Ceci n'est pas appliqué d'une manière particulière.
Cependant, nous sommes tous des adultes. Il serait imprudent d'implémenter __hash__
sauf si vous le vouliez vraiment. Grosso modo, cela se résume à dire que si un type peut réellement être utilisé comme clé de dictionnaire, alors c'est destiné à être utilisé de cette façon.
Si vous cherchez quelque chose qui est comme un dict, mais aussi immuable, alors namedtuple
peut-être votre meilleur pari de ce qui est dans la bibliothèque standard. Certes, il n'est pas une très bonne approximation, mais c'est un début.
Il y a MutableSequence, MutableSet, MutableMapping dans le module collections . Qui peut être utilisé pour vérifier la mutabilité des types prédéfinis.
issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))
Si vous voulez l'utiliser sur des types définis par l'utilisateur, le type doit être hérité de l'un d'entre eux ou enregistré en tant que sous-classe virtuelle.
class x(MutableSequence):
...
Ou
class x:
...
abc.ABCMeta.register(MutableSequence,x)
Dict keys doit être hashable, ce qui implique qu'ils ont une immuable hash valeur. les valeurs dict peuvent ou non être mutables; cependant, si elles sont mutables, cela a un impact sur votre deuxième question.
-
Les "modifications apportées aux clés" ne seront pas reflétées entre les deux dicts. Les modifications apportées aux valeurs immuables, telles que les chaînes, ne seront pas non plus reflétées. Les modifications apportées aux objets mutables, tels que les classes définies par l'utilisateur, seront est stocké par l'id de référence).
class T(object): def __init__(self, v): self.v = v t1 = T(5) d1 = {'a': t1} d2 = d1.copy() d2['a'].v = 7 d1['a'].v # = 7 d2['a'] = T(2) d2['a'].v # = 2 d1['a'].v # = 7 import copy d3 = copy.deepcopy(d2) # perform a "deep copy" d3['a'].v = 12 d3['a'].v # = 12 d2['a'].v # = 2
Je pense que cela s'explique par les deux premières réponses.
Pas que je sache à cet égard.
Quelques réflexions supplémentaires:
Il y a deux choses principales à savoir pour comprendre le comportement de keys : les clés doivent être hashable (ce qui signifie qu'elles implémentent object.__hash__(self)
) et ils doivent aussi être " comparables "( ce qui signifie qu'ils implémentent quelque chose comme object.__cmp__(self)
). Un important retrait des documents: par défaut, les fonctions de hachage des objets définis par l'utilisateur reviennent id()
.
, Considérons cet exemple:
class K(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return self.x + self.y
k1 = K(1, 2)
d1 = {k1: 3}
d1[k1] # outputs 3
k1.x = 5
d1[k1] # KeyError! The key's hash has changed!
k2 = K(2, 1)
d1[k2] # KeyError! The key's hash is right, but the keys aren't equal.
k1.x = 1
d1[k1] # outputs 3
class NewK(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return self.x + self.y
def __cmp__(self, other):
return self.x - other.x
nk1 = NewK(3, 4)
nd1 = {nk1: 5}
nd1[nk1] # outputs 5
nk2 = NewK(3, 7)
nk1 == nk2 # True!
nd1[nk2] # KeyError! The keys' hashes differ.
hash(nk1) == hash(nk2) # False
nk2.y = 4
nd1[nk2] # outputs 5
# Where this can cause issues:
nd1.keys()[0].x = 5
nd1[nk1] # KeyError! nk1 is no longer in the dict!
id(nd1.keys()[0]) == id(nk1) # Yikes. True?!
nd1.keys()[0].x = 3
nd1[nk1] # outputs 5
id(nd1.keys()[0]) == id(nk1) # True!
les valeurs sont beaucoup plus faciles à comprendre, le dict stocke les références aux objets. Lisez les sections sur hashable. Des choses comme les chaînes sont immuables, si vous les "changez" , le dict dans lequel vous l'avez changé référence maintenant un nouvel objet. Les objets mutables peuvent être "changés sur place", d'où la valeur de les deux dicts vont changer.
d1 = {1: 'a'}
d2 = d1.copy()
id(d1[1]) == id(d2[1]) # True
d2[1] = 'z'
id(d1[1]) == id(d2[1]) # False
# the examples in section 2 above have more examples of this.
Quoi Qu'il en soit, voici les principaux points de tout cela:
- pour les clés , ce n'est peut-être pas mutabilité, mais plutôt hashability et comparabilité, qui vous intéressent.
- Vous vous souciez de la mutabilité des valeurs, car par définition, la valeur d'un objet mutable peut être modifiée sans changer la référence à celle-ci.
Je ne pense pas qu'il existe un moyen général de tester l'un ou l'autre de ces points. Test pour la pertinence dépendrait de votre cas d'utilisation. Par exemple, il peut être suffisant de vérifier qu'un objet implémente ou non les fonctions __hash__
et comparison (__eq__
ou __cmp__
). Comme-sage, vous pourriez être en mesure de" vérifier " la méthode __setattr__
d'un objet d'une manière ou d'une autre pour déterminer si elle est mutable.
Les Dicts sont des ensembles non ordonnés de paires clé:valeur. Les clés doivent être immuables, et donc hashable. Pour déterminer si un objet est hashable, vous pouvez utiliser la fonction hash()
:
>>> hash(1)
1
>>> hash('a')
12416037344
>>> hash([1,2,3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> hash((1,2,3))
2528502973977326415
>>> hash({1: 1})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
Les valeurs, d'autre part, peuvent être n'importe quel objet. Si vous avez besoin de vérifier si un objet est immuable, alors j'utiliserais hash()
.