인스턴스·클래스·스태틱 메서드의 바인딩 원리를 이해하고 각 상황에서 올바른 메서드를 선택한다.
🎯 이 강의에서 배우는 것
파이썬의 메서드는 세 종류입니다: 인스턴스 메서드, 클래스 메서드, 스태틱 메서드. 많은 초보자가 이 세 가지를 혼동하거나 무조건 인스턴스 메서드만 씁니다. 언제 어떤 것을 선택해야 하는지 알면, 코드의 의도가 명확해지고 상속 시 올바르게 동작합니다. 세 메서드의 차이를 내부 바인딩 원리부터 이해합니다.
🔗 인스턴스 메서드 — 가장 일반적인 메서드
class Circle:
PI = 3.14159265358979 # 클래스 변수
def __init__(self, radius: float):
self.radius = radius # 인스턴스 변수
def area(self) -> float:
# self → 인스턴스. 인스턴스 상태와 클래스 상태 모두 접근 가능
return self.PI * self.radius ** 2
def scale(self, factor: float) -> None:
# 인스턴스 상태 변경
self.radius *= factor
def __repr__(self) -> str:
return f"Circle(radius={self.radius})"
c = Circle(5)
print(c.area()) # 78.539...
c.scale(2)
print(c) # Circle(radius=10)
# 인스턴스 메서드의 바인딩 원리 — 디스크립터 프로토콜
# c.area 는 Circle.area.__get__(c, Circle) 를 호출
# → c와 area 함수를 묶은 "바운드 메서드" 반환
print(type(c.area)) # <class 'method'> (바운드)
print(type(Circle.area)) # <class 'function'> (언바운드)
# 두 호출은 완전히 동일
print(c.area())
print(Circle.area(c)) # self를 명시적으로 전달
🏭 @classmethod — 팩토리 메서드와 대안 생성자
class Date:
def __init__(self, year: int, month: int, day: int):
self.year = year
self.month = month
self.day = day
@classmethod
def from_string(cls, date_str: str) -> "Date":
# cls: 클래스 자체 (인스턴스가 아님)
# 대안 생성자 — "2024-01-15" 형식의 문자열로 생성
year, month, day = map(int, date_str.split("-"))
return cls(year, month, day) # cls(...)로 인스턴스 생성
@classmethod
def today(cls) -> "Date":
import datetime
t = datetime.date.today()
return cls(t.year, t.month, t.day)
def __repr__(self):
return f"Date({self.year}, {self.month}, {self.day})"
def __str__(self):
return f"{self.year}-{self.month:02d}-{self.day:02d}"
# 여러 방법으로 생성
d1 = Date(2024, 1, 15)
d2 = Date.from_string("2024-01-15") # 클래스 메서드
d3 = Date.today() # 클래스 메서드
print(d1) # 2024-01-15
print(d2) # 2024-01-15
print(d1 == d2) # False (아직 __eq__ 없음)
# 상속 시 cls의 강점: cls가 실제 클래스를 가리킴
class JapaneseDate(Date):
def __str__(self):
return f"{self.year}年{self.month}月{self.day}日"
jd = JapaneseDate.from_string("2024-03-15")
print(jd) # 2024年03月15日 ← JapaneseDate 인스턴스!
print(type(jd)) # <class '__main__.JapaneseDate'>
# cls가 JapaneseDate여서 cls(year, month, day) → JapaneseDate(...)
# 만약 Date.from_string 에서 Date(...)로 하드코딩했다면 Date 인스턴스가 생겼을 것!
🔧 @staticmethod — 논리적 그루핑을 위한 유틸리티
class Date:
def __init__(self, year: int, month: int, day: int):
self.year = year
self.month = month
self.day = day
@staticmethod
def is_valid(year: int, month: int, day: int) -> bool:
# 클래스나 인스턴스 상태가 전혀 필요 없음
# self도 cls도 받지 않음
if not (1 <= month <= 12):
return False
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if month == 2 and (year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)):
return day <= 29
return 1 <= day <= days_in_month[month]
@classmethod
def from_string(cls, date_str: str) -> "Date":
y, m, d = map(int, date_str.split("-"))
if not cls.is_valid(y, m, d): # 클래스 메서드에서 스태틱 메서드 호출
raise ValueError(f"유효하지 않은 날짜: {date_str}")
return cls(y, m, d)
# 스태틱 메서드는 클래스·인스턴스 모두에서 호출 가능
print(Date.is_valid(2024, 2, 29)) # True (윤년)
print(Date.is_valid(2023, 2, 29)) # False
d = Date(2024, 1, 15)
print(d.is_valid(2024, 6, 31)) # False
# 스태틱 메서드 vs 모듈 함수의 차이
# → 스태틱은 클래스 네임스페이스 안에 두어 "이 함수는 이 클래스와 관련 있음"을 표현
# → 모듈 수준 함수로 빼도 기술적으로 동일하지만 응집도가 낮아짐
📊 세 메서드 비교표와 선택 기준
| 종류 | 선언 | 첫 번째 인자 | 인스턴스 상태 | 클래스 상태 | 주요 용도 |
|---|---|---|---|---|---|
| 인스턴스 메서드 | def m(self) | 인스턴스 | ✅ | ✅ (self.__class__) | 일반 동작, 상태 변경 |
| 클래스 메서드 | @classmethod def m(cls) | 클래스 | ❌ | ✅ | 팩토리, 대안 생성자, 서브클래스 인식 |
| 스태틱 메서드 | @staticmethod def m() | 없음 | ❌ | ❌ | 유틸리티, 논리적 그루핑 |
# 선택 기준 예시
class BankAccount:
_interest_rate = 0.03 # 클래스 변수
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
# 인스턴스 메서드: 인스턴스 상태(self._balance) 필요
def deposit(self, amount):
self._balance += amount
def get_balance(self):
return self._balance
# 클래스 메서드: 클래스 상태(_interest_rate) 변경, 상속 시 cls 중요
@classmethod
def set_interest_rate(cls, rate: float):
cls._interest_rate = rate
@classmethod
def from_dict(cls, data: dict) -> "BankAccount":
return cls(data["owner"], data.get("balance", 0))
# 스태틱 메서드: 클래스/인스턴스 상태 불필요 — 순수 계산
@staticmethod
def format_money(amount: float) -> str:
return f"{amount:,.0f}원"
acc = BankAccount("홍길동", 100000)
acc.deposit(50000)
print(BankAccount.format_money(acc.get_balance())) # 150,000원
BankAccount.set_interest_rate(0.05)
acc2 = BankAccount.from_dict({"owner": "이순신", "balance": 200000})
print(acc2.owner, acc2._balance)
⚠️ 자주 하는 실수
- @classmethod에서 self 사용:
@classmethod def m(self, ...)라고 쓰면 파이썬은 오류를 내지 않지만, 실제로self에는 클래스가 전달됩니다. 관례적으로cls를 사용하고 인스턴스 상태가 필요하면 클래스 메서드가 아닌 인스턴스 메서드를 씁니다. - 팩토리 메서드에서 cls 대신 클래스 이름 하드코딩:
return Date(y, m, d)로 하드코딩하면 서브클래스에서 호출해도 항상Date가 반환됩니다. 반드시return cls(y, m, d)를 사용하세요. - 인스턴스·클래스 모두 불필요한데 인스턴스 메서드 사용: self나 cls를 전혀 사용하지 않는 인스턴스 메서드는
@staticmethod로 바꾸면 의도가 명확해집니다.
📝 정리 및 다음 강의 예고
- 인스턴스 메서드: self로 인스턴스 상태 접근. 가장 일반적.
- @classmethod: cls로 클래스 상태 접근. 팩토리 패턴, 상속 시 서브클래스 인식.
- @staticmethod: self/cls 없음. 클래스 네임스페이스 안 유틸리티.
- 선택 기준: 인스턴스 상태 → 인스턴스 메서드 / 클래스 상태나 서브클래스 인식 → 클래스 메서드 / 둘 다 불필요 → 스태틱 메서드.
다음 강의: 상속과 다형성 — 코드를 재사용하고 확장하는 OOP의 핵심. super()의 내부, MRO(메서드 해석 순서), 다형성을 활용한 설계를 배웁니다.
관련 주제
- 인스턴스 메서드
- @classmethod
- @staticmethod
- 디스크립터 프로토콜
- 팩토리 메서드 패턴
- 바운드 메서드
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 파이썬 기초 40강 — 처음 배우는 프로그래밍
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
파이썬 기초 40강 — 처음 배우는 프로그래밍
이 강의가 속한 시리즈는 총 40강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
0/1000
불러오는 중...
