파이썬의 classmethod vs staticmethod
파이썬의 classmethod와 staticmethod를 예제로 비교한다.
요약
메서드가 자신이 속한 클래스 를 알아야 할 때
@classmethod를 쓴다 (예: 대체 생성자, 다형적 팩토리, 클래스 전역 설정). 인스턴스(self)나 클래스(cls) 접근이 필요 없고 그저 클래스 아래 네임스페이스 를 갖는 헬퍼 함수를 원할 때@staticmethod를 쓴다.
이 데코레이터들이 존재하는 이유
파이썬 메서드는 그저 클래스 위에 놓인 함수다. 접근하는 순간 파이썬의 디스크립터 프로토콜이 첫 번째 인자로 무엇을 바인딩할지 결정한다:
- 인스턴스 메서드 (기본): 인스턴스를 바인딩한다 → 첫 매개변수는
self. - classmethod: 클래스를 바인딩한다 → 첫 매개변수는
cls. - staticmethod: 아무것도 바인딩하지 않는다 → 암묵적 첫 매개변수가 없다.
이 바인딩 동작이 올바른 데코레이터를 고르는 핵심 사고 모델이다.
간단한 정의
-
@classmethod- 첫 매개변수가
cls(메서드를 호출한 실제 클래스)다. - 클래스 속성을 볼 수 있고, 인스턴스를 만들어 반환할 수 있으며, 상속과 다형성을 자연스럽게 지원한다.
- 첫 매개변수가
-
@staticmethod-
self나cls가 주입되지 않는다. - 정리나 발견 용이성을 위해 클래스 위에 놓인 순수 헬퍼 함수다.
-
나란히 놓고 보는 치트시트
| 특성 | 인스턴스 메서드 | classmethod | staticmethod |
|---|---|---|---|
| 첫 인자 | self | cls | (없음) |
| 인스턴스 상태 읽기 | ✅ | ❌ | ❌ |
| 클래스 상태 읽기/변경 | ⚠️ self.__class__ 경유 | ✅ | ❌ |
| 대체 생성자 | ❌ | ✅ | ❌ |
| 서브클래스별 다형적 디스패치 | ✅ | ✅ | ⚠️ (조회는 되지만 바인딩은 없음) |
| 유틸리티에 적합 | ❌ | ⚠️ | ✅ |
| 테스트 용이성 | 좋음 | 좋음 | 훌륭함 (순수 함수) |
@classmethod를 쓸 때
대체 생성자
다른 표현으로부터 인스턴스를 만드는 명확한 방법을 독자에게 제공한다.
class User:
def __init__(self, name, is_admin=False):
self.name = name
self.is_admin = is_admin
@classmethod
def from_env(cls):
import os
return cls(os.getenv("USER", "anonymous"))
@classmethod
def admin(cls, name):
return cls(name, is_admin=True)
다형적 팩토리
서브클래스는 classmethod를 상속받고 재정의할 수 있다. 서브클래스를 통해 호출하면 cls가 그 서브클래스에 바인딩되므로, 팩토리가 계속 올바른 타입을 반환한다.
class User:
def __init__(self, name):
self.name = name
@classmethod
def from_dict(cls, data):
return cls(data["name"])
이제 서브클래스가 이를 재사용하면서 자동으로 자기 타입을 반환한다:
class Admin(User):
pass
user = User.from_dict({"name": "Alice"})
admin = Admin.from_dict({"name": "Bob"})
print(type(user)) # <class '__main__.User'>
print(type(admin)) # <class '__main__.Admin'>
클래스 수준 설정과 레지스트리
공유되는 타입별 상태를 클래스에 둔다.
class Service:
_registry = {}
@classmethod
def register(cls, name, impl):
cls._registry[name] = impl
@classmethod
def get(cls, name):
return cls._registry[name]
typing.Self를 쓴 버전별/타입별 생성자
파이썬 3.11부터는 Self로 “호출한 클래스의 인스턴스를 반환한다”를 표현한다.
from typing import Self
class Config:
@classmethod
def load(cls, path: str) -> Self:
...
return cls(...)
@staticmethod를 쓸 때
클래스/인스턴스 상태를 건드리지 않는 순수 헬퍼
진짜 유틸리티라면 모듈 수준 함수를 선호하되, 클래스 옆에 두는 편이 발견 용이성이나 API 사용감을 개선한다면 예외로 한다.
class Password:
@staticmethod
def hash(plain: str) -> str:
import hashlib
return hashlib.sha256(plain.encode()).hexdigest()
여러 생성자가 쓰는 검증이나 작은 변환
관련 헬퍼를 쓰이는 곳 가까이 둔다.
class Color:
@staticmethod
def _clamp(x: int) -> int:
return max(0, min(255, x))
경험 법칙: 명료함을 잃지 않고 모듈 스코프에 놓을 수 있다면
@staticmethod로 충분하다. 클래스를 아는 것이 이득이라면@classmethod로 만든다.
흔한 함정과 피하는 법
-
cls가 필요한데@staticmethod를 씀-
cls(...)를 반환할 가능성이 있거나(대체 생성자) 클래스 속성을 읽는다면@classmethod를 쓴다.
-
-
묶기만 하려고
@classmethod를 씀-
cls를 전혀 건드리지 않는다면@staticmethod(또는 최상위 함수)가 낫다.
-
-
상속 동작을 헷갈림
-
@classmethod는 다형성에 자연스럽게 참여한다. -
@staticmethod도 일반 속성 조회를 따르므로 재정의할 수 있지만, 클래스가 자동으로 바인딩되지는 않는다.
-
-
ABC와 함께 쓸 때 데코레이터 순서
-
abc.ABC와는 이렇게 조합한다:from abc import ABC, abstractmethod class Repo(ABC): @classmethod @abstractmethod def from_url(cls, url: str): ... @staticmethod @abstractmethod def validate(url: str) -> bool: ... -
(순서가 중요하다.
@classmethod/@staticmethod를@abstractmethod위에 둔다.)
-
-
내장
@classproperty는 없다- 파이썬에는 인스턴스용
@property와 메서드용@classmethod가 있다. 계산되는 클래스 수준 프로퍼티를 원한다면 작은 커스텀 디스크립터나 메타클래스가 필요하다. 대개는def info(cls)같은 단순한@classmethod가 더 명확하다.
- 파이썬에는 인스턴스용
실제로 마주치게 되는 설계 패턴
- 이름 있는 생성자:
Path.home(),datetime.fromtimestamp(...) - 포맷 디코더:
Message.from_json(...),Model.from_pretrained(...) - 레지스트리/플러그인:
Handler.register(...),Handler.create(name) - 유틸리티 네임스페이스:
Bytes.to_hex(...),Password.hash(...)
테스트 팁
-
@classmethod대체 생성자는 기반 클래스와 서브클래스 호출을 모두 테스트해 다형성을 확인한다:-
Base.from_x(...)는Base를 반환 -
Sub.from_x(...)는Sub를 반환
-
-
@staticmethod는 순수 함수처럼 다룬다:- 인스턴스/클래스용 픽스처가 필요 없다.
- 빠르고 결정적인 단위 테스트가 된다.
결정 가이드
- 메서드에 인스턴스 가 필요한가? → 평범한 메서드 (데코레이터 없음).
- 메서드에 클래스 가 필요한가? (클래스 데이터를 읽거나, 생성하거나, 다형적이어야 하거나) →
@classmethod. - 둘 다 아니다? →
@staticmethod(발견 용이성이 문제가 아니라면 모듈 수준 함수).
미니 FAQ
-
“staticmethod가 클래스 변수에 접근할 수 있나요?” 암묵적으로는 안 된다. 클래스를 이름으로 직접 참조해야 하는데(그러면 서브클래싱이 깨진다), 대개 나쁜 신호다.
@classmethod를 선호하자. -
”
@staticmethod가 더 빠른가요?” 바인딩 오버헤드를 피하긴 하지만 그 비용은 아주 작다. 미세 성능이 아니라 API 명료성을 기준으로 고르자. -
“classmethod를 캐싱할 수 있나요?” 하위 함수를
functools.cache/lru_cache로 감쌀 수 있지만, 첫 인자가cls라는 점에 유의해야 한다. 클래스 쪽에 캐싱하는 편(예:cls._cache[...])이 더 간단할 때가 많다.
정리
- 타입을 아는 동작을 표현할 때
@classmethod를 꺼내 든다. 대체 생성자, 다형적 팩토리, 클래스 수준 설정. - 클래스 옆에 두는 게 나은 순수 유틸리티 에는
@staticmethod를 쓴다. - 어느 바인딩도 도움이 안 된다면 모듈 수준 함수가 가장 깔끔한 선택인 경우가 많다.
즐거운 파이썬 되세요! 🐍