__get__ 및 __set__ 및 Python 설명자 이해


310

나는 노력 들이 유용 무엇 파이썬의 설명을하고 이해하는. 나는 그들이 어떻게 작동하는지 이해하지만 여기에 내 의심이 있습니다. 다음 코드를 고려하십시오.

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)


class Temperature(object):
    celsius = Celsius()
  1. 디스크립터 클래스가 필요한 이유는 무엇입니까?

  2. 무엇 instanceowner여기에? (에서 __get__). 이 매개 변수의 목적은 무엇입니까?

  3. 이 예제를 어떻게 호출 / 사용합니까?

답변:


147

디스크립터는 파이썬 property타입이 구현되는 방식입니다. 설명자 단순히 구현은 __get__, __set__, 등 다음 (당신은 온도 클래스 위처럼)의 정의에 다른 클래스에 추가됩니다. 예를 들면 다음과 같습니다.

temp=Temperature()
temp.celsius #calls celsius.__get__

디스크립터에 할당 한 속성에 액세스하면 ( celsius위의 예에서) 적절한 디스크립터 메서드가 호출됩니다.

instancein __get__은 클래스의 인스턴스입니다 (위와 같이, __get__receive) temp, owner디스크립터가있는 클래스는 (그래서 Temperature)입니다.

이를 구현하는 논리를 캡슐화하려면 설명자 클래스를 사용해야합니다. 이렇게하면 디스크립터를 사용하여 값 비싼 조작 (예를 들어)을 캐시하는 경우 클래스가 아닌 자체 값을 저장할 수 있습니다.

설명자에 대한 기사는 여기 에서 찾을 수 있습니다 .

편집 : jchl이 의견에서 지적했듯이 단순히 시도 Temperature.celsius하면 instance됩니다 None.


6
차이 무엇 selfinstance?
Lemma Prism

2
'인스턴스'는 모든 클래스의 인스턴스 일 수 있으며, 자체는 동일한 클래스의 인스턴스입니다.
TheBeginner

3
@LemmaPrism self은 디스크립터 인스턴스이며, 디스크립터 instance가있는 클래스 (인스턴스화 된 경우)의 인스턴스입니다 ( instance.__class__ is owner).
Tcll

Temperature.celsius0.0코드에 따라 값을 제공합니다 celsius = Celsius(). 디스크립터 Celsius가 호출되므로 해당 인스턴스 0.0의 온도 클래스 속성 인 celsius에 지정된 초기화 값이 있습니다.
Angel Salazar

109

디스크립터 클래스가 필요한 이유는 무엇입니까?

속성 작동 방식을 추가로 제어 할 수 있습니다. 예를 들어 Java에서 getter 및 setter에 익숙한 경우 Python의 방식입니다. 한 가지 장점은 사용자가 속성처럼 보이게한다는 것입니다 (구문에는 변화가 없습니다). 따라서 일반적인 속성으로 시작한 다음 멋진 작업을 수행해야 할 경우 설명 자로 전환하십시오.

속성은 변경 가능한 값입니다. 디스크립터를 사용하면 값을 읽거나 설정 (또는 삭제) 할 때 임의 코드를 실행할 수 있습니다. 따라서 속성을 사용하여 속성을 데이터베이스의 필드 (예 : 일종의 ORM)에 매핑 할 수 있습니다.

또 다른 용도 __set__는 "속성"을 읽기 전용으로 만드는 예외를 던져서 새로운 가치를 받아들이기를 거부 할 수 있습니다 .

무엇 instanceowner여기에? (에서 __get__). 이 매개 변수의 목적은 무엇입니까?

이것은 매우 미묘합니다 (그리고 여기에 새로운 답변을 쓰는 ​​이유-나는 똑같은 것을 궁금해 하면서이 질문을 찾았고 기존 답변을 그다지 찾지 못했습니다).

디스크립터는 클래스에 정의되어 있지만 일반적으로 인스턴스에서 호출됩니다. 그것은 인스턴스에서 모두라고 할 때 instanceowner설정 (당신은 해결할 수 owner에서 instance이 좀 무의미한 것 같다 그래서). 그러나 클래스에서 호출 될 때만 owner설정됩니다. 이것이 바로 그 이유입니다.

