Python 클래스의 __dict__와 __slots__: 객체 속성 관리 방법 이해하기

Ad Placeholder

앞선 글에서는 Python 클래스에서 사용하는 인스턴스 메서드, 클래스 메서드, 정적 메서드의 차이를 살펴봤습니다.

이번에는 조금 더 내부적인 내용을 알아보겠습니다.

Python에서 객체를 만들고 다음과 같이 속성을 추가하면:

person.name = "철수"
person.age = 20

이 값들은 객체 내부에서 어떻게 관리되고 있을까요?

이때 알아두면 좋은 것이 바로 **__dict__**입니다.

그리고 Python에서는 객체의 속성을 __dict__ 대신 미리 정해진 형태로 관리할 수 있도록 **__slots__**라는 기능도 제공합니다.

이번 글에서는 __dict__와 __slots__가 무엇인지, 둘 사이에는 어떤 차이가 있는지 살펴보겠습니다.

객체의 속성 확인하기

먼저 간단한 클래스를 만들어보겠습니다.

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

그리고 객체를 하나 만들어봅니다.

person = Person("철수", 20)

이 객체가 어떤 속성을 가지고 있는지 확인하려면 __dict__를 사용할 수 있습니다.

print(person.__dict__)

결과:

{'name': '철수', 'age': 20}

객체에 저장되어 있는 name과 age가 딕셔너리 형태로 나타나는 것을 확인할 수 있습니다.

즉, __dict__를 이용하면 객체의 속성을 확인할 수 있습니다.

__dict__란?

__dict__는 Python 객체가 가지고 있는 속성 정보를 딕셔너리 형태로 확인할 수 있도록 제공되는 특별한 속성입니다.

예를 들어:

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

다음 객체를 만들면:

person = Person("철수", 20)

개념적으로 다음과 같이 볼 수 있습니다.

person
  │
  └── __dict__
       ├── name → "철수"
       └── age  → 20

실제로 person.__dict__를 출력하면:

print(person.__dict__)

다음과 같은 결과를 확인할 수 있습니다.

{'name': '철수', 'age': 20}

__dict__를 직접 수정할 수도 있다

__dict__는 단순히 읽기만 할 수 있는 것이 아닙니다.

직접 값을 추가할 수도 있습니다.

person.__dict__["address"] = "서울"

그러면:

print(person.address)

결과:

서울

__dict__에 address가 추가되었기 때문입니다.

print(person.__dict__)

결과:

{'name': '철수', 'age': 20, 'address': '서울'}

이처럼 Python 객체는 기본적으로 새로운 속성을 동적으로 추가할 수 있습니다.

클래스에도 __dict__가 있다

__dict__는 객체에만 존재하는 것은 아닙니다.

클래스에서도 사용할 수 있습니다.

class Person:

    species = "Human"

    def introduce(self):
        print("안녕하세요")

다음과 같이 확인할 수 있습니다.

print(Person.__dict__)

그러면 클래스에 정의된 여러 정보가 들어 있는 것을 확인할 수 있습니다.

예를 들어 다음과 같은 항목들이 포함될 수 있습니다.

species
introduce
__module__
__dict__
__weakref__
__doc__
...

즉,

Person.__dict__

은 클래스 자체의 속성 정보를 확인하는 데 사용할 수 있고,

person.__dict__

은 특정 인스턴스의 속성 정보를 확인하는 데 사용할 수 있습니다.

둘은 서로 다른 대상의 __dict__입니다.

__dict__의 장점

__dict__를 사용하는 방식은 Python의 객체를 유연하게 사용할 수 있도록 해줍니다.

예를 들어 처음에는 name과 age만 가지고 있던 객체에 새로운 속성을 추가할 수 있습니다.

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age


person = Person("철수", 20)

person.address = "서울"
person.email = "example@example.com"

처음 클래스에서 address나 email을 정의하지 않았더라도 새로운 속성을 추가할 수 있습니다.

print(person.__dict__)

결과:

{
    'name': '철수',
    'age': 20,
    'address': '서울',
    'email': 'example@example.com'
}

이런 유연성은 Python 객체의 중요한 특징 중 하나입니다.

하지만 반대로 이런 방식이 항상 좋은 것은 아닙니다.

객체가 많아지면 어떻게 될까?

객체 하나만 사용하는 경우에는 __dict__의 특성을 크게 신경 쓰지 않아도 됩니다.

하지만 같은 클래스로 매우 많은 객체를 만들어야 하는 상황이라면 이야기가 달라질 수 있습니다.

예를 들어:

people = []

for i in range(100000):
    people.append(Person(f"사람{i}", i))

처럼 매우 많은 객체를 생성한다고 생각해봅시다.

각 객체가 자유롭게 속성을 관리할 수 있도록 __dict__를 사용하는 방식에는 객체의 속성 정보를 관리하기 위한 추가적인 메모리 사용이 발생할 수 있습니다.

