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


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

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

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

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

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

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

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

8   51  

8 ответов:

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

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

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

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

вот немного другой подход к свойствам только для чтения, которые, возможно, следует называть свойствами записи один раз, так как они должны быть инициализированы, не так ли? Для параноиков среди нас, которые беспокоятся о возможности изменения свойств путем прямого доступа к словарю объекта, я ввел "экстремальное" имя 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)

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

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

хотя мне нравится декоратор класса из 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 только для чтения, вы можете просто добавить:

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