이것은 __get__클래스에서 호출 할 수있는 유일한 것이기 때문에 필요합니다 . 클래스 값을 설정하면 설명자 자체를 설정하게됩니다. 마찬가지로 삭제합니다. 이것이 owner필요하지 않은 이유 입니다.

이 예제를 어떻게 호출 / 사용합니까?

비슷한 클래스를 사용하는 멋진 트릭이 있습니다.

class Celsius:

    def __get__(self, instance, owner):
        return 5 * (instance.fahrenheit - 32) / 9

    def __set__(self, instance, value):
        instance.fahrenheit = 32 + 9 * value / 5


class Temperature:

    celsius = Celsius()

    def __init__(self, initial_f):
        self.fahrenheit = initial_f


t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)

(저는 Python 3을 사용하고 있습니다 .python 2의 경우 해당 구분이 / 5.0및 인지 확인해야합니다 / 9.0) 그 결과는 다음과 같습니다.

100.0
32.0

이제 파이썬에서 동일한 효과를 얻는 다른 더 좋은 방법이 있습니다 (예 : 섭씨가 동일한 기본 메커니즘이지만 모든 소스를 Temperature 클래스에 배치하는 속성). 그러나 수행 할 수있는 작업을 보여줍니다 ...


2
변환이 잘못되었습니다. C = 5 (F−32) / 9, F = 32 + 9C / 5 여야합니다.
musiphil 2016 년

1
온도 개체가 하나 있는지 확인하십시오. 다음을 수행하면 물건이 엉망이됩니다. t1 = 온도 (190) print t1.celsius t1.celsius = 100 print t1.fahrenheit 이제 t.celcius와 t.fahrenheit를 확인하면 수정됩니다. t.celcius는 115이고 t.fahrenheit는 32입니다. 이것은 분명히 잘못되었습니다. @Eric
Ishan Bhatt

1
@IshanBhatt : 위의 musiphil이 지적한 오류 때문이라고 생각합니다. 또한 이것은 내 대답이 아닙니다
Eric

69

파이썬의 설명자가 무엇이며 유용 할 수있는 것을 이해하려고합니다.

디스크립터는 다음과 같은 특수 메소드가있는 클래스 속성 (예 : 특성 또는 메소드)입니다.

  • __get__ (비 데이터 디스크립터 방법 (예 : 메소드 / 함수))
  • __set__ (예 : 속성 인스턴스의 데이터 설명자 방법)
  • __delete__ (데이터 디스크립터 방법)

이 디스크립터 오브젝트는 다른 오브젝트 클래스 정의의 속성으로 사용될 수 있습니다. (즉, 그들은__dict__ , 클래스 객체 있습니다.)

디스크립터 객체를 사용하여 점선 조회 결과를 프로그래밍 방식으로 관리 할 수 ​​있습니다 (예 : foo.descriptor 정규 표현식, 대입, 심지어 삭제에서 .

기능 / 방법, 결합 방법 property, classmethodstaticmethod 모든 사용들은이 점 조회를 통해 액세스하는 방법을 제어하는이 특별한 방법.

데이터 기술자 와 같은property , 인스턴스가 각 가능한 속성을 미리 계산하는 경우보다 적은 메모리를 사용할 수 있도록 개체의 간단한 상태에 따라 속성의 게으른 평가를 할 수 있습니다.

member_descriptor의해 생성 된 다른 데이터 디스크립터__slots__ 는 클래스가보다 유연하지만 공간을 소비하는 대신 가변 터플과 유사한 데이터 구조에 데이터를 저장할 수 있도록하여 메모리를 절약 할 수 __dict__있습니다.

비 데이터 디스크립터 (일반적으로 인스턴스, 클래스 및 정적 메소드)는 내재 된 첫 번째 인수 (일반적으로 이름이 지정됨)를 가져옵니다. clsself )는 비 데이터 디스크립터 메소드 ()에서 각각 및 )를 가져옵니다 __get__.

대부분의 Python 사용자는 간단한 사용법 만 배울 필요가 있으며 설명 자의 구현을 더 배우거나 이해할 필요가 없습니다.

심도있는 내용 : 설명자는 무엇입니까?