이런 상황에서 고려할 수 있는 것이 __slots__입니다.

__slots__란?

__slots__는 클래스에서 사용할 인스턴스 속성을 미리 지정하는 기능입니다.

예를 들어 다음과 같이 작성할 수 있습니다.

class Person:

    __slots__ = ("name", "age")

    def __init__(self, name, age):
        self.name = name
        self.age = age

여기서는 name과 age를 사용할 속성으로 미리 지정했습니다.

person = Person("철수", 20)

다음과 같이 사용하는 것은 정상적으로 동작합니다.

print(person.name)
print(person.age)

결과:

철수
20

__slots__를 사용하면 새로운 속성을 추가할 수 없다

__slots__의 중요한 특징입니다.

다음 코드를 실행해보겠습니다.

person.address = "서울"

그러면 일반적인 __dict__ 기반 객체와 달리 새로운 속성을 추가할 수 없습니다.

대략 다음과 같은 오류가 발생합니다.

AttributeError

왜냐하면 __slots__에 address를 지정하지 않았기 때문입니다.

class Person:

    __slots__ = ("name", "age")

이 클래스에서는 인스턴스가 사용할 수 있는 속성을 name과 age로 제한합니다.

필요하다면 처음부터 추가할 속성을 지정해야 합니다.

class Person:

    __slots__ = ("name", "age", "address")

그러면 다음과 같이 사용할 수 있습니다.

person.address = "서울"

__slots__를 사용하면 __dict__가 없을 수 있다

다음 클래스를 살펴보겠습니다.

class Person:

    __slots__ = ("name", "age")

객체를 만든 후:

person = Person()

다음 코드를 실행하면:

print(person.__dict__)

일반적인 객체처럼 __dict__를 사용할 수 없습니다.

__slots__를 지정하면 기본적으로 인스턴스의 __dict__가 생성되지 않기 때문입니다.

즉 일반적인 클래스와 비교하면 다음과 같습니다.

일반 클래스

객체
 ├── __dict__
 │    ├── name
 │    └── age
 └── 기타 객체 정보


__slots__ 사용

객체
 ├── name
 └── age

실제 내부 동작은 이것보다 더 복잡하지만, 기본적인 개념은 이렇게 이해하면 됩니다.

__dict__와 __slots__ 비교

두 방식을 비교하면 다음과 같습니다.

구분__dict____slots__
속성 추가자유롭게 가능지정된 속성만 가능
동적 속성 추가가능기본적으로 불가능
__dict__사용 가능기본적으로 없음
메모리 사용상대적으로 많을 수 있음객체가 많을 때 줄이는 데 도움
유연성높음낮음
사용 방식일반적인 클래스속성이 고정된 객체가 많은 경우 고려

여기서 중요한 것은 __slots__가 무조건 더 좋은 것은 아니라는 점입니다.

__slots__가 메모리를 줄이는 이유

일반적인 Python 객체는 속성을 동적으로 관리할 수 있도록 __dict__를 사용합니다.

하지만 __slots__를 사용하면 이러한 속성 관리 방식을 제한하고 지정된 속성을 중심으로 관리할 수 있습니다.

따라서 동일한 형태의 객체를 아주 많이 생성하는 경우 객체마다 필요한 메모리를 줄이는 데 도움이 될 수 있습니다.

예를 들어 센서 데이터처럼 동일한 구조의 객체를 대량으로 생성해야 한다고 생각해보겠습니다.

class Sensor:

    __slots__ = ("temperature", "pressure", "rpm")

    def __init__(self, temperature, pressure, rpm):
        self.temperature = temperature
        self.pressure = pressure
        self.rpm = rpm

이런 구조에서는 모든 Sensor 객체가 동일한 세 가지 속성을 사용합니다.

따라서 객체에 새로운 속성을 자유롭게 추가할 필요가 없다면 __slots__를 고려할 수 있습니다.

다만 실제 프로그램에서는 객체의 수, 속성의 수, Python 구현과 버전 등에 따라 메모리 사용량이 달라질 수 있으므로 항상 __slots__가 큰 차이를 만든다고 단정해서는 안 됩니다.

__slots__ 사용 시 주의할 점

__slots__는 편리하지만 몇 가지 주의할 부분이 있습니다.

1. 동적으로 속성을 추가할 수 없다

앞서 살펴본 것처럼 다음과 같은 코드는 동작하지 않습니다.

person.address = "서울"

필요한 속성이 있다면 __slots__에 미리 지정해야 합니다.

2. __dict__를 사용할 수 없다

__slots__를 사용하면 기본적으로 인스턴스의 __dict__가 생성되지 않습니다.

따라서 다음과 같은 코드에 의존하고 있었다면 문제가 발생할 수 있습니다.

person.__dict__

3. 모든 클래스에서 사용할 필요는 없다

객체가 몇 개 생성되지 않거나 속성을 자유롭게 추가해야 하는 프로그램이라면 일반적인 클래스가 더 편리할 수 있습니다.

