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

Я не знаю, когда атрибут должен быть частным и если я должен использовать собственность.

недавно я прочитал, что сеттеры и геттеры не питоны, и я должен использовать декоратор свойств. Все в порядке.

но что, если у меня есть атрибут, который не должен быть установлен вне класса, но может быть прочитан (атрибут только для чтения). Должен ли этот атрибут быть частным, и под частным я имею в виду с подчеркиванием, как это self._x? Если да, то как я могу прочитать его без использования геттер? Единственный метод, который я знаю прямо сейчас, - это написать

@property
def x(self):
    return self._x

таким образом, я могу прочитать атрибут obj.x но я не могу установить его obj.x = 1 Так что все в порядке.

но должен ли я действительно заботиться о настройке объекта, который не должен быть установлен? Может, мне лучше оставить это. Но опять же я не могу использовать подчеркивание, потому что чтение obj._x нечетно для пользователя, поэтому я должен использовать obj.x и снова пользователь не знает, что он не должен установить этот атрибут.

каково Ваше мнение и практика?

8 ответов


Как правило, программы Python должны быть написаны с предположением, что все пользователи являются совершеннолетними и, следовательно, несут ответственность за правильное использование вещей. Однако в редких случаях, когда просто не имеет смысла устанавливать атрибут (например, производное значение или значение, считанное из некоторого статического источника данных), свойство getter-only обычно является предпочтительным шаблоном.


просто мои два цента, Сайлас Рэй находится на правильном пути, однако мне захотелось добавить пример. ;-)

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

Per PEP 8:

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

иметь свойство "только для чтения" в класс, который вы можете использовать @property украшение, вам нужно наследовать от object когда вы это делаете, чтобы использовать классы нового стиля.

пример:

>>> class A(object):
...     def __init__(self, a):
...         self._a = a
...
...     @property
...     def a(self):
...         return self._a
... 
>>> a = A('test')
>>> a.a
'test'
>>> a.a = 'pleh'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

вот способ избежать предположения, что

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

пожалуйста, смотрите мое обновление ниже

используя @property, очень подробный, например:

   class AClassWithManyAttributes:
        '''refactored to properties'''
        def __init__(a, b, c, d, e ...)
             self._a = a
             self._b = b
             self._c = c
             self.d = d
             self.e = e

        @property
        def a(self):
            return self._a
        @property
        def b(self):
            return self._b
        @property
        def c(self):
            return self._c
        # you get this ... it's long

используя

нет подчеркивания: это открытая переменная.
Одно подчеркивание: это защищенная переменная.
Два подчеркивания: это частный переменная.

кроме последнего, это условность. Вы все еще можете, если вы действительно стараетесь, получить доступ к переменным с двойным подчеркиванием.

так что же нам делать? Мы сдаваться прочитав только свойства в Python?

увы, read_only_properties декоратор на помощь!

@read_only_properties('readonly', 'forbidden')
class MyClass(object):
    def __init__(self, a, b, c):
        self.readonly = a
        self.forbidden = b
        self.ok = c

m = MyClass(1, 2, 3)
m.ok = 4
# we can re-assign a value to m.ok
# read only access to m.readonly is OK 
print(m.ok, m.readonly) 
print("This worked...")
# this will explode, and raise AttributeError
m.forbidden = 4

ты спрашиваешь:

где read_only_properties идет?

рад, что вы спросили, вот источник read_only_properties:

def read_only_properties(*attrs):

    def class_rebuilder(cls):
        "The class decorator"

        class NewClass(cls):
            "This is the overwritten class"
            def __setattr__(self, name, value):
                if name not in attrs:
                    pass
                elif name not in self.__dict__:
                    pass
                else:
                    raise AttributeError("Can't modify {}".format(name))

                super().__setattr__(name, value)
        return NewClass
    return class_rebuilder

обновление

Я никогда не ожидал, что этот ответ получит столько внимания. Удивительно, но это так. Это побудило меня создать пакет, который вы можете использовать.

$ pip install read-only-properties

в вашей оболочке python:

In [1]: from rop import read_only_properties

