본문 바로가기

프로그래밍/Python

[Python] @property는 언제 사용해?

728x90
Python @property 실습 중심 가이드
Python · @property

Python @property 실습 중심 가이드

이 글은 Python의 @property를 사용해 “메서드를 속성처럼 사용하는 방법”을 실습 위주로 설명하는 가이드입니다.

1. @property 한 줄 정의

@property“메서드를 속성처럼 보이게 만들어 주는 문법”입니다.

예를 들어 다음과 같은 코드가 가능해집니다.

class Person:
    @property
    def name(self):
        return self._name

내부적으로는 name()이라는 메서드이지만, 외부에서는 p.name처럼 괄호 없이 속성 접근 문법으로 사용할 수 있습니다.

2. 전통적인 get/set 방식 vs @property

2-1. get/set 메서드를 사용하는 방식

class Person:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

p = Person("Alice")
p.get_name()
p.set_name("Bob")

2-2. @property를 사용하는 방식

class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = value

p = Person("Alice")
print(p.name)    # getter
p.name = "Bob"   # setter 호출

기능적으로는 비슷하지만, p.name, p.name = ... 처럼 훨씬 자연스러운 문법으로 사용할 수 있습니다.

3. 기본 패턴 – getter / setter / deleter

3-1. 읽기 전용 프로퍼티 (getter만 있는 경우)

class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

p = Person("Alice")
print(p.name)
# p.name = "Bob"  # AttributeError: can't set attribute

setter를 정의하지 않으면 해당 프로퍼티는 읽기 전용이 됩니다.

3-2. setter 추가

class Person:
    def __init__(self, name):
        self._name = None
        self.name = name  # 아래 setter를 통해 값 설정

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not value:
            raise ValueError("이름은 비어 있을 수 없습니다.")
        self._name = value

p = Person("Alice")
p.name = "Bob"
p.name = ""   # ValueError 발생

@name.setter에서 사용하는 메서드 이름은 반드시 프로퍼티 이름(name)과 같아야 합니다.

3-3. deleter (참고용)

class Person:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    @name.deleter
    def name(self):
        print("이름을 삭제합니다.")
        del self._name

p = Person("Alice")
del p.name

실무에서 자주 쓰이진 않지만, 삭제 시에도 커스텀 로직을 넣을 수 있다는 정도만 알아두면 충분합니다.

4. @property가 유용한 상황들

4-1. 내부 구현을 숨기고 외부 API는 유지하고 싶을 때

처음에는 넓이(면적)를 직접 계산하고 있었다고 가정해 봅시다.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

rect = Rectangle(3, 4)
area = rect.width * rect.height

이후에 rect.area로 넓이를 표현하고 싶어졌습니다.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        return self.width * self.height

rect = Rectangle(3, 4)
print(rect.area)

넓이를 계산하는 로직은 메서드 안으로 들어갔지만, 외부에서는 여전히 속성처럼 사용할 수 있어서 코드 사용처를 크게 수정하지 않고도 내부 구현을 바꿀 수 있습니다.

4-2. 값 설정 시 유효성 검사가 필요할 때

class Person:
    def __init__(self, name, age):
        self.name = name
        self._age = None
        self.age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("나이는 음수가 될 수 없습니다.")
        self._age = value

p = Person("Alice", 30)
p.age = -1   # ValueError

외부에서는 p.age = 값처럼 단순하게 보이지만, 내부적으로는 언제나 유효성 검사가 거쳐지도록 만들 수 있습니다.

4-3. 계산된 속성 (Computed Property)

class Product:
    def __init__(self, price, quantity):
        self.price = price
        self.quantity = quantity

    @property
    def total(self):
        return self.price * self.quantity

item = Product(1000, 3)
print(item.total)  # 3000

total은 실제로는 저장된 값이 아니라, 다른 속성(price, quantity)을 이용해 매번 계산하는 “계산된 속성”입니다.

5. property() 함수 형태

@property는 문법 설탕(syntax sugar)이고, 원형은 property() 함수입니다.

class Person:
    def __init__(self, name):
        self._name = name

    def get_name(self):
        return self._name

    def set_name(self, value):
        self._name = value

    name = property(get_name, set_name)

p = Person("Alice")
print(p.name)      # get_name() 호출
p.name = "Bob"     # set_name() 호출

현재는 가독성이 좋은 @property / @name.setter 데코레이터 형태를 사용하는 것이 일반적입니다.

6. @property 사용 시 주의할 점

6-1. 너무 무거운 작업을 넣지 않기

obj.data 같은 속성 접근이 내부적으로는 DB 조회나 네트워크 호출 같은 무거운 작업을 수행하면, 코드를 읽는 사람이 예상하기 어렵습니다.

속성 접근은 보통 “가벼운 연산”이라는 인상을 주기 때문에,
무거운 작업은 get_data()와 같은 메서드로 만드는 편이 더 명확할 때가 많습니다.

6-2. 강한 사이드 이펙트는 신중하게

obj.value를 읽는 것만으로 파일을 생성한다거나, 내부 상태를 크게 변경하는 등의 사이드 이펙트는 가능한 한 피하는 것이 좋습니다.

7. 한 줄 요약

  • @property는 “메서드를 속성처럼 보이게 해주는 도구”이다.
  • getter만 있으면 읽기 전용, setter를 추가하면 쓰기 가능하다.
  • 내부 구현을 바꾸더라도 외부 API(obj.x)를 유지하고 싶을 때 특히 유용하다.
  • 유효성 검사, 계산된 속성 등에서 많이 쓰이며, 너무 무거운 작업이나 예상 밖의 사이드 이펙트는 피하는 것이 좋다.

8. 연습 문제

연습 1 – BankAccount.balance

BankAccount 클래스를 만들고 다음을 구현해 보세요.

  • 내부 잔액은 _balance에 저장
  • 외부에는 balance 프로퍼티로 노출
  • balance를 설정할 때 0 미만으로 내려가면 ValueError 발생

연습 2 – Vector2D.length

Vector2D 클래스를 만들고:

  • x, y 속성
  • length 프로퍼티 – sqrt(x*x + y*y) 를 반환
  • length에는 setter를 두지 않고 읽기 전용으로만 사용

이런 연습을 통해, @property를 “언제, 어떤 패턴에 쓰면 좋은지” 감이 훨씬 잘 잡힐 것입니다.