Profile picture

[Python] 캐싱 수행하여 성능 향상시키기 (cached_property)

JaehyoJJAng2024년 05월 23일

▶︎ functools.cached_property

파이썬 3.8 버전부터 __dict__를 활용한 cached_property 기능이 추가되었다.

특정 property를 캐싱할 수 있는 기능인데, 이를 이용하면 속성에 처음 접근할 때 계산을 수행하고, 이후에는 동일한 값을 반환하여 성능을 향상 시킬 수 있다.

cached_property 기능의 경우 주로 클래스의 인스턴스 속성이나, 메서드의 결과를 캐시할 때 매우 유용하다.


‣ 예제

아래의 첫 번째 예시는 연산이 오래 걸리는 property를 다시 계산하지 않고 재사용하기 위해 cached_property를 도입해본 예시이다.

from functools import cached_property

class Example:
    def __init__(self, peoples: list[dict[str, str|int]]) -> None:
        self.peoples = peoples

    @cached_property
    def average_age(self):
        print("나이 평균 계산 중...")
        total_age = sum(person['age'] for person in self.peoples)
        return total_age / len(self.peoples)

# 사용 예시
people_data = [
    {'name': 'Alice', 'age': 30},
    {'name': 'Bob', 'age': 25},
    {'name': 'Charlie', 'age': 35}
]

# 인스턴스 생성
example_instance = Example(people_data)

# 처음 average_age에 접근하면 계산이 수행됨
print(example_instance.average_age)  # 출력: 나이 평균 계산 중... 30.0

# 두 번째부터는 캐시된 결과를 반환
print(example_instance.average_age)  # 출력: 30.0

# __dict__ 값 조회
print(vars(example_instance))

# __dict__ 값 삭제하여 캐싱 초기화
example_instance.pop('peoples')

# 다시 연산 수행
example_instance.averate_age

위 예제에서는 Example 클래스의 average_age 속성을 @cached_property로 데코레이트 하였다.

해당 속성은 주어진 사람들의 나이 평균을 계산하는데, 이 때 처음 접근할 때에는 계산 로직을 수행하고, 이후에는 캐시된 결과를 반환한다.


이런 기능이 가능한 이유는 첫 호출 이후 객체의 __dict__에 결과 값이 저장되기 때문이다.

print(vars(example_instance))
# output
{'peoples': [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}, {'name': 'Charlie', 'age': 35}], 'average_age': 30.0}

따라서 다음과 같이 __dict__의 값을 지우게 되면 캐싱된 값이 무효화되고 다시 연산을 수행하게 된다.

# __dict__ 값 삭제하여 캐싱 초기화
example_instance.pop('peoples')

# 다시 연산 수행
example_instance.averate_age

• property의 호출 방법?

위에서 작성한 샘플 코드를 다시 살펴봐보자.

# ...

# 인스턴스 생성
example_instance = Example(people_data)

# 처음 average_age에 접근하면 계산이 수행됨
print(example_instance.average_age)  # 출력: 나이 평균 계산 중... 30.0

# 두 번째부터는 캐시된 결과를 반환
print(example_instance.average_age)  # 출력: 30.0

# ...

여기서 example_instance 인스턴스에서 average_age를 호출할 때 ()를 붙이지 않고 호출을 한다.


보통 메소드를 호출할 때에는 다음과 같이 호출해야 하는데

average_age()

예제에서는 왜 average_age를 속성처럼 호출해서 사용하는 걸까?

그 이유는 프로퍼티(property)가 메소드에 데코레이트 된 경우 일반적인 메서드처럼 호출하지 않고 속성처럼 접근할 수 있기 때문이다.

@cached_property를 데코레이터를 사용하면 메서드를 속성으로 변환하는데, 이렇게 하면 메서드를 호출하는 것이 아니라 속성처럼 접근할 수 있게된다. 따라서 괄호를 사용하지 않는다.

이와 대조적으로, 만약에 @property를 사용하여 메서드를 속성으로 변환하지 않았다면, 해당 메서드를 호출할 때에는 괄호를 사용해야 한다.


Loading script...