즉 다음처럼 생각하는 것이 좋습니다.

속성이 자유롭게 변경될 필요가 있다
        ↓
일반적인 클래스

속성이 고정되어 있고
동일한 객체를 매우 많이 생성한다
        ↓
__slots__ 고려

__slots__에 __dict__를 직접 추가할 수도 있다

조금 더 알아두면 좋은 내용도 있습니다.

__slots__를 사용하면서도 동적으로 속성을 추가해야 하는 특별한 경우라면 __dict__를 슬롯에 직접 포함할 수 있습니다.

class Person:

    __slots__ = ("name", "age", "__dict__")

    def __init__(self, name, age):
        self.name = name
        self.age = age

이렇게 하면 __dict__를 사용할 수 있습니다.

person = Person("철수", 20)

person.address = "서울"

print(person.__dict__)

결과:

{'address': '서울'}

다만 이렇게 하면 __slots__를 사용하는 목적 중 하나인 속성 관리 제한이 약해질 수 있습니다.

따라서 특별한 이유가 없다면 무조건 추가하기보다는 정말 동적 속성이 필요한지 먼저 확인하는 것이 좋습니다.

__slots__와 상속

__slots__는 상속을 사용할 때도 주의해야 합니다.

예를 들어 부모 클래스에서:

class Person:

    __slots__ = ("name",)

자식 클래스에서:

class Student(Person):

    __slots__ = ("school",)

처럼 사용할 수 있습니다.

이 경우 Student 객체는 부모 클래스에서 정의한 name과 자식 클래스에서 정의한 school을 사용할 수 있습니다.

student = Student()

student.name = "철수"
student.school = "고등학교"

따라서 __slots__를 사용하는 클래스에서 상속을 적용할 때는 부모와 자식 클래스의 슬롯 구조도 함께 고려해야 합니다.

상속에 대해서는 뒤에서 별도의 글을 통해 자세히 살펴볼 예정입니다.

__dict__와 __slots__를 어떻게 선택할까?

처음부터 __slots__를 사용해야 하는 것은 아닙니다.

일반적인 Python 클래스에서는 __dict__를 사용하는 기본 동작을 그대로 활용하는 경우가 많습니다.

예를 들어:

class User:

    def __init__(self, name):
        self.name = name

정도로 충분한 상황이라면 굳이 __slots__를 사용할 필요가 없습니다.

반면 다음과 같은 조건이라면 __slots__를 고려할 수 있습니다.

  • 동일한 구조의 객체가 매우 많이 생성된다.
  • 사용할 속성이 명확하게 정해져 있다.
  • 객체에 새로운 속성을 추가할 필요가 없다.
  • 메모리 사용량을 줄이는 것이 중요하다.

결국 중요한 것은 __slots__가 더 좋은 문법이라는 것이 아니라 목적에 따라 선택하는 것입니다.

마무리

Python 클래스에서 객체의 속성을 관리하는 기본적인 방식에는 __dict__가 있습니다.

class Person:

    def __init__(self, name, age):
        self.name = name
        self.age = age

객체의 속성을 확인하면:

person = Person("철수", 20)

print(person.__dict__)

다음과 같이 확인할 수 있습니다.

{'name': '철수', 'age': 20}

반면 __slots__를 사용하면 사용할 속성을 미리 지정할 수 있습니다.

class Person:

    __slots__ = ("name", "age")

이 경우 지정되지 않은 속성을 동적으로 추가할 수 없고, 기본적으로 인스턴스의 __dict__도 사용할 수 없습니다.

핵심만 정리하면 다음과 같습니다.

  • __dict__는 객체의 속성을 딕셔너리 형태로 관리할 수 있게 해준다.
  • 일반적인 Python 클래스에서는 __dict__ 기반의 속성 관리가 사용된다.
  • __dict__를 사용하면 객체에 새로운 속성을 동적으로 추가할 수 있다.
  • __slots__는 사용할 인스턴스 속성을 미리 지정하는 기능이다.
  • __slots__를 사용하면 동적 속성 추가가 제한된다.
  • __slots__를 사용하면 기본적으로 인스턴스의 __dict__가 생성되지 않는다.
  • 동일한 구조의 객체가 매우 많을 때 메모리 사용을 줄이는 데 도움이 될 수 있다.
  • 그렇다고 모든 클래스에 __slots__를 사용하는 것이 좋은 것은 아니다.

다음 글에서는 클래스의 속성을 직접 노출하지 않고 property를 이용해 안전하게 읽고 변경하는 방법을 알아보겠습니다. @property와 setter를 이용하면 단순한 변수처럼 보이는 속성에 값 검증이나 추가 로직을 넣을 수 있습니다.

jiji

AI, Python, 생활정보와 정부 정책 정보를 쉽게 설명하는 정보 블로그입니다.

Ad Placeholder
Scroll to Top