Подтвердить что ты не робот

Свойства только для чтения на уровне класса в Python

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

x = Foo.CLASS_PROPERTY

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

Foo.CLASS_PROPERTY = y

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

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
#

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

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

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

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

Ответ 1

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

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

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

Ответ 2

ActiveState solution, что ссылки 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

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

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

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

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

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

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

class Foo(object):
    __metaclass__=ROType

Ответ 3

Обнаружено это на 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.