디스크립터는 다음 방법 ( __get__, __set__또는 __delete__) 중 하나를 가진 객체로 , 인스턴스의 일반적인 속성 인 것처럼 점으로 찾아보기를 통해 사용됩니다. 소유자 - 객체의 경우, obj_instance, A의 descriptor객체 :

  • obj_instance.descriptor를 발동
    descriptor.__get__(self, obj_instance, owner_class)돌아 오는 value
    이 방법을 모든 방법이며, get속성 작업에.

  • obj_instance.descriptor = value
    descriptor.__set__(self, obj_instance, value)반환을 호출합니다. None
    이것은 setter속성이 작동하는 방식입니다.

  • del obj_instance.descriptor
    descriptor.__delete__(self, obj_instance)반환을 호출합니다. None
    이것은 deleter속성이 작동하는 방식입니다.

obj_instance클래스가 디스크립터 객체의 인스턴스를 포함하는 인스턴스입니다. 디스크립터self 의 인스턴스입니다 (아마도 클래스의 클래스 일 것입니다 )obj_instance

코드로 이것을 정의하기 위해 객체의 속성 세트가 필요한 속성과 교차하는 경우 객체는 디스크립터입니다.

def has_descriptor_attrs(obj):
    return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))

def is_descriptor(obj):
    """obj can be instance of descriptor or the descriptor class"""
    return bool(has_descriptor_attrs(obj))

데이터 설명자 가지고 __set__및 / 또는 __delete__. 비 - 데이터 - 기술자는 어느 쪽도 없습니다 아니다 .
__set____delete__

def has_data_descriptor_attrs(obj):
    return set(['__set__', '__delete__']) & set(dir(obj))

def is_data_descriptor(obj):
    return bool(has_data_descriptor_attrs(obj))

내장 디스크립터 오브젝트 예제 :

  • classmethod
  • staticmethod
  • property
  • 일반적인 기능

비 데이터 디스크립터

우리는 볼 수 있습니다 classmethodstaticmethod비 - 데이터 - 설명자입니다 :

>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)

둘 다 __get__방법이 있습니다.

>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))

모든 함수는 비 데이터 디스크립터이기도합니다.

>>> def foo(): pass
... 
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)

데이터 설명자, property

그러나 property데이터 설명자입니다.

>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])

점선 조회 순서

이는 점으로 구분 된 조회의 조회 순서에 영향을주기 때문에 중요한 차이점 입니다.

obj_instance.attribute
  1. 먼저 위의 속성이 인스턴스 클래스의 데이터 디스크립터인지 확인합니다.
  2. 그렇지 않으면 속성이있는 경우, 그것은에서 볼 수 보이는 obj_instance'의__dict__ 다음,
  3. 결국 비 데이터 디스크립터로 넘어갑니다.

이 조회 순서의 결과는 함수 / 메소드와 같은 비 데이터 디스크립터 가 인스턴스에 의해 대체 될 수 있다는 것 입니다.

요약 및 다음 단계

우리는 디스크립터의와 오브젝트 것을 배웠습니다 __get__, __set__또는이 __delete__. 이 디스크립터 오브젝트는 다른 오브젝트 클래스 정의의 속성으로 사용될 수 있습니다. 이제 코드를 예로 들어 어떻게 사용되는지 살펴 보겠습니다.


문제의 코드 분석

다음은 코드와 각 질문에 대한 답변입니다.

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Temperature(object):
    celsius = Celsius()
  1. 디스크립터 클래스가 필요한 이유는 무엇입니까?

디스크립터는이 클래스 속성에 대해 항상 float를 갖도록하고 속성 을 삭제하는 데 Temperature사용할 수 없도록 del합니다.

>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

그렇지 않으면 디스크립터는 소유자 클래스와 소유자 인스턴스를 무시하고 대신 디스크립터에 상태를 저장합니다. 간단한 클래스 속성으로 모든 인스턴스에서 상태를 쉽게 공유 할 수 있습니다 (항상 클래스에 float로 설정하고 삭제하지 않거나 코드 사용자에게 익숙한 경우).

class Temperature(object):
    celsius = 0.0

이렇게하면 예제와 정확히 동일한 동작을 얻을 수 있지만 (아래 질문 3에 대한 응답 참조) Pythons 내장 ( property)을 사용하며 더 관용적 인 것으로 간주됩니다.

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)
  1. 여기서 인스턴스와 소유자는 무엇입니까? ( 얻을 ). 이 매개 변수의 목적은 무엇입니까?

