Aunque nunca he necesitado esto, me llamó la atención que hacer un objeto inmutable en Python podría ser un poco complicado. No puede anular simplemente __setattr__
, porque entonces ni siquiera puede establecer atributos en el __init__
. Subclasificar una tupla es un truco que funciona:
class Immutable(tuple):
def __new__(cls, a, b):
return tuple.__new__(cls, (a, b))
@property
def a(self):
return self[0]
@property
def b(self):
return self[1]
def __str__(self):
return "<Immutable {0}, {1}>".format(self.a, self.b)
def __setattr__(self, *ignored):
raise NotImplementedError
def __delattr__(self, *ignored):
raise NotImplementedError
Pero entonces usted tiene acceso a la a
y b
las variables a través self[0]
y self[1]
, lo que es molesto.
¿Es esto posible en Pure Python? Si no, ¿cómo lo haría con una extensión C?
(Las respuestas que funcionan solo en Python 3 son aceptables).
Actualizar:
Entonces, subclasificar tuplas es la forma de hacerlo en Pure Python, que funciona bien, excepto por la posibilidad adicional de acceder a los datos [0]
, [1]
etc. Entonces, para completar esta pregunta, todo lo que falta es cómo hacerlo "correctamente" en C, que Sospecho que sería bastante simple, simplemente no implementando ninguno geititem
o setattribute
etc. Pero en lugar de hacerlo yo mismo, ofrezco una recompensa por eso, porque soy flojo. :)
fuente
.a
y.b
? Para eso parecen existir las propiedades después de todo.NotImplemented
solo se entiende como un valor de retorno para las comparaciones ricas. De__setatt__()
todos modos, un valor de retorno para es bastante inútil, ya que generalmente no lo verá en absoluto. Código comoimmutable.x = 42
silenciosamente no hará nada. Deberías plantear unTypeError
en su lugar.NotImplementedError
, peroTypeError
es lo que aumenta una tupla si intentas modificarlo.Respuestas:
Otra solución más en la que acabo de pensar: la forma más simple de obtener el mismo comportamiento que su código original es
No resuelve el problema de que se puede acceder a los atributos a través de
[0]
etc., pero al menos es considerablemente más corto y proporciona la ventaja adicional de ser compatible conpickle
ycopy
.namedtuple
crea un tipo similar al que describí en esta respuesta , es decir, derivadotuple
y utilizado__slots__
. Está disponible en Python 2.6 o superior.fuente
verbose
parámetro paranamedtuple
el código se genera fácilmente)) es la única interfaz / implementación de unnamedtuple
es preferible a docenas muy ligeramente diferentes interfaces escritas a mano / implementaciones que hacer casi lo mismo.namedtuple
Sería copiado por valor cuando se pasa a través de funciones?La forma más fácil de hacer esto es usar
__slots__
:Las instancias de
A
son inmutables ahora, ya que no puede establecer ningún atributo en ellas.Si desea que las instancias de clase contengan datos, puede combinar esto con derivar de
tuple
:Editar : si desea deshacerse de la indexación, puede anular
__getitem__()
:Tenga en cuenta que no puede usar
operator.itemgetter
las propiedades en este caso, ya que esto dependería enPoint.__getitem__()
lugar detuple.__getitem__()
. Además, esto no impedirá el uso detuple.__getitem__(p, 0)
, pero apenas puedo imaginar cómo esto debería constituir un problema.No creo que la forma "correcta" de crear un objeto inmutable sea escribir una extensión C. Python generalmente se basa en implementadores de bibliotecas y usuarios de bibliotecas que son adultos que dan consentimiento , y en lugar de imponer realmente una interfaz, la interfaz debe estar claramente indicada en la documentación. Es por eso que no considero la posibilidad de eludir una anulación
__setattr__()
llamando aobject.__setattr__()
un problema. Si alguien hace esto, es bajo su propio riesgo.fuente
tuple
aquí__slots__ = ()
, en lugar de__slots__ = []
? (Solo aclarando)__slots__
no se va a cambiar, ¿verdad? Su propósito es identificar por una vez qué atributos se pueden establecer. Entonces, ¿notuple
parece una elección muy natural en tal caso?__slots__
no puedo establecer ningún atributo. Y si tengo,__slots__ = ('a', 'b')
entonces los atributos ayb siguen siendo mutables.__setattr__
por lo que es una mejora sobre la mía. +1 :)Puede usar Cython para crear un tipo de extensión para Python:
Funciona tanto en Python 2.xy 3.
Pruebas
Si no le importa el soporte de indexación, entonces es preferible que
collections.namedtuple
sugiera @Sven Marnachfuente
namedtuple
(o más precisamente del tipo devuelto por la funciónnamedtuple()
) son inmutables. Seguro.namedtuple
pasa todas las pruebas (excepto el soporte de indexación). ¿Qué requisito me perdí?__weakref__
en Python?Otra idea sería no permitir
__setattr__
y usar por completoobject.__setattr__
en el constructor:Por supuesto, podría usar
object.__setattr__(p, "x", 3)
para modificar unaPoint
instanciap
, pero su implementación original tiene el mismo problema (pruebetuple.__setattr__(i, "x", 42)
con unImmutable
instancia).Puede aplicar el mismo truco en su implementación original: deshacerse de él
__getitem__()
y utilizarlotuple.__getitem__()
en sus funciones de propiedad.fuente
__setattr__
, porque el punto no es ser infalible. El punto es dejar en claro que no debe modificarse y evitar modificaciones por error.Puede crear un
@immutable
decorador que anule__setattr__
o cambie el__slots__
a una lista vacía, luego decore el__init__
método con él.Editar: como señaló el OP, cambiar el
__slots__
atributo solo impide la creación de nuevos atributos , no la modificación.Edit2: Aquí hay una implementación:
Edit3: el uso
__slots__
rompe este código, porque si detiene la creación del objeto__dict__
. Estoy buscando una alternativa.Edit4: Bueno, eso es todo. Es un poco duro, pero funciona como un ejercicio :-)
fuente
object.__setattr__()
se rompe stackoverflow.com/questions/4828080/…Usando una clase de datos congelada
Para Python 3.7+ puede usar una clase de datos con un
frozen=True
opción , que es una forma muy pitónica y fácil de hacer para hacer lo que quieras.Se vería algo así:
Como se requieren sugerencias de tipo para los campos de clases de datos, he utilizado Any del
typing
módulo .Razones para NO usar un Namedtuple
Antes de Python 3.7 era frecuente ver a las tuplas nombradas como objetos inmutables. Puede ser complicado de muchas maneras, una de ellas es que el
__eq__
método entre las tuplas nombradas no considera las clases de los objetos. Por ejemplo:Como puede ver, incluso si los tipos de
obj1
yobj2
son diferentes, incluso si los nombres de sus campos son diferentes,obj1 == obj2
todavía daTrue
. Esto se debe a que el__eq__
método utilizado es el de la tupla, que compara solo los valores de los campos en función de sus posiciones. Esa puede ser una gran fuente de errores, especialmente si está subclasificando estas clases.fuente
No creo que sea completamente posible, excepto mediante el uso de una tupla o una tupla con nombre. Pase lo que pase, si anula
__setattr__()
el usuario, siempre puede omitirlo llamandoobject.__setattr__()
directamente. Cualquier solución que dependa de__setattr__
está garantizada para no funcionar.Lo siguiente es sobre lo más cercano que puede llegar sin usar algún tipo de tupla:
pero se rompe si te esfuerzas lo suficiente:
pero el uso de Sven
namedtuple
es genuinamente inmutable.Actualizar
Como la pregunta se ha actualizado para preguntar cómo hacerlo correctamente en C, aquí está mi respuesta sobre cómo hacerlo correctamente en Cython:
Primero
immutable.pyx
:y a
setup.py
para compilarlo (usando el comandosetup.py build_ext --inplace
:Luego para probarlo:
fuente
Hice clases inmutables anulando
__setattr__
y permitiendo el conjunto si la persona que llama es__init__
:Esto aún no es suficiente, ya que permite que cualquiera
___init__
pueda cambiar el objeto, pero se entiende la idea.fuente
object.__setattr__()
se rompe stackoverflow.com/questions/4828080/…__init__
no sea muy satisfactoria.Además de las excelentes otras respuestas, me gusta agregar un método para Python 3.4 (o quizás 3.3). Esta respuesta se basa en varias respuestas previas a esta pregunta.
En python 3.4, puede usar propiedades sin setters para crear miembros de clase que no se puedan modificar. (En versiones anteriores era posible asignar propiedades sin un setter).
Puedes usarlo así:
que imprimirá
"constant"
Pero llamar
instance.a=10
causará:Explicación: las propiedades sin setters son una característica muy reciente de python 3.4 (y creo que 3.3). Si intenta asignar a dicha propiedad, se generará un error. Usando ranuras restrinjo las variables miembro a
__A_a
(que es__a
).Problema: Asignación a
_A__a
todavía es posible (instance._A__a=2
). Pero si asigna a una variable privada, es su culpa ...Esta respuesta, entre otras, sin embargo, desalienta el uso de
__slots__
. Usar otras formas de prevenir la creación de atributos puede ser preferible.fuente
property
también está disponible en Python 2 (mira el código en la pregunta en sí). No crea un objeto inmutable, pruebe las pruebas de mi respuesta , por ejemplo,instance.b = 1
crea un nuevob
atributo.A().b = "foo"
es decir, no permitir establecer nuevos atributos.Aquí hay una solución elegante :
Herede de esta clase, inicialice sus campos en el constructor y estará listo.
fuente
Si está interesado en objetos con comportamiento, entonces namedtuple es casi su solución.
Como se describe al final de la documentación de namedtuple , puede derivar su propia clase de namedtuple; y luego, puede agregar el comportamiento que desee.
Por ejemplo (código tomado directamente de la documentación ):
Esto resultará en:
Este enfoque funciona tanto para Python 3 como para Python 2.7 (también probado en IronPython).
El único inconveniente es que el árbol de herencia es un poco extraño; pero esto no es algo con lo que sueles jugar.
fuente
class Point(typing.NamedTuple):
Las clases que heredan de la siguiente
Immutable
clase son inmutables, como son sus instancias, una vez que su__init__
método termina de ejecutarse. Como se trata de Python puro, como han señalado otros, no hay nada que impida que alguien use los métodos especiales de mutación de la baseobject
ytype
, pero esto es suficiente para detener a nadie de la mutación de una clase / instancia por accidente.Funciona secuestrando el proceso de creación de clases con una metaclase.
fuente
Lo necesitaba hace un momento y decidí hacer un paquete de Python para él. La versión inicial está en PyPI ahora:
Usar:
Documentos completos aquí: https://github.com/theengineear/immutable
Espero que ayude, envuelve una tupla con nombre como se ha discutido, pero hace que la instanciación sea mucho más simple.
fuente
De esta manera no deja
object.__setattr__
de funcionar, pero aún lo encuentro útil:Es posible que deba anular más cosas (como
__setitem__
) según el caso de uso.fuente
getattr
para poder proporcionar un valor predeterminado parafrozen
. Eso simplificó un poco las cosas. stackoverflow.com/a/22545808/5987__new__
anulación. En el interior__setattr__
solo reemplaza el condicional conif name != '_frozen' and getattr(self, "_frozen", False)
freeze()
función. El objeto se "congelará una vez". Finalmente, preocuparseobject.__setattr__
es una tontería, porque "aquí todos somos adultos".¡A partir de Python 3.7, puede usar el
@dataclass
decorador en su clase y será inmutable como una estructura! Sin embargo, puede o no agregar un__hash__()
método a su clase. Citar:Aquí el ejemplo de los documentos vinculados anteriormente:
fuente
frozen
, es decir@dataclass(frozen=True)
, pero básicamente bloquea el uso__setattr__
y me__delattr__
gusta en la mayoría de las otras respuestas aquí. Simplemente lo hace de manera compatible con las otras opciones de clases de datos.Puede anular setattr y seguir usando init para establecer la variable. Usarías super class setattr . Aquí está el código.
fuente
pass
lugar deraise NotImplementedError
El
attr
módulo de terceros proporciona esta funcionalidad. .Editar: python 3.7 ha adoptado esta idea en stdlib con
@dataclass
.attr
implementa clases congeladas anulando__setattr__
y tiene un impacto menor en el rendimiento en cada tiempo de instanciación, de acuerdo con la documentación.Si tiene la costumbre de usar clases como tipos de datos,
attr
puede ser especialmente útil ya que se ocupa de la placa repetitiva por usted (pero no hace ninguna magia). En particular, escribe nueve métodos dunder (__X__) para usted (a menos que desactive alguno de ellos), incluidos repr, init, hash y todas las funciones de comparación.attr
También proporciona un ayudante para__slots__
.fuente
Entonces, estoy escribiendo respectivo de python 3:
I) con la ayuda del decorador de clase de datos y establecer frozen = True. Podemos crear objetos inmutables en Python.
para esto es necesario importar la clase de datos de las clases de datos lib y necesita configurar frozen = True
ex.
desde clases de datos importar clase de datos
o / p:
Fuente: https://realpython.com/python-data-classes/
fuente
Un enfoque alternativo es crear un contenedor que haga que una instancia sea inmutable.
Esto es útil en situaciones donde solo algunas instancias tienen que ser inmutables (como los argumentos predeterminados de las llamadas a funciones).
También se puede usar en fábricas inmutables como:
También protege
object.__setattr__
, pero puede fallar a otros trucos debido a la naturaleza dinámica de Python.fuente
Usé la misma idea que Alex: una metaclase y un "marcador de inicio", pero en combinación con sobreescribir __setattr__:
Nota: Llamo a la metaclase directamente para que funcione tanto para Python 2.xy 3.x.
Funciona también con máquinas tragamonedas ...:
... y herencia múltiple:
Sin embargo, tenga en cuenta que los atributos mutables siguen siendo mutables:
fuente
Una cosa que realmente no se incluye aquí es la inmutabilidad total ... no solo el objeto padre, sino también todos los hijos. Las tuplas / congelados pueden ser inmutables, por ejemplo, pero los objetos de los que forma parte pueden no serlo. Aquí hay una versión pequeña (incompleta) que hace un trabajo decente de imponer la inmutabilidad hasta el final:
fuente
Puede anular setAttr en la declaración final de init. Entonces puedes construir pero no cambiar. Obviamente, aún puede anular por el objeto usint. setAttr pero en la práctica la mayoría de los idiomas tienen alguna forma de reflexión, por lo que la inmutabilidad siempre es una abstracción permeable. La inmutabilidad se trata más de evitar que los clientes violen accidentalmente el contrato de un objeto. Yo suelo:
=============================
La solución original ofrecida era incorrecta, esta se actualizó en función de los comentarios utilizando la solución de aquí
La solución original es incorrecta de una manera interesante, por lo que se incluye en la parte inferior.
===============================
Salida:
======================================
Implementación original:
Se señaló en los comentarios, correctamente, que esto de hecho no funciona, ya que impide la creación de más de un objeto, ya que está anulando el método de clase setattr, lo que significa que no se puede crear un segundo como self.a = will fallar en la segunda inicialización.
fuente
La solución básica a continuación aborda el siguiente escenario:
__init__()
se puede escribir accediendo a los atributos como de costumbre.La idea es anular el
__setattr__
método y reemplazar su implementación cada vez que se cambia el estado del objeto congelado.Por lo tanto, necesitamos algún método (
_freeze
) que almacene esas dos implementaciones y cambie entre ellas cuando se solicite.Este mecanismo puede implementarse dentro de la clase de usuario o heredarse de una
Freezer
clase especial como se muestra a continuación:fuente
Como un
dict
Tengo una biblioteca de código abierto donde hago las cosas de manera funcional , por lo que es útil mover los datos en un objeto inmutable. Sin embargo, no quiero tener que transformar mi objeto de datos para que el cliente interactúe con ellos. Entonces, se me ocurrió esto: te da un dict como objeto inmutable + algunos métodos auxiliares.
Gracias a Sven Marnach en su respuesta por la implementación básica de restringir la actualización y eliminación de propiedades.
Métodos de ayuda
Ejemplos
fuente