[python] Python 읽기 전용 속성

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

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

그러나 속성이 있으면 클래스 외부에서 설정해서는 안되지만 읽을 수는 있습니다 (읽기 전용 속성). 이 속성은 비공개 여야하고 비공개로하면 밑줄과 같이 의미 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)


답변

첫 번째 솔루션은 읽기 전용 속성을 삭제 한 다음 설정하고 __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__ 문제가 해결 되었습니다. 따라서 __dict__를 통해 속성을 변경할 수 없습니다. __dict__ 설정 또는 삭제도 차단됩니다. 읽기 전용 속성을 변경하는 유일한 방법은 클래스 자체의 메서드를 변경하는 것입니다.

내 솔루션이 이전 두 가지보다 낫다고 생각하지만 개선 될 수 있습니다. 다음은이 코드의 약점입니다.

a) 읽기 전용 속성을 설정하거나 삭제하는 하위 클래스의 메서드에 추가하는 것을 허용하지 않습니다. 서브 클래스에 정의 된 메서드는 메서드의 수퍼 클래스 버전을 호출하더라도 읽기 전용 속성에 액세스 할 수 없도록 자동으로 차단됩니다.

b) 클래스의 읽기 전용 메서드를 변경하여 읽기 전용 제한을 무효화 할 수 있습니다.

그러나 읽기 전용 속성을 설정하거나 삭제하기 위해 클래스를 편집하지 않고는 방법이 없습니다. 이것은 명명 규칙에 의존하지 않습니다. 파이썬이 명명 규칙과 그렇게 일치하지 않기 때문에 좋습니다. 이것은 클래스 자체를 편집하지 않고는 숨겨진 허점으로 변경할 수없는 읽기 전용 속성을 만드는 방법을 제공합니다. 데코레이터를 인수로 호출 할 때 읽기 전용 속성을 나열하면 읽기 전용이됩니다.

Python의 다른 클래스 함수 내에서 호출자 클래스 이름을 얻는 방법에 대한 Brice의 답변에 대한 크레딧은 무엇입니까? 호출자 클래스 및 메서드를 가져옵니다.


답변

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

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


답변

나는 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()