instance디스크립터를 호출하는 소유자의 인스턴스입니다. 소유자는 설명자 객체를 사용하여 데이터 포인트에 대한 액세스를 관리하는 클래스입니다. 보다 자세한 변수 이름은이 답변의 첫 번째 단락 옆에 설명자를 정의하는 특수 메소드에 대한 설명을 참조하십시오.

  1. 이 예제를 어떻게 호출 / 사용합니까?

데모는 다음과 같습니다.

>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>> 
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0

속성을 삭제할 수 없습니다 :

>>> del t2.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

그리고 부동으로 변환 할 수없는 변수를 할당 할 수 없습니다 :

>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02

그렇지 않으면 여기에있는 것은 모든 인스턴스에 대한 글로벌 상태이며, 이는 모든 인스턴스에 지정하여 관리됩니다.

대부분의 숙련 된 Python 프로그래머가이 결과를 달성 할 것으로 예상되는 방식은 property데코레이터 를 사용하는 것입니다. 데코레이터는 동일한 설명자를 사용하지만 위에서 정의한대로 소유자 클래스의 구현으로 동작을 가져옵니다.

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)

다음은 원래 코드 조각과 정확히 동일한 동작을합니다.

>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02

결론

디스크립터를 정의하는 속성, 데이터 디스크립터와 비 데이터 디스크립터의 차이점,이를 사용하는 내장 객체 및 사용에 대한 특정 질문을 다뤘습니다.

다시 질문의 예를 어떻게 사용 하시겠습니까? 나는 당신이하지 않기를 바랍니다. 첫 번째 제안 (간단한 클래스 특성)으로 시작하고 필요하다고 생각되면 두 번째 제안 (속성 데코레이터)으로 넘어 가기를 바랍니다.


1
니스, 나는이 대답에서 가장 많이 배웠습니다 (확실히 다른 사람들 에게서도 배웠습니다). 이 문장에 대한 질문 "가장 경험이 많은 Python 프로그래머가이 결과를 달성 할 것으로 예상되는 방법 ..." 명령문 전후에 정의한 Temeperature 클래스는 동일합니다. 내가 여기서 뭘보고 싶었어?
Yolo Voe

1
@YoloVoe 아니, 맞아, 나는 위의 반복임을 강조하기 위해 괄호로 쓰는 말을 추가했다.
Aaron Hall

1
이것은 놀라운 답변입니다. 몇 번 더 읽어야하는데 파이썬에 대한 이해가 몇 가지 노치에 부딪친 것 같습니다
Lucas Young

20

디스크립터의 세부 사항으로 들어가기 전에 Python에서 속성 조회가 작동하는 방식을 알아야합니다. 이것은 클래스에 메타 클래스가없고 기본 구현을 사용한다고 가정합니다 __getattribute__(둘 다 동작을 "사용자 정의"하는 데 사용될 수 있음).

이 경우의 속성 조회 (Python 3.x 또는 Python 2.x의 새 스타일 클래스)에 대한 가장 좋은 그림은 Python 메타 클래스 이해 (ionel의 코드 로그) 입니다. 이미지는 :"사용자 정의 할 수없는 속성 조회"대신 사용 됩니다.

이 속성의 조회 나타내는 foobarinstance의를 Class:

여기에 이미지 설명을 입력하십시오

여기서 두 가지 조건이 중요합니다.

  • 의 클래스는 경우 instance속성 이름에 대한 항목을 가지고 있으며, 그것은 가지고 __get____set__.
  • (가) 경우 instance가 없습니다 에는 속성 이름에 대한 항목이 있지만 클래스를 가지고 있으며이있다 __get__.

여기에 설명자가 들어옵니다.

  • 데이터 기술자 모두가 __get____set__.
  • 비 - 데이터 기술자 만 있습니다 __get__.

두 경우 모두 반환 된 값은 __get__인스턴스를 첫 번째 인수로, 클래스를 두 번째 인수로 호출합니다.

조회는 클래스 속성 조회에 대해 훨씬 더 복잡합니다 (예 : 위에서 언급 한 블로그에서 클래스 속성 조회 참조). ).

구체적인 질문으로 넘어 갑시다.

디스크립터 클래스가 필요한 이유는 무엇입니까?

대부분의 경우 설명자 클래스를 작성할 필요가 없습니다! 그러나 아마도 매우 일반적인 최종 사용자 일 것입니다. 예를 들어 기능. 함수는 설명자이므로 함수를 self암시 적으로 첫 번째 인수로 전달한 메서드로 함수를 사용할 수 있습니다 .

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

