Menu
Coddy logo textTech

Property 데코레이터 심화

Coddy Python 여정의 Object Oriented Programming 섹션에 포함된 레슨 — 64개 중 31번째.

고급 속성 데코레이터는 계산된 속성(computed properties), 삭제자(deleters) 및 전체 속성 관리를 포함하여 속성 접근에 대한 더 정교한 제어를 제공합니다.

다음은 다른 속성으로부터 값을 도출하는 계산된 속성(computed properties)의 예입니다:

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    @property
    def area(self):
        return self.width * self.height
    
    @property
    def perimeter(self):
        return 2 * (self.width + self.height)

계산된 속성을 일반 속성처럼 사용하세요:

rect = Rectangle(5, 3)
print(rect.area)      # 15 - 자동으로 계산됨
print(rect.perimeter) # 16 - 자동으로 계산됨

getter, setter, deleter를 가진 프로퍼티를 생성합니다:

class Temperature:
    def __init__(self):
        self._temp = 0
    
    @property
    def temperature(self):
        return self._temp
    
    @temperature.setter
    def temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._temp = value
    
    @temperature.deleter
    def temperature(self):
        print("Resetting temperature to 0")
        self._temp = 0

전체 프로퍼티 기능을 사용하세요:

temp = Temperature()

검증 기능이 포함된 세터를 사용하세요:

temp.temperature = 25
print(temp.temperature)  # 25

# temp.temperature = -300  # ValueError를 발생시킵니다

deleter를 사용하세요:

del temp.temperature
print(temp.temperature)  # 0

게임 점수를 활용한 더 복잡한 예제를 만들어 보겠습니다:

