2009-05-04 10 views
16

Quiero crear un objeto en python que tenga algunos atributos y quiero protegerme de usar accidentalmente el nombre de atributo incorrecto. El código es el siguiente:python, __slots__, y "el atributo es de solo lectura"

class MyClass(object) : 
    m = None # my attribute 
    __slots__ = ("m") # ensure that object has no _m etc 

a = MyClass() # create one 
a.m = "?" # here is a PROBLEM 

Pero después de ejecutar este código simple, aparece un extraño error:

Traceback (most recent call last): 
    File "test.py", line 8, in <module> 
    a.m = "?" 
AttributeError: 'test' object attribute 'm' is read-only 

¿Hay cualquier programador prudente, que puede dedicar un poco de su tiempo y me ilumine sobre los errores de "solo lectura"

+1

Esto parece un caso de uso innecesario. ¿Por qué estás haciendo esto? ¿Por qué no estás usando propiedades? –

+0

Quiero proponerme escribir un nombre de propiedad con un error tipográfico, por ejemplo si escribo object.my_prperty = 1 en lugar de object.my_property = 1 (error 'o'), python no mostrará ningún error, pero tendré un error de lógica en mi programa. Por lo tanto, quiero limitar las propiedades a mi conjunto predefinido, por lo que solo se pueden procesar. ¿Cómo puedo resolver esto con propiedades? – grigoryvp

+9

generalmente en python aceptamos la posibilidad de errores ortográficos en las asignaciones y escribe buenas pruebas que también detectarían errores superficiales y más semánticos. Humildemente sugiero que si quieres escribir Python, deberías considerar hacerlo de la forma de pitón en lugar de luchar contra el lenguaje con uñas y dientes. Añadiré que las faltas de ortografía en mi código python me han costado * quizás * 20 minutos en los últimos 9 años. – llimllib

Respuesta

38

Cuando se declara variables de instancia utilizando __slots__, Python crea un descriptor object como una variable de clase con el mismo nombre. En su caso, este descriptor se sobrescribe con la variable de clase m que está definiendo en la siguiente línea:

m = None # my attribute 

Esto es lo que hay que hacer: No definir una variable de clase llamada m, e inicializar la instancia variable m en el método __init__.

class MyClass(object): 
    __slots__ = ("m",) 
    def __init__(self): 
    self.m = None 

a = MyClass() 
a.m = "?" 

Como nota al margen, las tuplas con elementos individuales necesitan una coma después del elemento. Ambos funcionan en su código porque __slots__ acepta una sola cadena o una secuencia de cadenas iterable. En general, para definir una tupla que contenga el elemento 1, use (1,) o 1, y no (1).

+0

¿Conoce por qué si escribía: __slots__ = [ "m"] m = 5 Python se sobreponen con objeto descriptor variable y no usará el método set() de los objetos del descriptor para establecer el valor en su lugar? – grigoryvp

+7

Porque m en este contexto es una variable de clase, no una variable de instancia. Para utilizar el método set() del descriptor, debe asignarle la m de un objeto, no una clase. Si desea inicializar una variable de instancia de un objeto, hágalo desde el método __init__ como lo hice en mi fragmento de código. –

+0

La clave de los documentos (que por cierto se movieron [aquí] (https://docs.python.org/2/reference/datamodel.html#slots)) es '__slots__ se implementan en el nivel de clase mediante la creación de descriptores para cada variable nombre. Como resultado, los atributos de clase no se pueden usar para establecer valores predeterminados para las variables de instancia definidas por __slots__; de lo contrario, el atributo de clase sobrescribiría la asignación del descriptor'. Entonces, ¿qué sucede es que (independientemente del orden de definición de 'm' y' __slots__'?) Cuando se intenta la asignación, el método set ya no se asigna a nada, ni se obtiene sino que vuelve a MyClass.m –

7

__slots__ funciona con variables de instancia, mientras que lo que tiene allí es una variable de clase. Esta es la forma en que debe hacerlo:

class MyClass(object) : 
    __slots__ = ("m",) 
    def __init__(self): 
    self.m = None 

a = MyClass() 
a.m = "?"  # No error 
6

Considere esto.

class SuperSafe(object): 
    allowed= ("this", "that") 
    def __init__(self): 
     self.this= None 
     self.that= None 
    def __setattr__(self, attr, value): 
     if attr not in self.allowed: 
      raise Exception("No such attribute: %s" % (attr,)) 
     super(SuperSafe, self).__setattr__(attr, value) 

Un mejor enfoque es utilizar pruebas unitarias para este tipo de comprobación. Esta es una buena cantidad de sobrecarga en tiempo de ejecución.

+4

+1 para sin abusar de __slots__ – Ravi

+3

¿Pero esto es más líneas de código en comparación con __slots__? Por qué verificar manualmente qué se puede realizar automáticamente por idioma. – grigoryvp

+3

Para evitar mensajes de "error muy extraño" y para evitar tener que pasar mucho tiempo depurando misterios internos de __slots__. Prefiero soluciones obvias y simples donde no se requiere conocimiento secreto para depurar. –

14

Está haciendo un uso indebido __slots__. Impide la creación de __dict__ para las instancias. Esto solo tiene sentido si se encuentra con problemas de memoria con muchos objetos pequeños, porque deshacerse de __dict__ puede reducir la huella. Esta es una optimización hardcore que no es necesaria en el 99.9% de todos los casos.

Si necesita el tipo de seguridad que describió, Python realmente es el idioma equivocado. Mejor utilizar algo estricto como Java (en lugar de tratar de escribir Java en Python).

Si no pudiste averiguar por qué los atributos de la clase causaron estos problemas en tu código, entonces tal vez deberías pensar dos veces antes de introducir hacks de idiomas como este. Probablemente sería más inteligente familiarizarse primero con el idioma.

Para completar, aquí está el documentation link for slots.

+4

+1 para completar. Como se mencionó en todo el lugar, __slots__ no es una mala idea, pero requiere un uso cuidadoso. Es más que un control de seguridad y agrega mucha complejidad a la implementación que requerirá prestar atención a cómo usa el objeto. –

0
class MyClass(object) : 
    m = None # my attribute 

El m aquí es los atributos de clase, más que el atributo de instancia. Debe conectarlo con su instancia por cuenta propia en __init__.

Cuestiones relacionadas