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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

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

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

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

Использование @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 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

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

Where is read_only_properties coming from?

Рад, что вы спросили, вот источник для 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

update

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

$ 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

Ответ 4

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

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)

Ответ 5

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

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

Ответ 6

В то время как мне нравится декоратор класса от 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()

Ответ 7

Это мой обходной путь.

@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

Ответ 8

Я недоволен предыдущими двумя ответами на создание свойств только для чтения, поскольку первое решение позволяет удалить атрибут 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 __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__ также блокируется. Единственный способ изменить свойства только для чтения - это изменить методы самого класса.

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

а) Не позволяет добавлять метод в подкласс, который устанавливает или удаляет атрибут "только для чтения". Методу, определенному в подклассе, автоматически запрещается доступ к атрибуту только для чтения, даже при вызове версии метода для суперкласса.

b) Методы readonly класса могут быть изменены, чтобы обойти ограничения только для чтения.

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

Ответ благодарности Брайсу в Как получить имя класса вызывающей стороны внутри функции другого класса в python? для получения классов вызывающих и методов.

Ответ 9

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

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

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