test_method인스턴스 를 조회 하면 "바운드 메소드"가 다시 나타납니다.

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

마찬가지로 __get__메소드를 수동으로 호출하여 함수를 바인딩 할 수도 있습니다 (실제로 권장하지는 않지만 설명 목적으로).

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

이 "자체 바운드 메서드"를 호출 할 수도 있습니다.

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

인수를 제공하지 않았으며 함수가 바인딩 한 인스턴스를 반환했습니다.

함수는 비 데이터 디스크립터입니다 !

데이터 디스크립터의 일부 내장 예제는 property입니다. 무시 getter, setter그리고 (에서 기술자는 기술자 하우투 가이드 "등록 정보" ) :deleterproperty

class Property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

당신은의 "이름"을 찾아 볼 때마다 데이터가 호출이야라는 표현 이래로 property과 장식 기능이 단순히 대표 @property, @name.setter@name.deleter(있는 경우).

표준 라이브러리에는 몇 가지 다른 디스크립터가 있습니다 (예 staticmethod:) classmethod.

디스크립터의 요점은 간단합니다 (필수는 거의 없지만). 속성 액세스를위한 추상 공통 코드. property인스턴스 변수 액세스에 function대한 추상화이며 메소드에 staticmethod대한 추상화를 제공하며 인스턴스 액세스가 필요하지 않은 메소드에 대한 추상화를 제공합니다.classmethod 액세스보다는 클래스 액세스가 필요한 메소드에 대한 추상화를 제공합니다 (조금 단순화 됨).

또 다른 예는 클래스 속성 입니다.

하나의 재미있는 예 ( __set_name__Python 3.6에서 사용 )는 특정 유형 만 허용하는 속성 일 수 있습니다.

class TypedProperty(object):
    __slots__ = ('_name', '_type')
    def __init__(self, typ):
        self._type = typ

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError(f"Expected class {self._type}, got {type(value)}")
        instance.__dict__[self._name] = value

    def __delete__(self, instance):
        del instance.__dict__[self._name]

    def __set_name__(self, klass, name):
        self._name = name

그런 다음 클래스에서 설명자를 사용할 수 있습니다.

class Test(object):
    int_prop = TypedProperty(int)

그리고 조금 연주 :

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

또는 "게으른 재산":

class LazyProperty(object):
    __slots__ = ('_fget', '_name')
    def __init__(self, fget):
        self._fget = fget

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        try:
            return instance.__dict__[self._name]
        except KeyError:
            value = self._fget(instance)
            instance.__dict__[self._name] = value
            return value

    def __set_name__(self, klass, name):
        self._name = name

class Test(object):
    @LazyProperty
    def lazy(self):
        print('calculating')
        return 10

>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10

논리를 공통 디스크립터로 이동하는 것이 의미가있는 경우이지만 다른 방법으로 논리를 해결할 수도 있습니다 (일부 코드를 반복하여).

무엇 instanceowner여기에? (에서 __get__). 이 매개 변수의 목적은 무엇입니까?

속성을 찾는 방법에 따라 다릅니다. 인스턴스에서 속성을 조회하면 다음이 수행됩니다.

  • 두 번째 인수는 속성을 찾는 인스턴스입니다.
  • 세 번째 인수는 인스턴스의 클래스입니다

클래스에서 속성을 조회하는 경우 (설명자가 클래스에 정의되어 있다고 가정) :

  • 두 번째 주장은 None
  • 세 번째 인수는 속성을 찾는 클래스입니다.

따라서 기본적으로 클래스 수준 조회를 수행 할 때 ( instanceis이므로 None) 동작을 사용자 정의하려면 세 번째 인수가 필요합니다 .

이 예제를 어떻게 호출 / 사용합니까?

귀하의 예제는 기본적으로 float클래스의 모든 인스턴스와 클래스에서 공유 할 수있는 값만 허용하는 속성 입니다. 클래스에서는 "읽기"액세스 만 사용할 수 있지만 그렇지 않으면 설명자 인스턴스를 대체합니다. ) :

>>> t1 = Temperature()
>>> t2 = Temperature()

>>> t1.celsius = 20   # setting it on one instance
>>> t2.celsius        # looking it up on another instance
20.0

