Я не знаю, когда атрибут должен быть частным и следует ли использовать свойство.
Недавно я прочитал, что сеттеры и геттеры не являются питоническими, и я должен использовать декоратор свойств. Все нормально.
Но что, если у меня есть атрибут, который нельзя устанавливать извне класса, но можно прочитать (атрибут только для чтения). Должен ли этот атрибут быть частным, и под частным я имею в виду с подчеркиванием self._x
? Если да, то как я могу его прочитать без использования геттера? Единственный метод, который я знаю прямо сейчас, - это написать
@property
def x(self):
return self._x
Таким образом, я могу читать атрибут, obj.x
но не могу его установить, obj.x = 1
так что все в порядке.
Но должен ли я действительно заботиться об установке объекта, который нельзя устанавливать? Может мне стоит просто оставить это. Но опять же, я не могу использовать подчеркивание, потому что чтение obj._x
нечетное для пользователя, поэтому я должен использовать, obj.x
а затем снова пользователь не знает, что он не должен устанавливать этот атрибут.
Каково ваше мнение и практика?
источник
self.x
и верьте, что никто не изменитсяx
. Еслиx
важно убедиться, что это нельзя изменить, используйте свойство._x
это совсем не странно: условно это означает что-то «личное».object
, чтобы это действительно мешало вам устанавливатьobj.x
. В классе старого стиля вы все еще можете установитьobj.x
с довольно неожиданными результатами.Ответы:
Как правило, программы 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
источник
self.__a = []
вы все еще можете это сделать,a.a.append('anything')
и это будет работать.Вот способ избежать предположения, что
пожалуйста, посмотрите мое обновление ниже
Использование
@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 :
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
Обновить
Я никогда не ожидал, что этот ответ привлечет столько внимания. Удивительно, но это так. Это побудило меня создать пакет, который вы можете использовать.
в вашей оболочке 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
источник
if..elif..else
блок можно было простоif name in attrs and name in self.__dict__: raise Attr...
неpass
требовать. Проблема 1: все классы, оформленные таким образом, в конечном итоге идентичны__name__
, и строковое представление их типа также гомогенизировано. Проблема 2: это украшение перезаписывает любой кастом__setattr__
. Проблема 3: пользователи могут победить это с помощьюdel MyClass.__setattr__
.object.__setattr__(f, 'forbidden', 42)
. Я не вижу, чтоread_only_properties
добавляет, что не обрабатывается искажением имени двойного подчеркивания.Вот несколько иной подход к свойствам, доступным только для чтения, которые, возможно, следует называть свойствами однократной записи, поскольку они должны быть инициализированы, не так ли? Для параноиков из нас, которые беспокоятся о возможности изменять свойства путем прямого доступа к словарю объекта, я ввел "крайнее" искажение имен:
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)
источник
dict_name
вместо этого вы искажаете , например,dict_name = "_spam_" + name
это удаляет зависимость отuuid4
и значительно упрощает отладку.p.__dict__['_spam_x'] = 5
нужно изменить значениеp.x
, так что это не обеспечивает достаточного искажения имени.Я не удовлетворен двумя предыдущими ответами на создание свойств только для чтения, потому что первое решение позволяет удалить атрибут readonly, а затем установить и не блокирует __dict__. Второе решение можно обойти с помощью тестирования - найти значение, равное тому, что вы установили, и изменить его в конечном итоге.
Теперь о коде.
def final(cls): clss = cls @classmethod def __init_subclass__(cls, **kwargs): raise TypeError("type '{}' is not an acceptable base type".format(clss.__name__)) cls.__init_subclass__ = __init_subclass__ return cls def methoddefiner(cls, method_name): for clss in cls.mro(): try: getattr(clss, method_name) return clss except(AttributeError): pass return None def readonlyattributes(*attrs): """Method to create readonly attributes in a class Use as a decorator for a class. This function takes in unlimited string arguments for names of readonly attributes and returns a function to make the readonly attributes readonly. The original class's __getattribute__, __setattr__, and __delattr__ methods are redefined so avoid defining those methods in the decorated class You may create setters and deleters for readonly attributes, however if they are overwritten by the subclass, they lose access to the readonly attributes. Any method which sets or deletes a readonly attribute within the class loses access if overwritten by the subclass besides the __new__ or __init__ constructors. This decorator doesn't support subclassing of these classes """ def classrebuilder(cls): def __getattribute__(self, name): if name == '__dict__': from types import MappingProxyType return MappingProxyType(super(cls, self).__getattribute__('__dict__')) return super(cls, self).__getattribute__(name) def __setattr__(self, name, value): if name == '__dict__' or name in attrs: import inspect stack = inspect.stack() try: the_class = stack[1][0].f_locals['self'].__class__ except(KeyError): the_class = None the_method = stack[1][0].f_code.co_name if the_class != cls: if methoddefiner(type(self), the_method) != cls: raise AttributeError("Cannot set readonly attribute '{}'".format(name)) return super(cls, self).__setattr__(name, value) def __delattr__(self, name): if name == '__dict__' or name in attrs: import inspect stack = inspect.stack() try: the_class = stack[1][0].f_locals['self'].__class__ except(KeyError): the_class = None the_method = stack[1][0].f_code.co_name if the_class != cls: if methoddefiner(type(self), the_method) != cls: raise AttributeError("Cannot delete readonly attribute '{}'".format(name)) return super(cls, self).__delattr__(name) clss = cls cls.__getattribute__ = __getattribute__ cls.__setattr__ = __setattr__ cls.__delattr__ = __delattr__ #This line will be moved when this algorithm will be compatible with inheritance cls = final(cls) return cls return classrebuilder def setreadonlyattributes(cls, *readonlyattrs): return readonlyattributes(*readonlyattrs)(cls) if __name__ == '__main__': #test readonlyattributes only as an indpendent module @readonlyattributes('readonlyfield') class ReadonlyFieldClass(object): def __init__(self, a, b): #Prevent initalization of the internal, unmodified PrivateFieldClass #External PrivateFieldClass can be initalized self.readonlyfield = a self.publicfield = b attr = None def main(): global attr pfi = ReadonlyFieldClass('forbidden', 'changable') ###---test publicfield, ensure its mutable---### try: #get publicfield print(pfi.publicfield) print('__getattribute__ works') #set publicfield pfi.publicfield = 'mutable' print('__setattr__ seems to work') #get previously set publicfield print(pfi.publicfield) print('__setattr__ definitely works') #delete publicfield del pfi.publicfield print('__delattr__ seems to work') #get publicfield which was supposed to be deleted therefore should raise AttributeError print(pfi.publlicfield) #publicfield wasn't deleted, raise RuntimeError raise RuntimeError('__delattr__ doesn\'t work') except(AttributeError): print('__delattr__ works') try: ###---test readonly, make sure its readonly---### #get readonlyfield print(pfi.readonlyfield) print('__getattribute__ works') #set readonlyfield, should raise AttributeError pfi.readonlyfield = 'readonly' #apparently readonlyfield was set, notify user raise RuntimeError('__setattr__ doesn\'t work') except(AttributeError): print('__setattr__ seems to work') try: #ensure readonlyfield wasn't set print(pfi.readonlyfield) print('__setattr__ works') #delete readonlyfield del pfi.readonlyfield #readonlyfield was deleted, raise RuntimeError raise RuntimeError('__delattr__ doesn\'t work') except(AttributeError): print('__delattr__ works') try: print("Dict testing") print(pfi.__dict__, type(pfi.__dict__)) attr = pfi.readonlyfield print(attr) print("__getattribute__ works") if pfi.readonlyfield != 'forbidden': print(pfi.readonlyfield) raise RuntimeError("__getattr__ doesn't work") try: pfi.__dict__ = {} raise RuntimeError("__setattr__ doesn't work") except(AttributeError): print("__setattr__ works") del pfi.__dict__ raise RuntimeError("__delattr__ doesn't work") except(AttributeError): print(pfi.__dict__) print("__delattr__ works") print("Basic things work") main()
Нет смысла создавать атрибуты только для чтения, кроме случаев, когда вы пишете код библиотеки, код, который распространяется другим как код для использования для улучшения их программ, а не код для каких-либо других целей, таких как разработка приложений. Проблема __dict__ решена, потому что __dict__ теперь относится к неизменяемым типам. MappingProxyType , поэтому атрибуты нельзя изменить с помощью __dict__. Установка или удаление __dict__ также заблокировано. Единственный способ изменить свойства, доступные только для чтения, - это изменить методы самого класса.
Хотя я считаю, что мое решение лучше, чем из двух предыдущих, его можно улучшить. Вот слабые стороны этого кода:
a) Не позволяет добавлять в метод в подклассе, который устанавливает или удаляет атрибут только для чтения. Методу, определенному в подклассе, автоматически запрещается доступ к атрибуту только для чтения, даже при вызове версии метода суперкласса.
б) Методы класса только для чтения можно изменить, чтобы обойти ограничения только для чтения.
Однако без редактирования класса невозможно установить или удалить атрибут только для чтения. Это не зависит от соглашений об именах, и это хорошо, потому что Python не очень согласуется с соглашениями об именах. Это дает возможность сделать атрибуты только для чтения, которые нельзя изменить с помощью скрытых лазеек без редактирования самого класса. Просто укажите атрибуты, которые будут доступны только для чтения при вызове декоратора в качестве аргументов, и они станут доступны только для чтения.
Кредит на ответ Брайса в разделе Как получить имя класса вызывающего абонента внутри функции другого класса в Python? для получения классов и методов вызывающего объекта.
источник
object.__setattr__(pfi, 'readonly', 'foobar')
нарушает это решение, не редактируя сам класс.Обратите внимание, что методы экземпляра также являются атрибутами (класса), и вы можете установить их на уровне класса или экземпляра, если действительно хотите быть крутым. Или что вы можете установить переменную класса (которая также является атрибутом класса), где удобные свойства, доступные только для чтения, не будут работать аккуратно из коробки. Я пытаюсь сказать, что проблема с атрибутом «только для чтения» носит более общий характер, чем обычно кажется. К счастью, существуют общепринятые ожидания, которые настолько сильны, что ослепляют нас по отношению к этим другим случаям (в конце концов, почти все является каким-то атрибутом в python).
Основываясь на этих ожиданиях, я думаю, что наиболее общий и легкий подход состоит в том, чтобы принять соглашение о том, что «общедоступные» (без начального подчеркивания) атрибуты доступны только для чтения, за исключением случаев, когда они явно задокументированы как доступные для записи. Это подразумевает обычное ожидание того, что методы не будут исправлены, а переменные класса, указывающие значения по умолчанию для экземпляра, лучше не говорить. Если вы чувствуете себя действительно параноиком по поводу какого-то специального атрибута, используйте дескриптор только для чтения в качестве последней меры ресурса.
источник
Хотя мне нравится декоратор классов из Oz123, вы также можете сделать следующее, в котором используется явная оболочка класса и __new__ с помощью метода Factory класса, возвращающего класс внутри замыкания:
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
источник
кто-то упомянул об использовании прокси-объекта, я не видел примера этого, поэтому в конце концов попробовал его, [плохо].
/! \ По возможности предпочитайте определения классов и конструкторы классов
этот код эффективно переписывается
class.__new__
(конструктор класса), за исключением худшего во всех отношениях. Избавьте себя от боли и по возможности не используйте эту схему.def attr_proxy(obj): """ Use dynamic class definition to bind obj and proxy_attrs. If you can extend the target class constructor that is cleaner, but its not always trivial to do so. """ proxy_attrs = dict() class MyObjAttrProxy(): def __getattr__(self, name): if name in proxy_attrs: return proxy_attrs[name] # overloaded return getattr(obj, name) # proxy def __setattr__(self, name, value): """ note, self is not bound when overloading methods """ proxy_attrs[name] = value return MyObjAttrProxy() myobj = attr_proxy(Object()) setattr(myobj, 'foo_str', 'foo') def func_bind_obj_as_self(func, self): def _method(*args, **kwargs): return func(self, *args, **kwargs) return _method def mymethod(self, foo_ct): """ self is not bound because we aren't using object __new__ you can write the __setattr__ method to bind a self argument, or declare your functions dynamically to bind in a static object reference. """ return self.foo_str + foo_ct setattr(myobj, 'foo', func_bind_obj_as_self(mymethod, myobj))
источник
Я знаю, что воскресил эту ветку из мертвых, но я искал, как сделать свойство доступным только для чтения, и после того, как нашел эту тему, меня не удовлетворили уже опубликованные решения.
Итак, возвращаясь к первоначальному вопросу, если вы начнете с этого кода:
@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"
источник
AttributeError('can't set attribute')
)