Python self와 cls 이해하기

2026년 8월 25일·
작성:홍용재

1. 클래스도 런타임에 존재하는 객체다

class Quiz:
    def __init__(self, score):
        self.score = score

    def add_score(self):
        self.score += 10

파이썬이 class Quiz:를 실행하면 런타임에 Quiz라는 클래스 객체가 만들어진다.

Quiz ─────→ [ Quiz 클래스 객체 ]

그리고:

quiz1 = Quiz(10)
quiz2 = Quiz(20)

를 실행하면 Quiz 클래스로부터 서로 다른 인스턴스 객체가 만들어진다.

               Quiz 클래스 객체
                /          \
               /            \
quiz1 ─→ [인스턴스]      quiz2 ─→ [인스턴스]
          score=10                   score=20

quiz1, quiz2는 객체 그 자체가 아니라 각 객체를 참조하는 이름이다.


2. 인스턴스마다 메서드가 복사되는 것은 아니다

처음에는 다음처럼 생각하기 쉽다.

quiz1
├── score = 10
└── add_score()

quiz2
├── score = 20
└── add_score()

하지만 실제로는 기본적으로 다음처럼 생각하는 것이 좋다.

               Quiz 클래스 객체
          ┌────────────────────┐
          │ add_score()        │
          │ __init__()         │
          └────────────────────┘
               ↑          ↑
               │          │
             quiz1      quiz2
          score = 10   score = 20
  • score 같은 개별 상태는 각 인스턴스가 가진다.
  • add_score 같은 공통 메서드는 클래스 쪽에 존재한다.

3. self란?

self는 현재 메서드가 동작하고 있는 인스턴스 객체 자기 자신을 참조한다.

quiz1.add_score()

를 호출하면 개념적으로 다음과 비슷하게 동작한다.

Quiz.add_score(quiz1)

메서드 정의가:

def add_score(self):
    self.score += 10

이므로 이 호출에서는:

self → quiz1

이 된다.

따라서:

self.score += 10

은 사실상:

quiz1.score += 10

이라는 의미다.

핵심

self = 현재 작업 대상인 인스턴스

예:

quiz1.add_score()
→ self = quiz1

quiz2.add_score()
→ self = quiz2

4. quiz1.add_score()가 가능한 이유

quiz1 자체에 add_score 함수가 복사되어 있는 것은 아니다.

파이썬은 quiz1.add_score를 요청받으면 인스턴스와 클래스의 관계를 이용해 Quiz에서 add_score를 찾는다.

quiz1.add_score

quiz1에 add_score가 있는지 확인
        ↓
필요하면 quiz1의 클래스 확인
        ↓
       Quiz
        ↓
add_score 발견

그 후 add_score와 quiz1을 연결해서 호출한다.

Quiz.add_score
     +
   quiz1
     ↓
self = quiz1

따라서:

quiz1.add_score()

와

Quiz.add_score(quiz1)

는 개념적으로 연결되어 있다.


5. cls란?

cls는 인스턴스가 아니라 현재 클래스 객체 자체를 참조한다.

class Quiz:
    count = 0

    @classmethod
    def increase_count(cls):
        cls.count += 1

호출:

Quiz.increase_count()

이때:

cls → Quiz 클래스 객체

이므로:

cls.count += 1

은 사실상:

Quiz.count += 1

과 같은 의미다.

핵심

self = 현재 인스턴스 객체
cls  = 현재 클래스 객체

6. 인스턴스 상태와 클래스 상태

class Quiz:
    difficulty = "easy"

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

구조를 단순화하면:

Quiz 클래스 객체
└── difficulty = "easy"

quiz1 인스턴스
└── score = 10

quiz2 인스턴스
└── score = 20

여기서:

self.score

는 인스턴스마다 다른 값이다.

반면:

cls.difficulty

또는:

Quiz.difficulty

는 클래스 쪽에 존재하는 값이다.


7. cls로 클래스 상태를 변경하면 인스턴스에도 영향을 줄 수 있다

class Quiz:
    difficulty = "easy"

    @classmethod
    def change_difficulty(cls):
        cls.difficulty = "hard"
quiz1 = Quiz()

