본문 바로가기

개발

[오픈소스에서 배우다] Django의 cached_property

반응형

파이썬을 대표하는 웹프레임워크 중 하나인 Django에는 다음과 같은 코드가 있다. (코드 직접 확인하기)

class cached_property(object):
    """
    Decorator that converts a method with a single self argument into a
    property cached on the instance.
    Optional ``name`` argument allows you to make cached properties of other
    methods. (e.g.  url = cached_property(get_absolute_url, name='url') )
    """
    def __init__(self, func, name=None):
        self.func = func
        self.__doc__ = getattr(func, '__doc__')
        self.name = name or func.__name__

    def __get__(self, instance, cls=None):
        if instance is None:
            return self
        res = instance.__dict__[self.name] = self.func(instance)
        return res

번역하면, "self만을 argument로 가지고 있는 인스턴스의 메소드를 인스턴스에 캐싱되는 속성(Property)으로 만들어주는 클래스 데코레이터"이다. (클래스도 데코레이터로 사용할 수 있다.)

예를 들어 아래와 같은 클래스를 정의하고 called_count라는 메소드를 정의해보자.

class TestClass(object):

    def __init__(self):
        self.called = 0

    @cached_property
    def called_count(self):
        self.called += 1
        return self.called

아래와 같이 인스턴스를 만들어서 called_count를 실행해보면 처음 호출시에만 called가 +1이 되고 그 이후부터는 해당 값을 캐싱하여 별도의 연산없이 캐싱된 값을 리턴한다.

t = TestClass()
print(t.called)  # >> 0
print(t.called_count)  # >> 1
print(t.called_count)  # >> 1
print(t.called_count)  # >> 1

TestClass 클래스가 선언되면서 cached_property클래스의 __init__메소드가 실행되고 TestClasscalled_count 메소드가 cached_property클래스에 등록된다. (메소드 뿐만이아니라 메소드의 docstring, 그리고 name까지 등록된다.) 처음 called_count를 부를 때 cached_property__get__ 매직 메소드를 호출하고, TestClass의 인스턴스 (여기서는 t)의 __dict__에 'key'로 called_count, 'value'로 called_count메소드를 실행한 값을 담는다. 그 이후 called_count를 호출할 때는 cached_property__get__을 거치지않고 t__dict__에 저장된 값을 반환한다.

그렇다면 cached_property를 사용하면 어떤 장점이 있을까? 복잡한 연산이나 네트워크 IO를 통해 얻을 수 있는 속성값의 경우 해당 속성을 사용하지 않는 경우 굉장히 큰 비용이 발생하는데 이를 방지할 수 있으며, 해당 속성값을 여러번 호출하여야 하는 경우에도 결과값이 캐싱이 되어있기 때문에 반복적으로 연산이나 네트워크 IO를 거치며 발생하는 딜레이를 줄여 프로그램을 보다 효율적으로 만들 수 있다.


'오픈소스에서 배우다'는 오픈소스 코드들을 읽으면서 스스로 배우고 싶은 것들이나, 나누고 싶은 것들을 공유하는 시리즈입니다.

참조

반응형