class Player:
    def __init__(self, name):
        self.name = name
        self._score = 0
        self._level = 1
    
    @property
    def score(self):
        return self._score
    
    @score.setter
    def score(self, value):
        if value >= 0:
            self._score = value
            self._level = (value // 1000) + 1
        else:
            raise ValueError("Score cannot be negative")
    
    @score.deleter
    def score(self):
        print(f"Resetting {self.name}'s progress")
        self._score = 0
        self._level = 1
    
    @property
    def level(self):
        return self._level

player = Player("Alice")
player.score = 2500
print(f"Score: {player.score}, Level: {player.level}")  # Score: 2500, Level: 3

del player.score
print(f"Score: {player.score}, Level: {player.level}")  # Score: 0, Level: 1

출력:

15
16
25
Resetting temperature to 0
0
Score: 2500, Level: 3
Resetting Alice's progress
Score: 0, Level: 1

핵심 포인트: 고급 프로퍼티 데코레이터를 사용하면 계산된 프로퍼티(다른 데이터로부터 계산됨), @property.deleter를 사용한 프로퍼티 삭제, 그리고 속성의 가져오기(getting), 설정하기(setting), 삭제하기(deleting)에 대한 완전한 제어가 가능합니다. 이를 통해 강력한 데이터 검증 및 캡슐화를 유지하면서도 직관적인 인터페이스를 만들 수 있습니다.

challenge icon

챌린지

중급

이 챌린지에서는 적절한 캡슐화와 속성 검증을 갖춘 Rectangle 클래스를 구현합니다.

  • rectangle.py - 구현을 안내하는 TODO 주석이 포함된, 수정해야 할 파일입니다.
  • driver.py - 광범위한 테스트 시나리오가 포함되어 있습니다 (수정하지 마세요).
  1. _width_height에 대한 비공개 속성을 구현합니다.
  2. 검증 기능(양수여야 함)이 포함된 widthheight 속성을 생성합니다.
    • TODO에 명시된 대로 적절한 ValueError 메시지를 발생시킵니다.
    • 중요: 값이 0보다 작거나 같은지 확인한 다음 오류를 발생시킵니다 (이는 일관된 동작을 보장합니다).
  3. areaperimeter에 대한 읽기 전용 속성을 구현합니다.
  4. TODO에 설명된 대로 getter, setter 및 deleter 기능이 있는 dimensions 속성을 생성합니다.
    • 중요: setter에서 튜플 언패킹 구문인 width, height = dimensions를 사용하세요.

치트 시트

고급 프로퍼티 데코레이터는 계산된 프로퍼티(computed properties), 삭제자(deleters) 및 전체 프로퍼티 관리를 통해 속성 액세스에 대한 정교한 제어를 제공합니다.

계산된 프로퍼티(Computed Properties):

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    @property
    def area(self):
        return self.width * self.height
    
    @property
    def perimeter(self):
        return 2 * (self.width + self.height)

Getter, Setter, Deleter를 포함한 전체 프로퍼티:

class Temperature:
    def __init__(self):
        self._temp = 0
    
    @property
    def temperature(self):
        return self._temp
    
    @temperature.setter
    def temperature(self, value):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._temp = value
    
    @temperature.deleter
    def temperature(self):
        print("Resetting temperature to 0")
        self._temp = 0

사용법:

# 계산된 프로퍼티
rect = Rectangle(5, 3)
print(rect.area)      # 15
print(rect.perimeter) # 16

# 전체 프로퍼티 기능
temp = Temperature()
temp.temperature = 25  # setter 사용
print(temp.temperature)  # getter 사용

del temp.temperature   # deleter 사용

직접 해보기

from rectangle import Rectangle

# 테스트 케이스 처리기
test_case = input()

# 기본 기능 테스트
if test_case == "default_test":
    rect = Rectangle(5, 3)
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
    
    # dimensions 속성 테스트
    print(f"Dimensions: {rect.dimensions}")
    rect.dimensions = (10, 8)
    print(f"New area: {rect.area}")
    
    # 유효성 검사 테스트
    try:
        rect.width = -2
    except ValueError as e:
        print(f"Validation error: {e}")
    
    # 삭제자(deleter) 테스트
    del rect.dimensions
    print(f"After reset: {rect.dimensions}")

# 0 값으로 테스트
elif test_case == "zero_values":
    try:
        rect = Rectangle(0, 5)
    except ValueError as e:
        print(f"Error creating rectangle: {e}")
    
    try:
        rect = Rectangle(5, 0)
    except ValueError as e:
        print(f"Error creating rectangle: {e}")

# 음수 값으로 테스트
elif test_case == "negative_values":
    rect = Rectangle(5, 3)
    original_dimensions = rect.dimensions
    
    try:
        rect.dimensions = (5, -3)
    except ValueError as e:
        print(f"Error setting dimensions: {e}")
    
    print(f"Dimensions after failed update: {rect.dimensions}")
    print(f"Original dimensions preserved: {rect.dimensions == original_dimensions}")

# 큰 값으로 테스트
elif test_case == "large_values":
    rect = Rectangle(1000000, 2000000)
    print(f"Large rectangle area: {rect.area}")
    print(f"Large rectangle perimeter: {rect.perimeter}")

# 부동 소수점 값으로 테스트
elif test_case == "float_values":
    rect = Rectangle(3.5, 2.75)
    print(f"Dimensions: {rect.dimensions}")
    print(f"Area: {rect.area}")
    print(f"Perimeter: {rect.perimeter}")
    
    rect.dimensions = (1.1, 2.2)
    print(f"New area with float dimensions: {rect.area}")

# 타입 오류 테스트
elif test_case == "type_errors":
    try:
        rect = Rectangle("5", 3)
    except Exception as e:
        print(f"Type error during creation: {type(e).__name__}: {e}")
    
    rect = Rectangle(5, 3)
    try:
        rect.dimensions = 10  # 튜플이 아님
    except Exception as e:
        print(f"Type error setting dimensions: {type(e).__name__}: {e}")

# 여러 속성 연산 테스트
elif test_case == "property_operations":
    rect = Rectangle(5, 10)
    print(f"Initial - Width: {rect.width}, Height: {rect.height}, Area: {rect.area}")
    
    rect.width = 8
    print(f"After width change - Width: {rect.width}, Area: {rect.area}")
    
    rect.height = 6
    print(f"After height change - Height: {rect.height}, Area: {rect.area}")
    
    rect.dimensions = (12, 9)
    print(f"After dimensions change - Dimensions: {rect.dimensions}, Area: {rect.area}")
    
    del rect.dimensions
    print(f"After reset - Dimensions: {rect.dimensions}, Area: {rect.area}")

# 속성 유효성 검사 경계 사례 테스트
elif test_case == "validation_edge_cases":
    rect = Rectangle(5, 3)
    
    try:
        rect.width = 0
    except ValueError as e:
        print(f"Zero width error: {e}")
    
    # 매우 작은 양수 값은 허용되어야 함
    rect.height = 0.0001
    print(f"Small height accepted: {rect.height}")
    print(f"Area with small height: {rect.area}")

# 여러 사각형 테스트
elif test_case == "multiple_rectangles":
    rect1 = Rectangle(5, 3)
    rect2 = Rectangle(10, 2)
    rect3 = Rectangle(4, 4)
    
    print(f"Rectangle 1 - Area: {rect1.area}, Perimeter: {rect1.perimeter}")
    print(f"Rectangle 2 - Area: {rect2.area}, Perimeter: {rect2.perimeter}")
    print(f"Rectangle 3 - Area: {rect3.area}, Perimeter: {rect3.perimeter}")
    
    # 각 사각형 수정
    rect1.width = 7
    rect2.height = 5
    rect3.dimensions = (6, 6)
    
    print(f"After modifications:")
    print(f"Rectangle 1 - Dimensions: {rect1.dimensions}")
    print(f"Rectangle 2 - Dimensions: {rect2.dimensions}")
    print(f"Rectangle 3 - Dimensions: {rect3.dimensions}")

# 많은 연산에 대한 성능 테스트
elif test_case == "performance_test":
    rect = Rectangle(5, 5)
    
    # 많은 속성 연산 수행
    for i in range(1000):
        rect.width = i % 10 + 1  # 1-10 사이의 값
        rect.height = i % 5 + 1  # 1-5 사이의 값
        area = rect.area  # 계산된 속성에 접근
        perimeter = rect.perimeter  # 계산된 속성에 접근
        dims = rect.dimensions  # 속성 getter에 접근
    
    print(f"Final state after 1000 operations:")
    print(f"Width: {rect.width}, Height: {rect.height}")
    print(f"Area: {rect.area}, Perimeter: {rect.perimeter}")
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

Object Oriented Programming의 모든 레슨