Python self와 cls 이해하기
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=20quiz1, quiz2는 객체 그 자체가 아니라 각 객체를 참조하는 이름이다.
2. 인스턴스마다 메서드가 복사되는 것은 아니다
처음에는 다음처럼 생각하기 쉽다.
quiz1
├── score = 10
└── add_score()
quiz2
├── score = 20
└── add_score()하지만 실제로는 기본적으로 다음처럼 생각하는 것이 좋다.
Quiz 클래스 객체
┌────────────────────┐
│ add_score() │
│ __init__() │
└────────────────────┘
↑ ↑
│ │
quiz1 quiz2
score = 10 score = 20score같은 개별 상태는 각 인스턴스가 가진다.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 = quiz24. 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)
# easyquiz1 자체에는 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):
passQuiz.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.namecls
cls → 현재 클래스 객체
Quiz.class_method()
→ cls = Quiz
HardQuiz.class_method()
→ cls = HardQuiz클래스 전체의 상태를 다루거나, 현재 클래스를 이용해 새로운 객체를 만들 때 사용할 수 있다.
cls.count
cls.difficulty
cls(...)한 문장으로 기억하기
self= 지금 이 인스턴스는 누구인가?
cls= 지금 이 클래스는 누구인가?
그리고 self나 cls 자체가 상태를 변경하는 것은 아니다.
둘은 단순히 각각 인스턴스 객체와 클래스 객체를 참조할 뿐이며, 실제로 무엇을 읽고 변경할지는 뒤에 작성된 코드가 결정한다.