Свойство Python только для чтения
Я не знаю, когда атрибут должен быть частным и если я должен использовать собственность.
Я недавно читал, что сеттеры и геттеры не подходящие для Python, и я должен использовать декоратор собственность. Все нормально.
но что, если у меня есть атрибут, который не должен быть установлен вне класса, но может быть прочитан (атрибут только для чтения). Должен ли этот атрибут быть частным, и под частным я имею в виду с подчеркиванием, как это self._x
?
Если да, то как я могу прочитать его без использования геттер?
Единственный метод, который я знаю прямо сейчас, это написать
@property
def x(self):
return self._x
таким образом, я могу прочитать атрибут obj.x
но я не могу установить его obj.x = 1
Так что все в порядке.
но должен ли я действительно заботиться о настройке объекта, который не должен быть установлен? Может быть, мне стоит просто оставить его. Но опять же я не могу использовать подчеркивание, потому что чтение obj._x
странно для пользователя, поэтому я должен использовать obj.x
и снова пользователь не знает, что он не должен установить этот атрибут.
каково Ваше мнение и практика?
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"