2009-11-14 5 views
25

Existe-t-il un moyen de créer une propriété en lecture seule au niveau de la classe dans Python? Par exemple, si j'ai une classe Foo, je veux dire:Propriétés en lecture seule au niveau de la classe en Python

x = Foo.CLASS_PROPERTY 

mais empêcher quiconque de dire:

Foo.CLASS_PROPERTY = y 

EDIT: J'aime la simplicité de Alex Martelli's solution, mais pas le syntaxe requise. Les deux son et ~unutbu's answer s inspire la solution suivante, qui est plus à l'esprit de ce que je cherchais:

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 

Maintenant, je reçois:

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 
# 

Je préfère cette solution parce que:

  • les membres se déclarent en ligne au lieu de après le fait, comme type(X).foo = ...
  • Les valeurs des membres sont définies dans le code de la classe réelle plutôt que dans le code de la métaclasse.

Il est toujours pas idéal parce que:

  • Je dois régler la __metaclass__ afin d'interpréter correctement const_value objets.
  • Les const_value ne se comportent pas comme les valeurs normales. Par exemple, je ne pouvais pas l'utiliser comme valeur par défaut pour un paramètre d'une méthode de la classe.
+2

Pourquoi quelqu'un aurait-il déjà essayé de définir Foo.CLASS_PROPERTY? C'est un style typique en Python de choisir la simplicité plutôt que la planification pour empêcher une instance théorique où quelqu'un fait quelque chose de stupide. Il n'y a rien que vous puissiez faire pour empêcher les gens de faire des trucs stupides avec votre code. –

+0

@Mike: Vous avez raison; le bon sens dirait aux gens de ne pas modifier ces propriétés. Et pourtant, de temps en temps, ils sont modifiés. C'est pourquoi j'aime bien l'épeler (plus explicitement qu'avec des majuscules). Même justification que l'utilisation de membres privés. Les interfaces explicites sont bonnes. Ne vous méprenez pas: j'aime aussi la simplicité. Je préférerais beaucoup s'il y avait une installation intégrée pour cela (comme un décorateur @classproperty ou quelque chose). Comme c'est le cas, je ne suis même pas sûr que j'utiliserais la solution ci-dessus si cela nécessite de régler le '__metaclass__' à chaque fois. Les casquettes peuvent gagner, pour l'instant. – mjumbewu

+2

Note pour les gens du futur: Les versions modernes de Python peuvent utiliser '@ property' dans les nouvelles classes: http://docs.python.org/library/functions.html#property –

Répondre

1

trouvé sur 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() 

Notez que si vous essayez de définir un python attribut de lecture seule (t.a = 2) soulèvera une AttributeError.

+0

Cela fonctionne pour les propriétés de niveau d'instance, mais pas class-level (ie je ne peux toujours pas dire 'ROClass.a' et je m'attends à ce que la valeur soit' 1'). – mjumbewu

4

Les références Pynt ActiveState solution font que les instances de ROClass ont des attributs en lecture seule. Votre question semble demander si la classe elle-même peut avoir des attributs en lecture seule.

est ici un moyen, basé sur 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 

L'idée est la suivante: Considérons d'abord la solution de Raymond Hettinger:

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

Il montre un moyen relativement simple de donner bar un en lecture seule propriété.

Notez que vous devez ajouter la ligne CLASS_PROPERTY = property(lambda self: 1) à la définition de la classe de barre, pas à la barre elle-même.Par conséquent, si vous souhaitez que la classe Foo ait une propriété en lecture seule, la classe Foo doit avoir la valeur CLASS_PROPERTY = property(lambda self: 1) définie.

La classe parent d'une classe est une métaclasse. Par conséquent, nous définissons ROType comme métaclasse:

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

Ensuite, nous faisons la classe parente de Foo être ROType:

class Foo(object): 
    __metaclass__=ROType 
+0

Maintenant, je me sens comme un idiot de ne pas lire attentivement les commentaires>. < –

8

Les solutions existantes sont un peu complexe - Qu'en est-il simplement faire en sorte que chaque classe dans une Le groupe possède une métaclasse unique, puis définit une propriété en lecture seule normale sur la métaclasse personnalisée. A savoir:

>>> 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 
>>> 

ce qui est vraiment beaucoup plus simple, car il est basé sur rien de plus que le fait que lorsque vous obtenez les descripteurs d'attributs d'une instance sont recherchés sur la classe (si bien sûr quand vous obtenez les descripteurs d'attributs d'une classe sont regardé sur la métaclasse), et rendre classe/métaclasse unique n'est pas terriblement difficile.

Oh, et bien sûr:

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

juste pour confirmer qu'il est bien en lecture seule!

Questions connexes