2009-11-14 14 views
25

¿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:Propiedades de solo lectura de nivel de clase en Python

x = Foo.CLASS_PROPERTY 

pero evitar que alguien diciendo:

Foo.CLASS_PROPERTY = y 

EDIT: me gusta la simplicidad de Alex Martelli's solution, pero no el sintaxis que requiere Tanto su ~unutbu's answer s inspiró la siguiente solución, que es más cerca al 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 "<stdin>", line 1, in <module> 
# AttributeError: can't set attribute 
Foo.BAZ=3 
# 

prefiero esta solución porque:

  • los miembros consiguen declaradas en línea en lugar de después del hecho, al igual que con type(X).foo = ...
  • Los valores de los miembros se establecen en el código de la clase real en lugar de en el código de la metaclase.

todavía no es ideal porque:

  • tengo que fijar el __metaclass__ con el fin de const_value objetos sean interpretados correctamente.
  • Los const_value s no se "comportan" como los valores normales. Por ejemplo, no pude usarlo como un valor predeterminado para un parámetro a un método en la clase.
+2

¿Por qué alguien intentará establecer Foo.CLASS_PROPERTY? El estilo típico en Python es elegir la simplicidad sobre la planificación para evitar una instancia teórica donde alguien hace algo estúpido. No hay nada que puedas hacer para evitar que la gente haga cosas estúpidas con tu código. –

+0

@Mike: tienes razón; el sentido común le diría a las personas que no modifiquen estas propiedades. Y sin embargo, de vez en cuando, se modifican. Es por eso que me gusta que se deletree (más explícitamente que con mayúsculas). La misma lógica que usar miembros privados. Las interfaces explícitas son buenas. No me malinterpreten: me gusta la simplicidad también. Preferiría mucho si hubiera alguna instalación incorporada para esto (como un decorador @classproperty o algo así). Tal como están las cosas, ni siquiera estoy seguro de que use la solución anterior si requiere configurar '__metaclass__' cada vez. Las Mayores pueden ganar, por ahora. – mjumbewu

+2

Nota para las personas del futuro: las versiones modernas de Python pueden usar '@ property' en las clases de estilo nuevo: http://docs.python.org/library/functions.html#property –

Respuesta

1

encontrado 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 t.a 
     print t.b 

    def test2(): 
     t = ROClass() 
     t.a = 2 

    test1() 

Tenga en cuenta que si se intenta establecer un atributo de sólo lectura (t.a = 2) Python lanzará una AttributeError.

+0

Esto funciona para las propiedades de nivel de instancia, pero no nivel de clase (es decir, todavía no puedo decir 'ROClass.a' y espero que el valor sea' 1'). – mjumbewu

4

El ActiveState solution al 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í es una manera, basado en Raymond Hettinger's comment:

#!/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 la siguiente: Consideremos en primer lugar la solución de Raymond Hettinger:

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

Se muestra una forma relativamente sencilla de dar a la barra una lectura solo propiedad

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

Por lo tanto, si desea que la clase Foo tenga una propiedad de solo lectura, la clase padre de Foo debe tener CLASS_PROPERTY = property(lambda self: 1) definido.

La clase principal de una clase es una metaclase. Por lo tanto definimos ROType como el metaclase:

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

entonces hacemos la clase padre de Foo ser ROType:

class Foo(object): 
    __metaclass__=ROType 
+0

Ahora me siento como un idiota por no leer los comentarios cuidadosamente>. < –

8

Las soluciones existentes son un poco complejo - ¿qué pasa con el hecho de garantizar que cada clase en una determinada grupo tiene una metaclase única, luego establece 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 el hecho de que cuando llegue a los descriptores de atributo de una instancia se buscan en la clase (lo que, por supuesto, cuando llegue a los descriptores de atributo de una clase se miró en la metaclase), y hacer que la clase/metaclass sea única no es terriblemente difícil.

Ah, y por supuesto:

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

sólo para confirmar de sólo lectura Es verdad!

Cuestiones relacionadas