클래스에서 디스크립터를 설정하면 디스크립터를 덮어 쓰는 이유는 무엇입니까?


10

간단한 재현 :

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

class B(object):
    v = VocalDescriptor()

B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor

이 질문에는 효과적인 복제본 이 있지만 복제본에 대한 답변은 없었으며 학습 연습으로 CPython 소스를 조금 더 파헤 쳤습니다. 경고 : 나는 잡초에 들어갔다. 나는 그 물을 알고있는 선장 으로부터 도움을받을 수 있기를 정말로 바라고 있다 . 나는 내 자신의 미래의 이익과 미래 독자들의 이익을 위해 내가보고 있던 전화를 추적하면서 가능한 한 명시 적으로 노력했다.

__getattribute__예를 들어 조회 우선 순위와 같은 설명자 에 적용되는 동작으로 인해 많은 잉크가 쏟아지는 것을 보았습니다 . 아래의 "Descriptors 호출"에 있는 Python 스 니펫은 For classes, the machinery is in type.__getattribute__()...필자가 생각하는 해당 CPython 소스 에 대해 대략적으로 동의합니다 type_getattro. "tp_slots" 를 보고 추적 한 다음 tp_getattro가 채워집니다 . 그리고 B.v처음에 인쇄 한다는 사실이 __get__, obj=None, objtype=<class '__main__.B'>의미가 있습니다.

내가 이해하지 못하는 것은 할당 B.v = 3이 트리거하기보다는 설명자를 맹목적으로 덮어 쓰는 이유는 v.__set__무엇입니까? 나는에서 한 번 더 시작하는 CPython의 호출을 추적하는 시도 "tp_slots" 다음보고, tp_setattro가 채워되는 경우 다음을보고, type_setattro . type_setattro 것으로 보인다 주위에 얇은 래퍼 _PyObject_GenericSetAttrWithDict . : 그리고 내 혼란의 핵심 거기에 _PyObject_GenericSetAttrWithDict있는 것 같습니다 기술자의에 우선 순위를 부여 논리 __set__방법 ! 이를 염두에두고 트리거하지 않고 B.v = 3맹목적으로 덮어 쓰는 이유를 알 수 없습니다 .vv.__set__

면책 조항 1 : printfs를 사용하여 소스에서 Python을 다시 작성하지 않았기 때문에 type_setattro에서 호출되는 것이 확실하지 않습니다 B.v = 3.

고지 사항 2 : VocalDescriptor"일반"또는 "권장"설명자 정의를 예로 들지 않습니다. 메소드가 호출되는 시점을 알려주는 것은 장황한 일이 아닙니다.


1
나를 위해 이것은 마지막 줄에 3을 인쇄합니다 ... 코드는 잘 작동합니다
Jab

3
디스크립터 는 클래스 자체가 아닌 인스턴스 에서 속성에 액세스 할 때 적용됩니다 . 나에게 미스터리는 왜 그렇지 않은 __get__것이 아니라 전혀 효과 __set__가 없었습니다.
jasonharper

1
@Jab OP는 여전히 __get__메소드를 호출 할 것으로 예상합니다 . B.v = 3로 속성을 효과적으로 덮어 썼습니다 int.
r.ook

2
@jasonharper 속성 액세스 는 인스턴스 또는 클래스를 사용할 때 호출 여부 __get__와 기본 구현 object.__getattribute__type.__getattribute__호출을 결정합니다 __get__. via를 할당하는__set__ 것은 인스턴스 전용입니다.
chepner

@jasonharper 나는 __get__클래스 자체에서 호출 될 때 디스크립터의 메소드가 트리거되어야한다고 생각합니다. 사용법 안내서에 따라 @classmethods 및 @staticmethods가 구현되는 방식 입니다. @Jab 왜 B.v = 3클래스 설명자를 덮어 쓸 수 있는지 궁금 합니다. CPython 구현을 기반으로 나는 B.v = 3또한 트리거 할 것으로 예상 했다 __set__.
Michael Carilli

답변:


6

B.v = 3디스크립터를 정수로 덮어 쓰는 것이 맞습니다 (필요한대로).

위해 B.v = 3기술자를 호출하기 위해, 기술자은 (는) 즉 메타 클래스에 정의되어 있어야합니다 type(B).

>>> class BMeta(type): 
...     v = VocalDescriptor() 
... 
>>> class B(metaclass=BMeta): 
...     pass 
... 
>>> B.v = 3 
__set__

on 설명자를 호출하려면 B인스턴스를 사용하면 B().v = 3됩니다.

B.v게터 를 호출하는 이유 는 디스크립터 인스턴스 자체를 반환 할 수 있기 때문입니다 . 일반적으로 클래스 객체를 통해 디스크립터에 대한 액세스를 허용하려면 다음을 수행하십시오.

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        if obj is None:
            return self
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

이제 상호 작용할 수있는 B.v인스턴스를 반환 <mymodule.VocalDescriptor object at 0xdeadbeef>합니다. 문자 그대로 디스크립터 객체이며 클래스 속성으로 정의되며의 상태 B.v.__dict__는의 모든 인스턴스간에 공유 B됩니다.

물론 원하는 B.v것을 정확하게 정의하는 것은 사용자의 코드에 달려 self있습니다. 반환 은 일반적인 패턴입니다.


1
이 답변을 완료하려면 __get__인스턴스 속성 또는 클래스 속성 __set__으로 호출되도록 설계 되었지만 인스턴스 속성으로 만 호출되도록 설계했습니다. 그리고 관련 문서 : docs.python.org/3/reference/datamodel.html#object.__get__
sanyash

@wim 장엄한 !! 동시에 나는 type_setattro 호출 체인을 다시 한번보고있었습니다. _PyObject_GenericSetAttrWithDict에 대한 호출 이 유형을 제공한다는 것을 알았 습니다 (필자의 경우 B 지점에서).
Michael Carilli

_PyObject_GenericSetAttrWithDict에서 B의 Py_TYPE을 as로 가져옵니다.tp 이 경우 B의 메타 클래스 ( type내 경우)이며 설명자 단락 논리에 의해 처리되는 메타 클래스 tp 입니다 . 직접에 정의 된 기술자가 그래서 되지 않은 그 단락 논리에 의해 볼 (그러므로 내 원래 코드에서 호출되지 않습니다)하지만, 메타 클래스에 정의 된 기술자가 되어 단락 논리에 의해 본. B __set__
Michael Carilli

따라서 메타 클래스에 설명자가있는 경우 __set__해당 설명 자의 메소드 호출됩니다.
Michael Carilli

@sanyash 자유롭게 편집 할 수 있습니다.
wim

3

재정의를 금지하는 B.v것은에 해당하는 type.__getattribute__(B, "v")반면 b = B(); b.v에는 같습니다 object.__getattribute__(b, "v"). 두 정의 모두 정의 __get__된 경우 결과 의 메소드를 호출합니다 .

에 대한 호출 __get__은 각각의 경우 에 다르다는 점에 유의하십시오 . B.v통과 None하는 동안, 첫 번째 인수로 B().v인스턴스 자체를 전달합니다. 두 경우 모두 두 B번째 인수로 전달됩니다.

B.v = 3반면에,와 같습니다 type.__setattr__(B, "v", 3), 어떤 작업을 수행 하지 호출 __set__.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.