03.1 데이터와 동작, 그리고 self
객체는 두 가지로 이루어진다. 속성(attribute)은 객체가 가진 데이터이고, 메서드(method)는 객체가 할 수 있는 동작이다. 계좌라면 잔액은 속성, 입금은 메서드다. 속성은 self.balance처럼 값으로 남고, 메서드는 클래스 안에 정의된 함수로 남는다.
둘을 잇는 것이 self다. self는 이 메서드를 부른 바로 그 객체 자신을 가리키는 이름이다. 메서드 안에서 자기 속성을 읽거나 쓰려면 항상 self.을 앞에 붙인다. self가 없으면 그 이름은 그냥 지역 변수가 되어 객체의 속성과 아무 상관이 없어진다. self가 정확히 무슨 일을 하는지 예제로 확인한다.
파이썬은 self를 명시적으로 드러낸다. 어떤 언어는 이 객체 자신을 숨은 이름으로 몰래 넘겨서 코드에 안 보이지만, 파이썬은 메서드의 첫 매개변수 자리에 self를 직접 적게 한다. 처음엔 번거로워 보여도, 이 속성이 객체에 속한 것인지 그냥 지역 변수인지가 self. 하나로 한눈에 구분되는 이점이 있다. self라는 이름 자체는 강제가 아니라 관례다. 다른 이름을 써도 동작은 하지만, 모든 파이썬 코드가 self를 쓰므로 우리도 self를 쓴다.
03.2 self가 하는 일
카운터 객체가 자기 안의 count를 하나씩 늘린다. self 덕분에 늘어나는 count가 누구의 것인지 정해진다.
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
return self.count
cnt = Counter()
print(cnt.increment(), cnt.increment(), cnt.increment())
세 번 부르면 값이 누적된다.
1 2 3
cnt.increment()라고 쓰면 파이썬이 점 앞의 cnt를 self 자리에 자동으로 넣어준다. 그래서 메서드 안의 self.count는 cnt의 count를 가리킨다. Counter를 두 개 만들면 각자 자기 count를 따로 세는데, 그 구분을 해주는 것이 바로 self다. 메서드 정의에서 첫 매개변수 자리에 self를 쓰지만, 부를 때는 우리가 직접 넣지 않는다는 점이 핵심이다.
만약 self.count가 아니라 그냥 count라고 썼다면 어떻게 될까. 그 count는 메서드가 끝나면 사라지는 지역 변수가 되어, 다음번 호출 때 값이 남지 않는다. 1, 2, 3처럼 누적되는 것은 값이 객체 self 안에 저장되기 때문이다. 객체가 호출과 호출 사이에 상태를 기억한다는 것, 그리고 그 기억의 주소가 self라는 것을 이 한 줄짜리 출력이 보여준다. increment가 self.count를 return하므로 늘린 값을 바로 확인할 수도 있다.
03.3 self를 빠뜨리면 나는 에러
메서드 정의에서 self를 안 쓰면, 객체를 통해 부를 때 인자 개수가 어긋나 에러가 난다. 왜 그런지 정상 코드와 함께 본다.
class Temp:
def __init__(self, c):
self.c = c
def to_f(self):
return self.c * 9 / 5 + 32
t = Temp(100)
print(t.to_f())
# self를 빠뜨린 잘못된 메서드
try:
class Broken:
def show(): # self 없음
return "hi"
Broken().show()
except TypeError as e:
print("TypeError:", e)
정상 메서드는 값을 내고, 잘못된 메서드는 예외를 잡아 찍는다.
212.0
TypeError: Broken.show() takes 0 positional arguments but 1 was given
Broken().show()를 부르면 파이썬이 그 객체를 자동으로 첫 인자로 넘기는데, show가 인자를 하나도 안 받게 정의돼서 0개 받는데 1개가 왔다는 TypeError(타입 오류)가 난다. 인스턴스 메서드의 첫 매개변수는 언제나 self여야 한다는 규칙이 여기서 나온다. self는 장식이 아니라 파이썬이 객체를 넘겨줄 자리다.
정상 코드인 Temp의 to_f가 왜 잘 도는지도 같이 보면 대비가 분명하다. to_f는 self를 첫 매개변수로 받았기 때문에, t.to_f()로 부를 때 파이썬이 넘겨주는 t를 self로 정확히 받아낸다. 그 안에서 self.c는 t의 c인 100을 가리킨다. self를 적었느냐 빠뜨렸느냐, 이 사소해 보이는 차이가 메서드가 도느냐 터지느냐를 가른다. 에러 메시지에 positional argument라는 말이 보이면 대개 self를 빠뜨린 것이니 첫 자리부터 확인한다.
03.4 메서드가 다른 메서드를 부른다
메서드 안에서 자기 다른 메서드를 부를 때도 self를 붙인다. 사각형이 넓이를 구하는 area를 갖고, 설명 문장을 만드는 describe가 그 area를 self로 불러 쓴다.
class Rectangle:
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
def describe(self):
return f"넓이는 {self.area()}입니다"
r = Rectangle(4, 5)
print(r.describe())
describe가 area의 결과를 문장에 끼워 넣는다.
넓이는 20입니다
describe 안에서 self.area()라고 불렀기 때문에, 넓이를 구하는 로직은 area 한 곳에만 있다. 나중에 넓이 계산 방식이 바뀌어도 area만 고치면 describe는 저절로 새 계산을 따라간다. 같은 계산을 두 군데에 적지 않는 것, 이것이 메서드끼리 self로 협력할 때 얻는 이득이다.
여기서도 self가 빠지면 안 된다. describe 안에서 self 없이 그냥 area()라고 쓰면 파이썬은 그런 이름의 함수를 바깥에서 찾다가 못 찾고 에러를 낸다. area는 이 객체에 속한 메서드이므로 self.area()라고 해야 이 사각형의 area를 부른다. 속성을 읽을 때든 다른 메서드를 부를 때든, 객체 자신의 것을 건드릴 때는 예외 없이 self를 앞에 붙인다는 원칙이 그대로 적용된다.
03.5 정리
속성은 객체가 지닌 데이터, 메서드는 객체가 하는 동작이고, self는 그 둘을 묶어 이 객체 자신을 가리킨다. 메서드 안에서 속성을 다루거나 다른 메서드를 부를 때는 반드시 self.을 붙인다. self를 빠뜨리면 인자 개수가 어긋나 TypeError가 나므로, 인스턴스 메서드의 첫 자리는 항상 self로 둔다. 속성으로 상태를 담고, 메서드로 동작을 정의하고, self로 그 둘을 이 객체에 묶는 것, 이 세 가지가 객체 하나를 온전히 짜는 기본기다.