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 ay blas 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 geititemo setattributeetc. Pero en lugar de hacerlo yo mismo, ofrezco una recompensa por eso, porque soy flojo. :)
fuente

.ay.b? Para eso parecen existir las propiedades después de todo.NotImplementedsolo 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 = 42silenciosamente no hará nada. Deberías plantear unTypeErroren su lugar.NotImplementedError, peroTypeErrores 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 conpickleycopy.namedtuplecrea un tipo similar al que describí en esta respuesta , es decir, derivadotupley utilizado__slots__. Está disponible en Python 2.6 o superior.fuente
verboseparámetro paranamedtupleel código se genera fácilmente)) es la única interfaz / implementación de unnamedtuplees preferible a docenas muy ligeramente diferentes interfaces escritas a mano / implementaciones que hacer casi lo mismo.namedtupleSerí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
Ason 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.itemgetterlas 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
tupleaquí__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, ¿notupleparece 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.namedtuplesugiera @Sven Marnachfuente
namedtuple(o más precisamente del tipo devuelto por la funciónnamedtuple()) son inmutables. Seguro.namedtuplepasa 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 unaPointinstanciap, pero su implementación original tiene el mismo problema (pruebetuple.__setattr__(i, "x", 42)con unImmutableinstancia).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
@immutabledecorador 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=Trueopció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
typingmó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
obj1yobj2son diferentes, incluso si los nombres de sus campos son diferentes,obj1 == obj2todaví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
namedtuplees 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.pypara 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=10causará: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__atodaví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
propertytambié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 = 1crea un nuevobatributo.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
Immutableclase 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 baseobjectytype, 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
getattrpara 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
@dataclassdecorador 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.
clase inmutable: __slots__ = ('a', 'b') def __init __ (self, a, b): super () .__ setattr __ ('a', a) super () .__ setattr __ ('b', b) def __str __ (self): return "" .format (self.a, self.b) def __setattr __ (self, * ignorado): raise NotImplementedError def __delattr __ (self, * ignorado): raise NotImplementedErrorfuente
passlugar deraise NotImplementedErrorEl
attrmódulo de terceros proporciona esta funcionalidad. .Editar: python 3.7 ha adoptado esta idea en stdlib con
@dataclass.attrimplementa 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,
attrpuede 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.attrTambié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
Freezerclase especial como se muestra a continuación:fuente
Como un
dictTengo 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