>>> Temperature.celsius  # looking it up on the class
20.0

따라서 디스크립터는 일반적으로 두 번째 인수 ( instance)를 사용하여 값을 공유하지 않기 위해 값을 저장합니다. 그러나 어떤 경우에는 인스턴스간에 값을 공유해야 할 수도 있습니다 (현재 시나리오를 생각할 수는 없지만). 그러나 순수한 학문적 운동을 제외하고 온도 등급의 섭씨 속성에는 실제로 의미가 없습니다.


어두운 모드에서 실제로 고통받는 그래픽의 투명한 배경이 stackoverflow의 버그로보고되어야하는지 확실하지 않습니다.
Tshirtman

@ Tshirtman 나는 이것이 이미지 자체의 문제라고 생각합니다. 완전히 투명하지는 않습니다 ... 블로그 게시물을 가져 와서 투명한 배경으로 다시 만드는 방법을 모릅니다. 너무 나빠서 어두운 배경에서 너무 이상해 보입니다 :(
MSeifert

9

디스크립터 클래스가 필요한 이유는 무엇입니까?

Buciano Ramalho의 Fluent Python에서 영감을 받음

이런 클래스가 있습니다

class LineItem:
     price = 10.9
     weight = 2.1
     def __init__(self, name, price, weight):
          self.name = name
          self.price = price
          self.weight = weight

item = LineItem("apple", 2.9, 2.1)
item.price = -0.9  # it's price is negative, you need to refund to your customer even you delivered the apple :(
item.weight = -0.8 # negative weight, it doesn't make sense

음수를 할당하지 않기 위해 무게와 가격을 확인해야합니다.

class Quantity(object):
    __index = 0

    def __init__(self):
        self.__index = self.__class__.__index
        self._storage_name = "quantity#{}".format(self.__index)
        self.__class__.__index += 1

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self._storage_name, value)
        else:
           raise ValueError('value should >0')

   def __get__(self, instance, owner):
        return getattr(instance, self._storage_name)

그런 다음 LineItem 클래스를 다음과 같이 정의하십시오.

class LineItem(object):
     weight = Quantity()
     price = Quantity()

     def __init__(self, name, weight, price):
         self.name = name
         self.weight = weight
         self.price = price

보다 일반적인 검증을 위해 Quantity 클래스를 확장 할 수 있습니다.


1
디스크립터를 사용하여 여러 사용자 인스턴스와 상호 작용하는 방법을 보여주는 흥미로운 유스 케이스. 필자는 처음에 중요한 점을 이해하지 못했습니다. 디스크립터 있는 속성 은 클래스 네임 스페이스에서 작성 weight = Quantity()해야합니다 self(예 : ( self.weight = 4) 만 사용하여 인스턴스 네임 스페이스에 값을 설정해야 함 ). 그렇지 않으면 속성이 새 값으로 리바운드됩니다. 디스크립터는 폐기 될 것입니다 ..
mins

한 가지를 이해할 수 없습니다. 당신이 정의하는 weight = Quantity()클래스 변수와 같은 __get____set__인스턴스 변수에 노력하고 있습니다. 어떻게?
테크노 크라 트

0

Andrew Cooke의 답변에서 코드를 (사소한 변경으로) 시도했습니다. (파이썬 2.7을 실행 중입니다).

코드:

#!/usr/bin/env python
class Celsius:
    def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0
    def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0

class Temperature:
    def __init__(self, initial_f): self.fahrenheit = initial_f
    celsius = Celsius()

if __name__ == "__main__":

    t = Temperature(212)
    print(t.celsius)
    t.celsius = 0
    print(t.fahrenheit)

결과:

C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212

3 이전의 Python에서는 get 스타일이 이전 스타일 클래스에서 작동하지 않으므로 디스크립터가 올바르게 작동하도록 오브젝트에서 서브 클래스를 작성하십시오 .


1
설명자는 새 스타일 클래스에서만 작동합니다. 파이썬 파이썬 3의 기본값은 "개체"에서 클래스를 파생이 수단 2.x에서
이보 반 데르 Wijk

0

당신은 볼 것 https://docs.python.org/3/howto/descriptor.html#properties을

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

1
이것은 질문에 대답하지 않거나 유용한 정보를 제공하지 않습니다.
Sebastian Nielsen
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.