ONE QUESTION AT A TIME.

천천히 생각하고, 분명하게.

TODAY’S QUESTION1166 / 1500

클래스 함수가 인스턴스에서 bound method가 되는 과정과 클래스에서 읽을 때의 차이는 무엇인가요?

잠깐, 답변을 보기 전에
먼저 내 말로 설명해 볼까요?

답변과 점검 포인트 펼치기답변 접기
01

구두 답변

정의를 외우기보다, 원리와 조건을 연결해 설명해 보세요.

클래스 본문에서 정의한 함수는 함수 객체이면서 non-data descriptor입니다. c.f를 읽으면 함수의 __get__(c, C)가 호출되어 함수와 인스턴스 c를 묶은 bound method가 만들어집니다. 그래서 self를 호출자가 다시 넘기지 않아도 됩니다. 반면 C.f에서는 descriptor의 instance 인자가 None이므로 원 함수에 가까운 객체가 반환되고, C.f(c, 3)처럼 receiver를 직접 전달해야 합니다.

class Counter:
    def add(self, n): return self.base + n
c = Counter(); c.base = 7
bound = c.add
raw = Counter.add
print(bound(5))       # 12
print(raw(c, 5))      # 12
print(bound.__self__ is c) # True

T0에서 Counter.add는 함수입니다. T1에 c.add를 읽으면 bound wrapper의 __self__가 c로 설정되고, T2에 bound(5)가 내부적으로 add(c, 5)처럼 호출됩니다. T3에 클래스에서 읽은 raw는 아직 receiver가 없어서 raw(5)는 TypeError가 됩니다. 이 함수는 non-data descriptor라 c.add = lambda n: 0으로 인스턴스에서 shadowing할 수도 있고, del c.add 후에는 다시 class 함수 binding으로 돌아옵니다. 메서드를 callback registry에 저장할 때는 bound object의 수명과 비교 기준을 함께 정해야 합니다. 예를 들어 등록할 때 c.add를 한 번 읽어 저장하고 해제할 때 같은 객체를 다시 읽어 비교하면 wrapper identity가 달라질 수 있으므로, 등록 token이나 (instance, function) 쌍을 별도로 보관하는 편이 안전합니다.

02

득점 포인트

이해가 드러나는 지점을 확인해 보세요.

  • function.__get__(instance, owner)의 instance가 인스턴스 접근에서는 c, 클래스 접근에서는 None이라는 차이를 말합니다.
  • bound(5)와 raw(c, 5)가 같은 12를 만드는 실제 인자 흐름을 보여 줍니다.
  • 함수가 non-data descriptor라서 인스턴스 속성으로 shadowing 가능하다는 점을 property와 대비합니다.
03

감점 포인트

놓치기 쉬운 조건과 오해를 짚어 보세요.

  • “Python이 호출 시점에 self를 전역 변수로 찾아 넣는다”고 말하면 binding이 일어나는 descriptor 단계를 놓친 것입니다.
  • C.f도 이미 c가 묶인 bound method라고 하면 클래스 접근과 인스턴스 접근을 혼동한 것입니다.
  • c.add와 c.add의 객체 identity가 항상 같다고 전제하면 callback 해제 설계가 흔들릴 수 있습니다.
04

더 파고들 거리

하나 더 질문하며 이해의 범위를 넓혀 보세요.

  • 직접 만든 descriptor에서 __get__(None, Owner)와 __get__(obj, Owner)를 다르게 반환하는 패턴을 구현해 보세요.
  • 인스턴스에 같은 이름의 callable을 넣어 메서드를 잠시 교체할 때, 삭제와 thread 간 callback 수명을 어떻게 보장할지 생각해 보세요.

득점·감점 포인트는 학습용 자기 점검 기준이며, 특정 회사의 실제 채점표가 아닙니다.

← 전체 질문 보기