In [2]: @read_only_properties('a')
   ...: class Foo:
   ...:     def __init__(self, a, b):
   ...:         self.a = a
   ...:         self.b = b
   ...:         

In [3]: f=Foo('explodes', 'ok-to-overwrite')

In [4]: f.b = 5

In [5]: f.a = 'boom'
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-a5226072b3b4> in <module>()
----> 1 f.a = 'boom'

/home/oznt/.virtualenvs/tracker/lib/python3.5/site-packages/rop.py in __setattr__(self, name, value)
    116                     pass
    117                 else:
--> 118                     raise AttributeError("Can't touch {}".format(name))
    119 
    120                 super().__setattr__(name, value)

AttributeError: Can't touch a

вот немного другой подход к свойствам только для чтения, которые, возможно, следует назвать свойствами write-once, поскольку они должны быть инициализированы, не так ли? Для параноиков среди нас, которые беспокоятся о возможности изменения свойств путем прямого доступа к словарю объекта, я ввел" экстремальное " имя mangling:

from uuid import uuid4

class Read_Only_Property:
    def __init__(self, name):
        self.name = name
        self.dict_name = uuid4().hex
        self.initialized = False

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return instance.__dict__[self.dict_name]

    def __set__(self, instance, value):
        if self.initialized:
            raise AttributeError("Attempt to modify read-only property '%s'." % self.name)
        instance.__dict__[self.dict_name] = value
        self.initialized = True

class Point:
    x = Read_Only_Property('x')
    y = Read_Only_Property('y')
    def __init__(self, x, y):
        self.x = x
        self.y = y

if __name__ == '__main__':
    try:
        p = Point(2, 3)
        print(p.x, p.y)
        p.x = 9
    except Exception as e:
        print(e)

обратите внимание, что методы экземпляра также являются атрибутами (класса) и что вы можете установить их на уровне класса или экземпляра, если вы действительно хотите быть задирой. Или что вы можете установить переменную класса (которая также является атрибутом класса), где удобные свойства readonly не будут работать аккуратно из коробки. Я пытаюсь сказать, что проблема" только для чтения " на самом деле более общая, чем обычно воспринимается. К счастью, есть обычные ожидания на работе которые настолько сильны, что ослепляют нас wrt эти другие случаи (в конце концов, почти все является атрибутом какого-то рода в python).

основываясь на этих ожиданиях, я думаю, что наиболее общий и легкий подход заключается в принятии Конвенции о том, что "публичные" (без ведущего подчеркивания) атрибуты читаются только тогда, когда явно задокументированы как записываемые. Это подводит обычное ожидание, что методы не будут исправлены, а переменные класса, указывающие значения по умолчанию экземпляра, лучше пусть один. Если вы чувствуете себя параноиком по поводу какого-то специального атрибута, используйте дескриптор readonly в качестве последней меры ресурса.


хотя мне нравится декоратор класса из Oz123, вы также можете сделать следующее, в котором используется явная оболочка класса и _ _ new__ с методом фабрики класса, возвращающим класс в закрытии:

class B(object):
    def __new__(cls, val):
        return cls.factory(val)

@classmethod
def factory(cls, val):
    private = {'var': 'test'}

    class InnerB(object):
        def __init__(self):
            self.variable = val
            pass

        @property
        def var(self):
            return private['var']

    return InnerB()

Это мое решение.

@property
def language(self):
    return self._language
@language.setter
def language(self, value):
    # WORKAROUND to get a "getter-only" behavior
    # set the value only if the attribute does not exist
    try:
        if self.language == value:
            pass
        print("WARNING: Cannot set attribute \'language\'.")
    except AttributeError:
        self._language = value

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

Итак, возвращаясь к первоначальному вопросу, если вы начинаете с этого кода:

@property
def x(self):
    return self._x

и вы хотите сделать X readonly, вы можете просто добавить:

@x.setter
def x(self, value):
    raise Exception("Member readonly")

затем, если вы выполните следующее:

print (x) # Will print whatever X value is
x = 3 # Will raise exception "Member readonly"