Compruebe la mutabilidad en Python?
python-3.x immutability (6)
Considere este code :
a = {...} # a is an dict with arbitrary contents
b = a.copy()
- ¿Qué papel juega la mutabilidad en las claves y valores de los dictados?
- ¿Cómo me aseguro de que los cambios en las claves o los valores de un dict no se reflejen en el otro?
- ¿Cómo se relaciona esto con la constraint de hashable de las claves de dictado?
- ¿Hay alguna diferencia en el comportamiento entre Python 2.xy Python 3.x?
¿Cómo verifico si un tipo es mutable en Python?
Las claves de dict deben ser hashable, lo que implica que tienen un valor de hash inmutable. los valores de dict pueden o no ser mutables; sin embargo, si son mutables, esto afecta tu segunda pregunta.
"Los cambios en las teclas" no se reflejarán entre los dos dictados. Los cambios en valores inmutables, como las cadenas, tampoco se reflejarán. Los cambios en objetos mutables, como las clases definidas por el usuario, se reflejarán porque el objeto está almacenado por id (es decir, referencia).
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
Creo que esto se explica por las dos primeras respuestas.
No que yo sepa al respecto.
algunos pensamientos adicionales :
Hay dos cosas principales que debe saber para comprender el comportamiento de las claves : las claves deben ser hashable (lo que significa que implementan object.__hash__(self)
) y también deben ser "comparables" (lo que significa que implementan algo como object.__cmp__(self)
). Una importante retirada de la documentación: de forma predeterminada, las funciones hash de los objetos definidos por el usuario devuelven id()
.
Considera este ejemplo:
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!
Los valores son mucho más fáciles de entender, el dict almacena referencias a objetos. Lea las secciones en hashable. Cosas como las cadenas son inmutables, si las "cambias", el dict en el que las cambias ahora hace referencia a un nuevo objeto. Los objetos que son mutables pueden "cambiarse en el lugar", por lo tanto, el valor de ambos dictados cambiará.
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.
De todos modos, aquí están los puntos principales de todo esto:
- Para las claves , puede que no sea la mutabilidad , sino la capacidad de almohadilla y la comparabilidad , lo que le interesa.
- Se preocupa por la mutabilidad de los valores, porque, por definición, el valor de un objeto mutable se puede cambiar sin cambiar la referencia a él.
No creo que haya una manera general de probar ninguno de esos puntos. Las pruebas de idoneidad dependerían de su caso de uso. Por ejemplo, puede ser suficiente para verificar que un objeto implementa o no las __hash__
y de comparación ( __eq__
o __cmp__
). De igual manera, es posible que pueda "verificar" el método __setattr__
un objeto de alguna manera para determinar si es mutable.
1) Las claves no deben ser mutables, a menos que tenga una clase definida por el usuario que sea hashable pero también mutable. Eso es todo lo que te obliga. Sin embargo, usar un objeto mutable hashable como clave de dictado puede ser una mala idea.
2) Al no compartir valores entre los dos dicts. Está bien compartir las claves, ya que deben ser inmutables. Copiar el diccionario, en el sentido de módulo de copy
, es definitivamente seguro. Llamar al constructor de dict aquí también funciona: b = dict(a)
. También podrías usar valores inmutables.
3) Todos los tipos inmutables incorporados son hashable. Todos los tipos mutables incorporados no son hashable. Para que un objeto sea hashable, debe tener el mismo hash durante toda su vida útil, incluso si está mutado.
4) No que yo sepa; Estoy describiendo 2.x.
Un tipo es mutable si no es inmutable. Un tipo es inmutable si es un tipo inmutable incorporado: str
, int
, long
, bool
, float
, tuple
y probablemente un par de otros que estoy olvidando. Los tipos definidos por el usuario son siempre mutables.
Un objeto es mutable si no es inmutable. Un objeto es inmutable si consiste, recursivamente, solo en subobjetos de tipo inmutable. Así, una tupla de listas es mutable; no puede reemplazar los elementos de la tupla, pero puede modificarlos a través de la interfaz de la lista, cambiando los datos generales.
En realidad, no existe la mutabilidad o la inmutabilidad en el nivel de lenguaje en Python. Algunos objetos no proporcionan ninguna forma de cambiarlos (p. Ej., Cuerdas y tuplas), por lo que son efectivamente inmutables, pero son puramente conceptuales; no hay ninguna propiedad en el nivel de idioma que indique esto, ni a su código ni a Python.
La inmutabilidad no es realmente relevante para los dictados; Está perfectamente bien usar valores mutables como claves. Lo que importa es la comparación y el hashing: el objeto siempre debe permanecer igual a sí mismo. Por ejemplo:
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)]
Aquí, el example
no es inmutable; Lo estamos modificando con a.data = 2
. Sin embargo, lo estamos utilizando como la clave de un hash sin ningún problema. ¿Por qué? La propiedad que estamos cambiando no tiene ningún efecto en la igualdad: el hash no se modifica, y el example(1)
siempre es igual al example(1)
, ignorando cualquier otra propiedad.
El uso más común de esto es el almacenamiento en caché y la memorización: tener una propiedad en caché o no no cambia lógicamente el objeto, y por lo general no tiene ningún efecto sobre la igualdad.
(Voy a detenerme aquí, por favor no haga cinco preguntas a la vez).
Hay MutableSequence, MutableSet, MutableMapping en las collections módulos. Que se puede utilizar para comprobar la mutabilidad de los tipos prefabricados.
issubclass(TYPE, (MutableSequence, MutableSet, MutableMapping))
Si desea usar esto en tipos definidos por el usuario, el tipo debe ser heredado de uno de ellos o registrado como una subclase virtual.
class x(MutableSequence):
...
o
class x:
...
abc.ABCMeta.register(MutableSequence,x)
Los dictados son conjuntos desordenados de pares clave: valor. Las claves deben ser inmutables, y por lo tanto hashable. Para determinar si un objeto es hashable, puede usar la 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''
Los valores, por otro lado, pueden ser cualquier objeto. Si necesita comprobar si un objeto es inmutable, entonces usaría hash()
.
Realmente no hay garantía de que un tipo que sea hashable también sea inmutable, pero al menos, la implementación correcta de __hash__
requiere que el tipo sea inmutable, con respecto a su propio hash y a la igualdad. Esto no se aplica de ninguna manera en particular.
Sin embargo, todos somos adultos. Sería imprudente implementar __hash__
menos que realmente lo __hash__
. En términos generales, esto simplemente se reduce a decir que si un tipo se puede usar realmente como una clave de diccionario, entonces se pretende que se use de esa manera.
Si está buscando algo que es como un dict, pero también inmutable, namedtuple
podría ser su mejor apuesta de lo que hay en la biblioteca estándar. Es cierto que no es una muy buena aproximación, pero es un comienzo.