[Python] __slots__의 효과

1 분 소요

__dict__의 단점과 해결책

파이썬은 __dict__의 이름으로 객체 하나당 하나씩 딕셔너리를 할당한다. 그런데 딕셔너리는 리스트나 튜플에 비해 메모리 사용량이 많다. ‘키’를 이용해 ‘값’을 바로 얻을 수 있도록 하기 위해서 파이썬이 더 많은 정보를 유지하기 때문이다.

이러한 부담을 줄이기 위해 __slots__라는 것을 사용한다.

class Point3D:
    def __init__(self, x, y, z):
        self.x = x       # x 좌표
        self.y = y       # y 좌표
        self.z = z       # z 좌표
    def __str__(self):
        return '({0}, {1}, {2})'.format(self.x, self.y, self.z)

class Point3D:
    __slots__ = ('x', 'y', 'z')
    
    def __init__(self, x, y, z):
        self.x = x       # x 좌표
        self.y = y       # y 좌표
        self.z = z       # z 좌표
    def __str__(self):
        return '({0}, {1}, {2})'.format(self.x, self.y, self.z)

def main():
    p1 = Point3D(1, 1, 1)      # 3차원 좌표상의 한 점
    p2 = Point3D(24, 17, 31)       # 3차원 좌표상의 한 점
    print(p1)
    print(p2)
(1,1,1)
(24,17,31)

위 코드를 보면 __slots__를 이용하는 형태로 수정한 코드가 추가되있다.

__slots__ = (‘x’, ‘y’, ‘z’) 의 의미는 이 클래스를 기반으로 생성한 객체의 변수는 x,y,z로 제한한다는 의미이다. 이 코드를 통해 p1.w=30; 등과 같이 객체에 변수를 추가하는 것은 불가능하다.

__slots__를 통해 변수의 수와 이름을 제한하면 객체별로 __dict__이 생기지 않는다. 또 객체별로 __slots__가 하나씩 갖는 것이 아니라 클래스당 하나의 __slots__만 생성되기 때문에 메모리상 이득이 된다.



__dict__ 과 __slots__의 속도차이

__slots__가 존재하는 경우 객체 내에 있는 변수에 접근하는 데 있어 속도 상의 이점도 있다.

  • __dict__
import timeit

class Point3D:    
    def __init__(self, x, y, z):
        self.x = x       # x 좌표
        self.y = y       # y 좌표
        self.z = z       # z 좌표
    def __str__(self):
        return '({0}, {1}, {2})'.format(self.x, self.y, self.z)


def main():
    start = timeit.default_timer()
    p = Point3D(1, 1, 1)
    
    for i in range(3000):
        for i in range(3000):
            p.x += 1
            p.y += 1
            p.z += 1
    print(p)

    stop = timeit.default_timer()
    print(stop - start)

main()
(9000001, 9000001, 9000001)
4.1482692
  • __slots__
import timeit

class Point3D:
    __slots__ = ('x', 'y', 'z')
    
    def __init__(self, x, y, z):
        self.x = x       # x 좌표
        self.y = y       # y 좌표
        self.z = z       # z 좌표
    def __str__(self):
        return '({0}, {1}, {2})'.format(self.x, self.y, self.z)


def main():
    start = timeit.default_timer()
    p = Point3D(1, 1, 1)

    for i in range(3000):
        for i in range(3000):
            p.x += 1
            p.y += 1
            p.z += 1
    print(p)

    stop = timeit.default_timer()
    print(stop - start)


main()
(9000001, 9000001, 9000001)
3.4330227

위 코드를 통해 __slots__가 있을 때 더 적은 시간이 걸렸음을 확인할 수 있다.