Tengo muchas clases pequeñas diferentes que tienen algunos campos cada uno, p. esto:¿Cómo debo exponer los campos de solo lectura de las clases de Python?
class Article:
def __init__(self, name, available):
self.name = name
self.available = available
¿Cuál es la forma más fácil y/o más idiomática para que el campo name
de sólo lectura, por lo que
a = Article("Pineapple", True)
a.name = "Banana" # <-- should not be possible
ya no es posible?
Esto es lo que yo consideraba hasta ahora:
Utilice un captador (! Uf).
class Article: def __init__(self, name, available): self._name = name self.available = available def name(self): return self._name
feo, no Pythonic - y una gran cantidad de código repetitivo para escribir (especialmente si tengo varios campos para hacer de sólo lectura). Sin embargo, hace el trabajo y es fácil ver por qué es así.
Uso
__setattr__
:class Article: def __init__(self, name, available): self.name = name self.available = available def __setattr__(self, name, value): if name == "name": raise Exception("%s property is read-only" % name) self.__dict__[name] = value
ve bastante en el lado de la persona que llama, que parece ser la forma idiomática para hacer el trabajo - pero desafortunadamente no tengo muchas clases con sólo unos pocos campos para hacer única de cada lectura. Así que tendría que agregar una implementación
__setattr__
a todos ellos. ¿O usar algún tipo de mixin tal vez? En cualquier caso, necesitaría decidir cómo comportarme en caso de que un cliente intente asignar un valor a un campo de solo lectura. Rendimiento cierta excepción, supongo, pero ¿cuál?Utilice una función de utilidad para definir propiedades (y, opcionalmente, captadores) de forma automática. Esta es básicamente la misma idea que (1), excepto que yo no escribo los captadores de forma explícita, sino más bien hacer algo como
class Article: def __init__(self, name, available): # This function would somehow give a '_name' field to self # and a 'name()' getter to the 'Article' class object (if # necessary); the getter simply returns self._name defineField(self, "name") self.available = available
La desventaja de esto es que ni siquiera sé si esto es posible (o cómo implementarlo) ya que no estoy familiarizado con la generación de código de tiempo de ejecución en Python. :-)
Hasta ahora, (2) parece ser más prometedor para mí excepto por el hecho de que voy a necesitar __setattr__
definiciones a todas mis clases. Ojalá hubiera una forma de 'anotar' los campos para que esto ocurra automáticamente. ¿Alguien tiene una mejor idea?
Por lo que vale, estoy cantando Python 2.6.
ACTUALIZACIÓN: Gracias por todas las respuestas interesantes! Por ahora, tengo esto:
def ro_property(o, name, value):
setattr(o.__class__, name, property(lambda o: o.__dict__["_" + name]))
setattr(o, "_" + name, value)
class Article(object):
def __init__(self, name, available):
ro_property(self, "name", name)
self.available = available
Esto parece funcionar muy bien. Los únicos cambios necesarios para la clase original son
- necesito para heredar
object
(que no es una cosa tan estúpida de todas formas, supongo) - Necesito cambiar
self._name = name
aro_property(self, "name", name)
.
Esto se ve bastante limpio para mí - ¿alguien puede ver un inconveniente con él?
Su 'ro_property' es demasiado complicada: la respuesta de Sven o Chris es la mejor opción. –
@EthanFurman es complicado, pero tal vez ese sea el precio que tiene que pagar si desea mantener los cambios hechos a las clases existentes al mínimo? –
No recuerdo la última vez que escribí sobre el atributo de una instancia (desde fuera de los métodos de la instancia), excepto cuando se usa el código de otra persona. ¿No son los atributos de instancia casi siempre destinados a ser de solo lectura en la práctica? Los atributos de instancia generalmente se establecen en '__init__' y no se modifican después, a menos que se llame a un método en la instancia. Creo que considero este estilo de codificación adecuado. –