Свойства только для чтения на уровне класса в Python - PullRequest
29 голосов
/ 14 ноября 2009

Есть ли способ сделать свойство уровня только для чтения в Python ? Например, если у меня есть класс Foo, я хочу сказать:

x = Foo.CLASS_PROPERTY

но не позволяйте никому говорить:

Foo.CLASS_PROPERTY = y

EDIT: Мне нравится простота решения Алекса Мартелли , но не синтаксис, который ему требуется. И его, и ответ ~ unutbu s вдохновили следующее решение, которое ближе к духу того, что я искал:

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

Теперь я получаю:

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
#

Я предпочитаю это решение, потому что:

  • Члены объявляются встроенными, а не после факта, как с type(X).foo = ...
  • Значения членов задаются в фактическом коде класса, а не в коде метакласса.

Это все еще не идеально, потому что:

  • Я должен установить __metaclass__ для правильной интерпретации const_value объектов.
  • const_value s не "ведут себя" как простые значения. Например, я не мог использовать его в качестве значения по умолчанию для параметра метода в классе.

Ответы [ 3 ]

9 голосов
/ 15 ноября 2009

Существующие решения немного сложны - как насчет того, чтобы гарантировать, что каждый класс в определенной группе имеет уникальный метакласс, а затем установить обычное свойство только для чтения в пользовательском метаклассе. А именно:

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

это действительно намного проще, потому что оно основано не более чем на том факте, что когда вы получаете дескрипторы атрибутов экземпляра, ищутся в классе (поэтому, конечно, когда вы получаете дескрипторы атрибутов класса, ищутся в метаклассе) и сделать класс / метакласс уникальным не так уж сложно.

О, и конечно:

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

просто чтобы подтвердить, что это действительно только для чтения!

4 голосов
/ 15 ноября 2009

Решение ActiveState , на которое ссылается Pynt, делает экземпляры ROClass доступными только для чтения. Похоже, ваш вопрос состоит в том, может ли сам класс иметь атрибуты только для чтения.

Вот один из способов, основанный на комментарии Раймона Хеттингера :

#!/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

Идея такова: рассмотрим первое решение Раймона Хеттингера:

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

Показывает относительно простой способ присвоить bar свойство только для чтения.

Обратите внимание, что вы должны добавить CLASS_PROPERTY = property(lambda self: 1) строка определения класса бара, а не самого бара.

Итак, если вы хотите, чтобы класс Foo имел свойство только для чтения, тогда родительский класс Foo должен иметь значение CLASS_PROPERTY = property(lambda self: 1).

Родительским классом класса является метакласс. Следовательно, мы определяем ROType как метакласс:

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

Затем мы делаем родительский класс Foo ROType:

class Foo(object):
    __metaclass__=ROType
0 голосов
/ 14 ноября 2009

Нашел это на 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()

Обратите внимание, что если вы попытаетесь установить атрибут только для чтения (t.a = 2), Python вызовет AttributeError.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...