Programing

Python 읽기 전용 속성

lottogame 2020. 11. 1. 17:12
반응형

Python 읽기 전용 속성


속성이 언제 비공개 여야하는지, 속성을 사용해야하는지 모르겠습니다.

최근에 setter와 getter가 파이썬 적이 지 않으며 속성 데코레이터를 사용해야한다는 것을 읽었습니다. 괜찮아.

그러나 속성이 있으면 클래스 외부에서 설정해서는 안되지만 읽을 수는 있습니다 (읽기 전용 속성). 이 속성은 비공개 여야하고 비공개로하면 밑줄과 같이 의미 self._x합니까? 그렇다면 getter를 사용하지 않고 어떻게 읽을 수 있습니까? 지금 내가 아는 유일한 방법은

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

그렇게하면 속성을 읽을 수는 obj.x있지만 설정할 수 obj.x = 1없으므로 괜찮습니다.

하지만 설정하면 안되는 개체를 설정하는 데 정말 신경 써야할까요? 그냥 떠나야 할지도 몰라요. 하지만 다시 obj._x는 사용자에게 읽기 가 이상하기 때문에 밑줄을 사용할 수 없으므로 다시 사용해야 obj.x하며 사용자는이 속성을 설정하지 않아야한다는 것을 다시 알지 못합니다.

당신의 의견과 관행은 무엇입니까?


일반적으로 Python 프로그램은 모든 사용자가 성인에 동의한다는 가정하에 작성되어야하며, 따라서 스스로 올바르게 사용하는 책임이 있습니다. 그러나 속성을 설정할 수없는 드문 경우 (예 : 파생 값 또는 일부 정적 데이터 소스에서 읽은 값)는 일반적으로 getter 전용 속성이 선호되는 패턴입니다.


내 2 센트 인 Silas Ray 는 올바른 길을 가고 있지만 예제를 추가하고 싶었습니다. ;-)

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

사용

밑줄 없음 : 공개 변수입니다.
밑줄 하나 : 보호 된 변수입니다.
두 개의 밑줄 : 개인 변수입니다.

마지막을 제외하고는 컨벤션입니다. 정말 열심히 노력한다면 이중 밑줄로 변수에 액세스 할 수 있습니다.

그래서 우리는 무엇을합니까? 파이썬에서 읽기 전용 속성을 포기합니까?

보다! 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

파이썬 셸에서 :

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

다음은 읽기 전용 속성에 대한 약간 다른 접근 방식입니다. 초기화해야하기 때문에 한 번만 쓰기 속성이라고 할 수 있습니다. 그렇지 않나요? 객체의 사전에 직접 액세스하여 속성을 수정할 수 있는지 걱정하는 편집증 환자를 위해 "극단적 인"이름 변경을 도입했습니다.

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)

인스턴스 메서드도 (클래스의) 속성이며, 정말 나쁜 사람이되고 싶다면 클래스 또는 인스턴스 수준에서 설정할 수 있습니다. 또는 편리한 읽기 전용 속성이 즉시 작동하지 않는 클래스 변수 (클래스의 속성이기도 함)를 설정할 수도 있습니다. 내가 말하려는 것은 "읽기 전용 속성"문제가 실제로 일반적으로 인식되는 것보다 더 일반적이라는 것입니다. 다행히도 이러한 다른 경우에 대해 우리를 눈이 멀게 할 정도로 강한 기존의 기대가 있습니다 (결국 거의 모든 것이 파이썬에서 일종의 속성입니다).

이러한 기대를 바탕으로 가장 일반적이고 가벼운 접근 방식은 "공개"(선행 밑줄 없음) 속성이 쓰기 가능으로 명시 적으로 문서화되는 경우를 제외하고는 읽기 전용이라는 규칙을 채택하는 것이라고 생각합니다. 이것은 메소드가 패치되지 않을 것이라는 일반적인 기대를 포함하며 인스턴스 기본값을 나타내는 클래스 변수는 고사하고 더 좋습니다. 특별한 속성에 대해 정말로 편집증이라고 느끼면 읽기 전용 설명자를 마지막 리소스 측정으로 사용하십시오.


Oz123의 클래스 데코레이터를 좋아하지만, 클로저 내에서 클래스를 반환하는 클래스 Factory 메서드와 함께 명시 적 클래스 래퍼 및 __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

첫 번째 솔루션은 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()

There is no point to making read only attributes except when your writing library code, code which is being distributed to others as code to use in order to enhance their programs, not code for any other purpose, like app development. The __dict__ problem is solved, because the __dict__ is now of the immutable types.MappingProxyType, so attributes cannot be changed through the __dict__. Setting or deleting __dict__ is also blocked. The only way to change read only properties is through changing the methods of the class itself.

Though I believe my solution is better than of the previous two, it could be improved. These are this code's weaknesses:

a) Doesn't allow adding to a method in a subclass which sets or deletes a readonly attribute. A method defined in a subclass is automatically barred from accessing a readonly attribute, even by calling the superclass' version of the method.

b) The class' readonly methods can be changed to defeat the read only restrictions.

However, there is not way without editing the class to set or delete a read only attribute. This isn't dependent on naming conventions, which is good because Python isn't so consistent with naming conventions. This provides a way to make read only attributes that cannot be changed with hidden loopholes without editing the class itself. Simply list the attributes to be read only when calling the decorator as arguments and they will become read only.

Credit to Brice's answer in How to get the caller class name inside a function of another class in python? for getting the caller classes and methods.


I know i'm bringing back from the dead this thread, but I was looking at how to make a property read only and after finding this topic, I wasn't satisfied with the solutions already shared.

So, going back to the initial question, if you start with this code:

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

And you want to make X readonly, you can just add:

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

Then, if you run the following:

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

참고URL : https://stackoverflow.com/questions/14594120/python-read-only-property

반응형