
Any object which defines the methods
__get__(),__set__(), or__delete__().
When a class attribute is a descriptor, its special binding behavior is triggered upon attribute lookup.
Normally, using a.b to get, set or delete an attribute looks up the object named b in the class dictionary for a, but if b is a descriptor, the respective descriptor method gets called.
Python의 descriptor는 단순히 "클래스의 속성을 클래스 형태로 대체"하는 것을 넘어, 속성 접근 방식 자체를 제어할 수 있는 메커니즘입니다.
Python 의 @property 데코레이터를 사용해 본 적 있다면 이해하기 수월합니다.
getter, setter 와 같은 메소드들을 마치 변수처럼 사용할 수 있도록 동작합니다.
descriptor 학습 계기
Django 의 View 클래스의 내부 메소드 중, view_is_async 에 대해 공부하는 중 마주친 @classproperty 라는 데코레이터 때문에 descriptor 라는 개념에 대해 공부하게 되었습니다.
개인적으로 프로그래밍 공부를 하며 생소한 단어가 문법으로 등장하니 머릿속이 복잡했는데, descriptor 라는 단어의 의미 그대로 설명을 대신 해준다는 의미로 받아 들이면 좋을 것 같습니다.
클래스에 직접 메소드를 작성하는 것보다 훨씬 간결하고 재사용성이 높게 메소드를 추가할 수 있습니다.
Descriptor는 클래스 내부에서 __get__, __set__, __delete__ 중 하나 이상을 정의한 객체입니다. 이 객체는 다른 클래스의 속성으로 사용될 때, 속성 접근 (a.b)을 제어할 수 있습니다.
class MyDescriptor:
def __get__(self, instance, owner):
print("Getting value")
return 42
def __set__(self, instance, value):
print("Setting value:", value)
class MyClass:
x = MyDescriptor() # descriptor 객체
obj = MyClass()
print(obj.x) # __get__ 호출
obj.x = 100 # __set__ 호출
@property 대체)| 이름 | 메서드 필요 | 예시 기능 |
|---|---|---|
| 데이터 디스크립터 | __get__, __set__ 정의 | 값 설정/검증 가능 |
| 비데이터 디스크립터 | __get__만 정의 | 읽기 전용 속성 등 |
데이터 디스크립터가 우선순위가 더 높습니다 (인스턴스 딕셔너리보다 먼저 호출됨).
@property도 descriptor다class MyClass:
def __init__(self):
self._x = 0
@property
def x(self): # property object가 __get__ 정의된 descriptor
return self._x
@x.setter
def x(self, value): # __set__ 정의됨
self._x = value
property도 내부적으로 descriptor 객체입니다. 즉, x = property(...)처럼 속성을 class 수준에서 descriptor로 대체한 것입니다.
Python의 descriptor는 클래스의 속성 접근을 자동으로 가로채어 동작을 정의할 수 있는 프로토콜입니다.
단순한 "클래스 형태로 대체"가 아니라, 속성의 읽기/쓰기/삭제 동작을 사용자 정의로 제어할 수 있는 강력한 도구입니다.
필요하시면 실제 사용 예시나 __get__ 호출 순서 흐름도도 함께 드릴 수 있습니다. 더 자세한 내용이 필요하신가요?
Python의 descriptor와 Java의 getter/setter 메서드는 기능적으로 유사한 목적(속성 제어)을 갖지만, 동작 방식과 철학이 완전히 다릅니다.
다음 표와 함께 핵심 차이점을 설명드리겠습니다.
| 항목 | Python descriptor | Java getter/setter |
|---|---|---|
| 핵심 개념 | 속성 접근 자체를 가로채는 특별한 객체 프로토콜 | 속성을 직접 노출하지 않고, 메서드로 값을 얻고(set)/설정(get) |
| 사용 방식 | __get__, __set__, __delete__ 메서드 구현 | getX(), setX() 식의 명시적 메서드 호출 |
| 문법적 통일성 | obj.x로 접근해도 자동으로 동작 | obj.getX()처럼 메서드 호출 |
| 속성처럼 사용 가능 | (ex: @property or descriptor 직접 구현) | ❌ 메서드이기 때문에 속성처럼 접근 불가 |
| 재사용 가능성 | 속성 제어 로직을 여러 클래스에 재사용 가능 (ex: ValidatedField) | 일반적으로 클래스마다 새로 작성 |
| OOP 철학 | "속성을 제어하는 방식"도 객체화 | "정보 은닉(encapsulation)" 강조 |
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
}
class NameDescriptor:
def __get__(self, instance, owner):
return instance._name
def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError("name must be a string")
instance._name = value
class Person:
name = NameDescriptor()
def __init__(self, name):
self.name = name # -> __set__ 호출됨
p.name으로 접근해도 내부적으로 get이 호출됨 → 속성처럼 보이지만 메서드처럼 동작
getX()/setX() 메서드를 명시적으로 호출해야 함obj.x처럼 속성처럼 사용하면서 내부적으로 __get__/__set__ 호출 가능즉, Python은 "속성과 메서드의 구분을 숨길 수 있음" (→ 내부 구현은 유연, 인터페이스는 단순)
필요하다면 @property vs descriptor 차이도 정리해드릴 수 있어요. 추가로 비교해볼까요?
Python에서 @property와 descriptor는 둘 다 속성 접근 제어를 위해 사용되며, 실제로 @property도 내부적으로 descriptor입니다. 하지만 두 방식은 목적과 사용 편의성, 유연성 측면에서 차이가 있습니다.
@property vs descriptor 차이 비교| 항목 | @property | descriptor (직접 구현) |
|---|---|---|
| 정의 방법 | 메서드에 @property / @x.setter 데코레이터 사용 | __get__, __set__ 메서드를 가진 클래스를 속성으로 정의 |
| 사용 목적 | 단일 클래스 내의 속성 제어 (간편함) | 복수 클래스에서 속성 제어 로직을 재사용하고 싶을 때 |
| 캡슐화 수준 | 간결하고 명확 (읽기 전용, 유효성 검사 등) | 복잡한 제어, 공통 로직 추출 시 유리 |
| 재사용성 | 낮음 (클래스마다 새로 정의) | 높음 (여러 클래스에 descriptor 객체 재사용 가능) |
| 읽기 전용 구현 | 간단 (@property만 정의) | __get__만 정의하면 됨 |
| 표준 라이브러리 지원 여부 | Python 문법에 기본 내장 | Python의 descriptor protocol로 지원됨 |
@property 사용 (간단하고 클래스 내부 집중)class Person:
def __init__(self, name):
self._name = name
@property
def name(self): # 읽기
return self._name
@name.setter
def name(self, value): # 쓰기
if not isinstance(value, str):
raise ValueError("이름은 문자열이어야 합니다.")
self._name = value
➤ 가장 많이 쓰이는 패턴
➤ 명확하고 직관적
➤ 하지만 다른 클래스에 그대로 재사용하기는 어려움
class StringField:
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError("문자열만 허용됩니다")
instance.__dict__[self.name] = value
def __set_name__(self, owner, name): # 파이썬 3.6+
self.name = name
class Person:
name = StringField()
nickname = StringField()
➤ StringField 하나로 여러 속성 재사용 가능
➤ 동적으로 속성 이름 바인딩도 가능 (
__set_name__)➤ 다소 복잡하지만 매우 유연
@property: 간단하고 클래스 내부 전용. 메서드처럼 정의하지만 속성처럼 접근할 수 있음.실제 프로젝트에서는 @property로 시작하다가, 반복되는 속성 제어 로직이 많아질 경우 descriptor로 refactor하는 경우도 많습니다.
원하시면 Django ORM에서 descriptor가 어떻게 사용되는지도 알려드릴까요?
Python의 descriptor는 Django ORM에서 핵심적인 역할을 하고 있습니다. 우리가 흔히 사용하는 models.CharField, models.ForeignKey 같은 필드들이 모두 descriptor 객체입니다.
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
위 코드에서 title과 author는 그냥 일반 값처럼 보이지만, 사실은 Book 클래스의 클래스 속성으로 등록된 descriptor입니다.
book = Book.objects.get(pk=1)
book.title # => 내부적으로 CharField의 __get__ 호출
book.author # => ForeignKey의 __get__ 호출 → Author 객체 반환
title, author는 실제 데이터를 가지고 있지 않음CharField, ForeignKey 등의 하위 클래스가 __get__, __set__ 등을 오버라이드Django는 ForeignKey 필드를 다음처럼 descriptor로 구현합니다 (django.db.models.fields.related_descriptors 참고):
class ForwardManyToOneDescriptor:
def __get__(self, instance, cls=None):
# 예: book.author -> Author 인스턴스를 DB에서 가져옴
return related_instance_from_db()
def __set__(self, instance, value):
# 예: book.author = author_instance
set_related_instance()
이 구조 덕분에 단순히 book.author만 접근해도 자동으로 관련된 Author 인스턴스를 DB에서 불러오는 게 가능해집니다.
| Django 요소 | 내부 구현 방식 |
|---|---|
CharField, IntegerField, DateField 등 | Field 클래스 → descriptor 구현 |
ForeignKey, ManyToManyField | related descriptor로 구현 |
model.field 접근 | __get__ 호출 → 내부 DB 접근 또는 캐싱 처리 |
model.field = 값 | __set__ 호출 → DB 값 캐싱 또는 관계 연결 |
Django에서 필드 정의는 단순 속성처럼 보이지만, 실제로는 descriptor 패턴을 이용해 복잡한 로직을 숨기고,
ORM 유저는 직관적인 속성 접근만으로도 DB와의 상호작용이 가능하도록 만들어져 있습니다.
필요하시면 CharField나 ForeignKey의 내부 코드를 부분적으로 정리해서 보여드릴 수도 있어요. 원하시나요?