Propiedades de solo lectura de nivel de clase en Python

¿Hay alguna manera de hacer una propiedad de solo lectura de nivel de clase en Python ? Por ejemplo, si tengo una clase Foo , quiero decir:

 x = Foo.CLASS_PROPERTY 

pero evita que alguien diga:

 Foo.CLASS_PROPERTY = y 

EDITAR: Me gusta la simplicidad de la solución de Alex Martelli , pero no la syntax que requiere. Tanto su respuesta como la de Unutbu inspiraron la siguiente solución, que está más cerca del espíritu de lo que estaba buscando:

 class const_value (object): def __init__(self, value): self.__value = value def make_property(self): return property(lambda cls: self.__value) class ROType(type): def __new__(mcl,classname,bases,classdict): class UniqeROType (mcl): pass for attr, value in classdict.items(): if isinstance(value, const_value): setattr(UniqeROType, attr, value.make_property()) classdict[attr] = value.make_property() return type.__new__(UniqeROType,classname,bases,classdict) class Foo(object): __metaclass__=ROType BAR = const_value(1) BAZ = 2 class Bit(object): __metaclass__=ROType BOO = const_value(3) BAN = 4 

Ahora, me sale:

 Foo.BAR # 1 Foo.BAZ # 2 Foo.BAR=2 # Traceback (most recent call last): # File "", line 1, in  # AttributeError: can't set attribute Foo.BAZ=3 # 

Prefiero esta solución porque:

  • Los miembros se declaran en línea en lugar de después del hecho, como con type(X).foo = ...
  • Los valores de los miembros se establecen en el código de la clase real, a diferencia del código de la metaclase.

Todavía no es ideal porque:

  • Tengo que configurar __metaclass__ para que los objetos const_value se interpreten correctamente.
  • Los const_value no se “comportan” como los valores simples. Por ejemplo, no podría usarlo como un valor predeterminado para un parámetro en un método de la clase.

Las soluciones existentes son un poco complejas. ¿Qué tal si nos aseguramos de que cada clase en un grupo determinado tenga una metaclase única y luego establezca una propiedad de solo lectura normal en la metaclase personalizada? A saber:

 >>> class Meta(type): ... def __new__(mcl, *a, **k): ... uniquemcl = type('Uniq', (mcl,), {}) ... return type.__new__(uniquemcl, *a, **k) ... >>> class X: __metaclass__ = Meta ... >>> class Y: __metaclass__ = Meta ... >>> type(X).foo = property(lambda *_: 23) >>> type(Y).foo = property(lambda *_: 45) >>> X.foo 23 >>> Y.foo 45 >>> 

esto es realmente mucho más simple, porque se basa en nada más que en el hecho de que cuando se obtienen los descriptores de atributos de una instancia en la clase (así que, por supuesto, cuando se obtienen los descriptores de atributos de una clase en la metaclase), y en la clase / metaclase único no es terriblemente difícil.

Ah, y por supuesto:

 >>> X.foo = 67 Traceback (most recent call last): File "", line 1, in  AttributeError: can't set attribute 

Sólo para confirmar es de hecho de sólo lectura!

La solución ActiveState a la que hace referencia Pynt hace que las instancias de ROClass tengan atributos de solo lectura. Su pregunta parece preguntar si la clase en sí misma puede tener atributos de solo lectura.

Aquí hay una forma, basada en el comentario de Raymond Hettinger :

 #!/usr/bin/env python def readonly(value): return property(lambda self: value) class ROType(type): CLASS_PROPERTY = readonly(1) class Foo(object): __metaclass__=ROType print(Foo.CLASS_PROPERTY) # 1 Foo.CLASS_PROPERTY=2 # AttributeError: can't set attribute 

La idea es esta: considere primero la solución de Raymond Hettinger:

 class Bar(object): CLASS_PROPERTY = property(lambda self: 1) bar=Bar() bar.CLASS_PROPERTY=2 

Muestra una forma relativamente simple de dar a la barra una propiedad de solo lectura.

Tenga en cuenta que debe agregar la línea CLASS_PROPERTY = property(lambda self: 1) a la definición de la clase de barra, no a la barra en sí.

Entonces, si desea que la clase Foo tenga una propiedad de solo lectura, entonces la clase primaria de Foo debe tener CLASS_PROPERTY = property(lambda self: 1) definida.

La clase padre de una clase es una metaclase. Por eso definimos ROType como la metaclase:

 class ROType(type): CLASS_PROPERTY = readonly(1) 

Luego hacemos que la clase padre de Foo sea ROType:

 class Foo(object): __metaclass__=ROType 

Encontré esto en ActiveState :

 # simple read only attributes with meta-class programming # method factory for an attribute get method def getmethod(attrname): def _getmethod(self): return self.__readonly__[attrname] return _getmethod class metaClass(type): def __new__(cls,classname,bases,classdict): readonly = classdict.get('__readonly__',{}) for name,default in readonly.items(): classdict[name] = property(getmethod(name)) return type.__new__(cls,classname,bases,classdict) class ROClass(object): __metaclass__ = metaClass __readonly__ = {'a':1,'b':'text'} if __name__ == '__main__': def test1(): t = ROClass() print ta print tb def test2(): t = ROClass() ta = 2 test1() 

Tenga en cuenta que si intenta establecer un atributo de solo lectura ( ta = 2 ), python generará un AttributeError .