클래스 안에서 정의된 함수, 즉 ‘메서드’는 단순한 기능 이상의 의미를 가집니다. 객체가 할 수 있는 행동을 정의하며, 객체 중심 프로그래밍의 핵심이죠. 그런데 메서드를 처음 배우는 분들이 가장 많이 헷갈려하는 것이 하나 있습니다. 바로 메서드 정의 시 등장하는 self
입니다. 대체 이 self는 왜 꼭 넣어야 할까요? 함수처럼 보이지만 클래스 안에 들어가면 달라지는 점은 뭘까요?
이번 장에서는 파이썬의 클래스 메서드 구조를 자세히 살펴보고, self의 진짜 역할과 필요성을 명확히 정리합니다. 클래스 구조를 제대로 이해하고, 객체 중심으로 생각하는 연습을 하고 싶다면 이 장은 결코 건너뛸 수 없습니다.
- 클래스 메서드란 무엇인가?
- 메서드와 일반 함수의 차이
- self의 의미 다시 보기
- self를 사용하는 이유
- 메서드 내부에서 인스턴스 변수 접근하기
- self 없이 메서드를 정의하면 생기는 문제
- 여러 개의 메서드와 self의 활용
- 메서드에서 다른 메서드 호출하기
- self와 클래스 변수 구분하기
- 실전 예제: 계산기 클래스 구현
클래스 메서드란 무엇인가?
파이썬에서 클래스는 단순히 데이터를 저장하는 구조가 아니라, 그 데이터와 관련된 기능을 함께 정의하는 구조입니다. 이 기능을 정의하는 것이 바로 "메서드"입니다. 메서드는 클래스 내부에 정의된 함수로, 객체가 수행할 수 있는 동작을 의미합니다. 예를 들어, 학생 클래스에서는 출석 확인, 성적 계산, 자기소개 등의 기능이 메서드가 될 수 있습니다.
메서드는 일반 함수처럼 보이지만, 반드시 첫 번째 매개변수로 self
를 포함해야 합니다. self
는 해당 메서드를 호출한 객체 자신을 가리키는 특별한 이름입니다. 이를 통해 메서드는 객체의 내부 상태(즉, 인스턴스 변수)에 접근하거나 수정할 수 있습니다.
class Student:
def __init__(self, name):
self.name = name
def introduce(self): # self가 반드시 있어야 함
print(f"안녕하세요. 저는 {self.name}입니다.")
s = Student("지훈")
s.introduce() # 출력: 안녕하세요. 저는 지훈입니다.
위 예제에서 introduce()
는 Student 클래스의 메서드입니다. 객체 s
가 해당 메서드를 호출하면, self
는 자동으로 s
객체를 참조하게 되고, 그 결과 해당 객체의 name
에 접근할 수 있게 됩니다. 이렇게 메서드는 객체의 데이터를 바탕으로 동작하는 구조이기 때문에, 클래스와 객체 지향 프로그래밍의 핵심 요소라 할 수 있습니다.
메서드와 일반 함수의 차이
함수와 메서드는 겉으로 보기엔 비슷해 보이지만, 소속과 사용 방식에서 분명한 차이가 있습니다. 일반 함수는 클래스 바깥에 정의되어 독립적으로 호출되는 반면, 메서드는 클래스 내부에 정의되어 객체를 통해 호출되는 함수입니다. 그리고 메서드는 호출될 때 자동으로 self
인자를 통해 객체 자신에 접근할 수 있는 특징이 있습니다.
일반 함수는 객체의 상태나 변수와는 관계없이 동작합니다. 반면 메서드는 객체의 상태(state)를 기반으로 동작하며, 이를 수정하거나 활용하는 데 집중합니다. 이러한 구조 덕분에 메서드는 객체지향 프로그래밍에서 중요한 구성 요소로 자리잡습니다.
# 일반 함수
def greet(name):
print(f"안녕하세요, {name}님!")
# 클래스 메서드
class Greeter:
def __init__(self, name):
self.name = name
def greet(self): # self는 필수
print(f"안녕하세요, {self.name}님!")
greet("민지") # 일반 함수 호출
g = Greeter("영수")
g.greet() # 메서드 호출
위 예제에서 일반 함수 greet()
는 외부에서 인자를 받아 출력하지만, 클래스의 greet()
메서드는 객체 내부에 저장된 name
을 사용합니다. 이처럼 메서드는 객체의 속성과 강하게 연결되어 있어, 프로그램의 구조화와 재사용성을 높이는 데 중요한 역할을 합니다.
self의 의미 다시 보기
파이썬 클래스에서 self
는 메서드의 첫 번째 매개변수로 사용되며, 해당 메서드를 호출한 인스턴스 자신을 가리키는 참조입니다. self를 통해 객체의 내부 변수(인스턴스 변수)에 접근하고, 다른 메서드를 호출할 수도 있습니다. self는 메서드가 자신이 속한 객체와 상호작용할 수 있도록 연결해주는 다리 역할을 합니다.
많은 초보자들이 self
를 단순한 예약어처럼 여기기도 하지만, 사실 그 이름은 개발자가 변경할 수도 있는 일반적인 매개변수입니다. 다만 관례적으로 self
를 사용하는 것이 읽기 쉽고, Python 커뮤니티에서도 그렇게 권장하고 있습니다.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rect = Rectangle(4, 5)
print(rect.area()) # 출력: 20
위 예제에서 self.width
와 self.height
는 객체 내부에 저장된 데이터를 의미하며, area()
메서드는 self를 통해 그 값에 접근합니다. 만약 self가 없다면 메서드는 객체의 상태를 알 수 없기 때문에, 클래스 내부에서 동작하는 모든 인스턴스 메서드에는 self가 반드시 필요합니다.
즉, self는 단순한 문법이 아니라 객체 지향의 핵심 개념 중 하나이며, 메서드가 자신의 객체와 연결될 수 있도록 해주는 중요한 연결 고리입니다.
self를 사용하는 이유
클래스 내부에서 메서드를 정의할 때 self
를 사용하는 이유는 명확합니다. self는 메서드가 객체 자신의 속성과 메서드에 접근할 수 있게 해주는 통로이기 때문입니다. 즉, self가 없다면 해당 객체의 고유 데이터를 참조할 수 없고, 클래스 설계의 목적도 퇴색됩니다.
예를 들어 두 개의 Rectangle 객체를 만들었다고 가정해봅시다. 각 객체는 다른 크기를 가질 수 있는데, 메서드에서 self를 사용하지 않으면 어떤 객체의 데이터를 사용해야 할지 파이썬은 알 수 없습니다. 결국 self는 ‘어떤 인스턴스가 이 메서드를 호출했는가’를 알려주는 중요한 단서입니다.
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def describe(self):
print(f"너비: {self.width}, 높이: {self.height}")
r1 = Rectangle(4, 5)
r2 = Rectangle(10, 3)
r1.describe() # 너비: 4, 높이: 5
r2.describe() # 너비: 10, 높이: 3
위 예제에서 describe()
메서드는 self.width
와 self.height
를 통해 각각의 객체가 가진 값을 출력합니다. r1
과 r2
는 동일한 메서드를 호출했지만 결과는 다릅니다. 이는 self 덕분에 메서드가 호출된 객체의 상태를 정확히 참조할 수 있기 때문입니다.
결론적으로, self는 인스턴스 메서드가 객체 자신의 정보를 알고, 동작을 그에 맞게 수행할 수 있도록 보장하는 핵심 메커니즘입니다.
메서드 내부에서 인스턴스 변수 접근하기
메서드 내부에서는 self를 통해 인스턴스 변수에 접근할 수 있습니다. 인스턴스 변수는 객체마다 다르게 설정되는 값이므로, 메서드가 동작할 때 해당 객체의 상태에 따라 결과가 달라지게 됩니다. 이때 self를 사용하지 않고 변수 이름만 사용하면, 지역 변수로 간주되어 원하는 값을 얻지 못하거나 오류가 발생할 수 있습니다.
이 구조는 객체의 상태를 기반으로 동작하는 메서드를 정의할 수 있게 해주며, 객체지향 프로그래밍의 핵심인 데이터와 기능의 결합을 실현하는 중요한 방식입니다.
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
def check_balance(self):
print(f"{self.owner}님의 잔액은 {self.balance}원입니다.")
account1 = BankAccount("지훈", 50000)
account2 = BankAccount("민지", 120000)
account1.check_balance() # 지훈님의 잔액은 50000원입니다.
account2.check_balance() # 민지님의 잔액은 120000원입니다.
위 예제에서 check_balance()
메서드는 self.owner
와 self.balance
를 통해 객체의 정보를 읽어옵니다. 만약 self
없이 owner
또는 balance
만 사용했다면, 파이썬은 해당 이름의 지역 변수를 찾으려 하고, 결국 정의되지 않았다는 오류(NameError)가 발생하게 됩니다.
이처럼 메서드 내부에서 인스턴스 변수에 접근하려면 반드시 self.변수명
형식을 사용해야 하며, 이는 객체 지향 코드의 명확성과 안정성을 확보하는 데 필수적인 규칙입니다.
self 없이 메서드를 정의하면 생기는 문제
클래스 내부에서 메서드를 정의할 때 self
를 생략하면 심각한 오류가 발생합니다. 파이썬은 메서드를 호출할 때 자동으로 첫 번째 인자로 호출한 인스턴스를 전달합니다. 하지만 self
를 명시하지 않으면 이 인자를 받을 수 없기 때문에 매개변수 개수 불일치(TypeError)가 발생하게 됩니다.
많은 초보자들이 def greet():
처럼 일반 함수처럼 메서드를 작성하고 나서, TypeError: greet() takes 0 positional arguments but 1 was given
오류를 마주하곤 합니다. 이는 파이썬이 자동으로 인스턴스를 넘기는데, 받을 변수가 없기 때문입니다.
class Person:
def greet(): # ❌ self 없음
print("안녕하세요!")
p = Person()
p.greet() # TypeError 발생
위 예제에서 greet()
는 인스턴스 메서드처럼 호출되었지만, self
를 받지 않도록 정의되어 있어 오류가 발생합니다. 올바른 방식은 다음과 같습니다.
class Person:
def greet(self): # ✅ self 포함
print("안녕하세요!")
p = Person()
p.greet() # 출력: 안녕하세요!
이처럼 클래스 내부의 메서드를 정의할 때는 항상 첫 번째 인자로 self를 포함해야 하며, 이를 통해 객체의 상태에 안전하게 접근할 수 있습니다. self를 생략한 실수는 작은 문법 오류처럼 보이지만, 객체지향 구조 전체를 무너뜨릴 수 있는 중요한 문제입니다.
여러 개의 메서드와 self의 활용
클래스 내부에는 하나의 메서드만 존재하는 것이 아니라, 객체가 수행해야 할 다양한 동작들을 각각의 메서드로 나누어 정의할 수 있습니다. 이때 각 메서드는 공통적으로 self를 사용하여 객체 자신의 상태에 접근하고, 다른 메서드들과도 데이터를 공유할 수 있습니다. 즉, 메서드 간에 인스턴스 변수를 통해 정보를 주고받는 구조가 됩니다.
예를 들어, 은행 계좌 클래스를 만든다고 할 때, 입금, 출금, 잔액 확인 등 다양한 기능이 필요합니다. 이 각각의 기능은 메서드로 나누어 구현되며, self를 통해 동일한 객체 상태를 관리하게 됩니다.
class BankAccount:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if self.balance >= amount:
self.balance -= amount
else:
print("잔액이 부족합니다.")
def check_balance(self):
print(f"{self.owner}님의 잔액: {self.balance}원")
이제 이 클래스는 다음처럼 사용할 수 있습니다:
account = BankAccount("지수")
account.deposit(10000)
account.withdraw(3000)
account.check_balance() # 출력: 지수님의 잔액: 7000원
위 예제처럼 각 메서드는 self.balance
를 통해 객체의 상태를 수정하고 공유합니다. 이렇게 여러 메서드 간 상태를 일관성 있게 유지할 수 있기 때문에, 객체지향 프로그래밍에서 self는 단순한 약속 이상의 의미를 가집니다. 잘 설계된 클래스는 여러 메서드가 서로 협력하여 유기적으로 동작합니다.
메서드에서 다른 메서드 호출하기
파이썬 클래스에서는 하나의 메서드 안에서 다른 메서드를 호출할 수 있습니다. 이때도 중요한 키워드는 self
입니다. 클래스 내부의 메서드는 객체에 종속되어 있으므로, 같은 클래스의 다른 메서드를 사용할 때 self.메서드명()
형태로 호출해야 합니다. 이렇게 하면 메서드 간의 로직 재사용이 가능해지고, 중복 코드를 줄이며 클래스 구조를 더 깔끔하게 유지할 수 있습니다.
예를 들어, 어떤 메서드에서 데이터를 계산한 후 그 결과를 출력하는 작업이 있다면, 출력은 별도 메서드로 분리한 뒤 self
를 통해 호출하는 식으로 구성할 수 있습니다. 아래 예제를 보겠습니다.
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius ** 2
def describe(self):
print(f"반지름: {self.radius}")
print(f"넓이: {self.area()}") # self를 통해 다른 메서드 호출
위 예제에서 describe()
메서드는 area()
메서드를 호출하여 원의 넓이를 출력합니다. self.area()
라고 작성해야 현재 인스턴스의 메서드에 접근할 수 있으며, 이 호출 방식은 객체의 내부 상태에 따라 동적으로 동작합니다.
이처럼 클래스 내부에서 메서드 간 호출이 필요한 경우, 항상 self.메서드명()
형태를 사용해야 객체 자신의 메서드와 연결됩니다. 이를 통해 객체 중심의 유기적 구조 설계가 가능해지고, 유지보수와 확장성도 좋아집니다.
self와 클래스 변수 구분하기
클래스를 학습하다 보면 인스턴스 변수와 클래스 변수를 혼동하기 쉽습니다. 인스턴스 변수는 self.변수명
으로 선언되어 각 객체가 고유하게 가지는 값을 저장합니다. 반면, 클래스 변수는 클래스 정의 내부에서 모든 인스턴스가 공유하는 값으로 설정되며, self
없이 클래스 이름으로 선언됩니다.
이 두 변수를 구분하는 것은 매우 중요합니다. 잘못 사용하면 객체 간 데이터가 공유되어 예기치 않은 동작이 발생할 수 있습니다. 아래 예제를 통해 차이를 확실히 알아보겠습니다.
class Robot:
kind = "AI" # 클래스 변수
def __init__(self, name):
self.name = name # 인스턴스 변수
r1 = Robot("Alpha")
r2 = Robot("Beta")
print(r1.name) # Alpha
print(r2.name) # Beta
print(r1.kind) # AI
print(r2.kind) # AI
Robot.kind = "Humanoid"
print(r1.kind) # Humanoid
print(r2.kind) # Humanoid
위 코드에서 name
은 self를 사용해 정의된 인스턴스 변수로, 객체마다 다른 값을 가집니다. 반면 kind
는 클래스 변수이므로 클래스 전체에서 공유되고, Robot.kind
를 변경하면 모든 인스턴스에 반영됩니다.
하지만 주의할 점도 있습니다. self.kind = "X"
처럼 클래스 변수 이름으로 인스턴스 변수를 선언하게 되면, 해당 인스턴스에 새로운 변수가 생기고 클래스 변수와 분리됩니다. 따라서 변수의 용도에 따라 self 사용 여부를 명확히 판단해야 합니다. 객체의 고유 속성은 self로 선언된 인스턴스 변수를 사용하세요.
실전 예제: 계산기 클래스 구현
지금까지 학습한 self
의 개념과 메서드 정의 방식을 종합하여, 간단한 계산기 클래스를 구현해보겠습니다. 이 클래스는 더하기, 빼기, 곱하기, 나누기 기능을 메서드로 포함하며, 각 연산 결과를 저장하는 인스턴스 변수 result
를 활용합니다.
class Calculator:
def __init__(self):
self.result = 0 # 초기값 설정
def add(self, value):
self.result += value
def subtract(self, value):
self.result -= value
def multiply(self, value):
self.result *= value
def divide(self, value):
if value != 0:
self.result /= value
else:
print("0으로 나눌 수 없습니다.")
def show_result(self):
print(f"현재 값: {self.result}")
이제 이 계산기 클래스를 활용해 연산을 실행해보겠습니다.
calc = Calculator()
calc.add(10)
calc.subtract(3)
calc.multiply(4)
calc.divide(2)
calc.show_result() # 출력: 현재 값: 14.0
이 예제에서 각 연산 메서드는 self.result
에 접근하여 값을 갱신하며, show_result()
는 현재 값을 출력합니다. 또한 divide()
에서는 0으로 나누는 경우를 예외적으로 처리하여, 메서드 간 상태 공유와 예외 처리를 함께 구현한 점이 눈에 띕니다.
이처럼 클래스와 메서드를 조합하면 단순한 수학 연산도 객체지향적으로 모델링할 수 있습니다. 학습자가 직접 다양한 기능을 추가해보며 클래스 설계에 익숙해지는 데 매우 좋은 연습이 됩니다. 예를 들어 초기값 설정, 연산 기록 기능 등을 추가해보는 것도 좋은 확장 방향입니다.
🔚 결론: self를 이해하면 객체지향이 보인다
이번 장에서는 클래스 내부에서 정의되는 메서드의 구조와, 그 핵심 매개변수인 self
의 의미를 깊이 있게 살펴보았습니다. self
는 단순한 문법 요소가 아니라, 객체 자신을 참조하기 위한 중요한 수단으로, 메서드가 객체의 상태와 동작을 연결하는 역할을 합니다. 이를 통해 각 인스턴스는 독립적인 속성과 행동을 가질 수 있고, 객체지향 프로그래밍의 근간인 데이터와 기능의 결합이 실현됩니다.
또한, 여러 메서드 간의 협력, 메서드 내부에서 인스턴스 변수 접근, 클래스 변수와의 구분 등도 실습을 통해 익혀보았습니다. 이러한 개념을 바탕으로 스스로 간단한 클래스 구조를 설계하고 확장하는 연습을 해보는 것이 좋습니다.
다음 장에서는 클래스 외부에서 정의된 정적 메서드(static method), 클래스 메서드(class method) 개념을 학습하며, 메서드의 다양한 종류와 활용법을 더 확장해보겠습니다.
'프로그래밍 > 파이썬' 카테고리의 다른 글
13장. 상속과 다형성 (1) | 2025.05.26 |
---|---|
11장. 생성자 __init__()과 인스턴스 변수 (4) | 2025.05.26 |
10장. 클래스와 인스턴스 개념 (1) | 2025.05.19 |
9장. 예외 처리 (try, except, finally, raise) (0) | 2025.05.19 |
8장. 함수 정의와 호출 (매개변수, 반환값, 기본값, 키워드 인자) (0) | 2025.05.19 |