print(quiz1.difficulty)
# easy

quiz1 자체에는 difficulty가 없기 때문에 Quiz.difficulty를 찾아서 사용한다.

quiz1.difficulty
      ↓
quiz1에 없음
      ↓
Quiz.difficulty
      ↓
"easy"

이제:

Quiz.change_difficulty()

를 실행하면:

cls → Quiz

이므로:

Quiz.difficulty = "hard"

가 된다.

따라서 기존 인스턴스에서도:

print(quiz1.difficulty)
# hard

가 나온다.

새 인스턴스를 만들어도:

quiz2 = Quiz()

print(quiz2.difficulty)
# hard

가 된다.

즉 클래스 상태를 참조하고 있는 인스턴스라면 기존 인스턴스와 앞으로 만들어질 인스턴스 모두 영향을 받을 수 있다.


8. 하지만 인스턴스가 자기 값을 가지고 있다면 다르다

class Quiz:
    difficulty = "easy"

    def __init__(self):
        self.difficulty = "normal"

이 경우:

Quiz
└── difficulty = "easy"

quiz1
└── difficulty = "normal"

quiz1.difficulty를 찾을 때 자기 자신의 값이 먼저 발견된다.

quiz1.difficulty
      ↓
quiz1에 있음
      ↓
"normal"

따라서:

Quiz.difficulty = "hard"

로 클래스 값을 변경해도:

print(quiz1.difficulty)
# normal

이다.


9. cls는 클래스 상태 변경에만 사용하는 것이 아니다

cls는 단순히 현재 클래스 객체를 참조하는 변수다.

따라서:

cls.count

→ 클래스 상태 읽기

cls.count = 10

→ 클래스 상태 변경

cls()

→ 현재 클래스로 인스턴스 생성

return cls

→ 클래스 객체 자체 반환

등 여러 용도로 사용할 수 있다.


10. cls()를 사용하는 이유

class Quiz:
    def __init__(self, score):
        self.score = score

    @classmethod
    def from_string(cls, text):
        score = int(text)
        return cls(score)
quiz = Quiz.from_string("100")

호출 시:

cls → Quiz

이므로:

return cls(100)

은 현재 상황에서는:

return Quiz(100)

과 같다.

즉 cls를 이용해 현재 클래스의 새로운 인스턴스를 생성할 수 있다.


11. 왜 그냥 Quiz()라고 하지 않고 cls()라고 할까?

상속 때문에 차이가 생긴다.

class Quiz:
    @classmethod
    def create(cls):
        return cls()


class HardQuiz(Quiz):
    pass
Quiz.create()

에서는:

cls = Quiz
→ Quiz() 생성

하지만:

HardQuiz.create()

에서는:

cls = HardQuiz
→ HardQuiz() 생성

즉 cls는 무조건 부모 클래스인 Quiz를 의미하는 것이 아니다.

실제로 클래스 메서드를 호출한 클래스를 가리킨다.


최종 정리

class Quiz:
    ...
        │
        ↓
[ Quiz 클래스 객체 ]
        │
        ├─────────────┐
        ↓             ↓
   [ quiz1 ]      [ quiz2 ]
   인스턴스         인스턴스

self

self → 현재 생성된 인스턴스 객체

quiz1.method()
→ self = quiz1

quiz2.method()
→ self = quiz2

주로 개별 인스턴스의 상태를 다룬다.

self.score
self.name

cls

cls → 현재 클래스 객체

Quiz.class_method()
→ cls = Quiz

HardQuiz.class_method()
→ cls = HardQuiz

클래스 전체의 상태를 다루거나, 현재 클래스를 이용해 새로운 객체를 만들 때 사용할 수 있다.

cls.count
cls.difficulty
cls(...)

한 문장으로 기억하기

self = 지금 이 인스턴스는 누구인가?

cls = 지금 이 클래스는 누구인가?

그리고 self나 cls 자체가 상태를 변경하는 것은 아니다.
둘은 단순히 각각 인스턴스 객체와 클래스 객체를 참조할 뿐이며, 실제로 무엇을 읽고 변경할지는 뒤에 작성된 코드가 결정한다.