[Python] class 기초: 정의, __init__, self
클래스 정의
class Point:
"""2D 점."""
def __init__(self, x, y): # 생성자 (정확히는 초기화)
self.x = x
self.y = y
def distance(self, other):
dx = self.x - other.x
dy = self.y - other.y
return (dx ** 2 + dy ** 2) ** 0.5
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
q = Point(0, 0)
print(p.distance(q)) # 5.0
print(p) # Point(3, 4)
self
Python 메서드의 첫 번째 인수는 인스턴스 자신이다. 관례적으로 self라 부른다(언어가 강제하진 않지만 PEP 8 권장).
class C:
def method(self_or_anything): # 동작하지만 비권장
pass
호출 시 p.method()는 내부적으로 C.method(p)로 바인딩된다. 메서드 = 첫 인수가 인스턴스인 함수.
init: 초기화
__init__은 인스턴스가 만들어진 후 호출되어 속성을 초기화한다. 실제 생성은 __new__. 99%의 경우 __init__만 정의.
class User:
def __init__(self, name, age=0):
self.name = name
self.age = age
self._login_count = 0 # 관례: _ 접두 = 내부용
u = User("Alice", 30)
__init__은 항상 None을 반환해야 한다. return 값이 있으면 TypeError.
클래스 변수 vs 인스턴스 변수
| 정의 위치 | 공유 | |
|---|---|---|
| 클래스 변수 | 클래스 본문 (메서드 밖) | 모든 인스턴스 공유 |
| 인스턴스 변수 | __init__ 등에서 self.xxx = ... | 인스턴스별 |
class Counter:
total = 0 # 클래스 변수 (모든 인스턴스 공유)
def __init__(self, name):
self.name = name # 인스턴스 변수
Counter.total += 1
a = Counter("a")
b = Counter("b")
c = Counter("c")
print(Counter.total) # 3
print(a.total) # 3 (클래스 변수 조회)3
3함정: 가변 클래스 변수는 모든 인스턴스가 공유한다.
class Bad:
items = [] # 공유됨!
def add(self, x):
self.items.append(x)
a, b = Bad(), Bad()
a.add(1)
b.add(2)
print(a.items) # [1, 2] (!!)
class Good:
def __init__(self):
self.items = [] # 인스턴스별
메서드 종류
인스턴스 메서드
기본. self를 첫 인수로.
class C:
def instance_method(self, x):
return self.value + x
클래스 메서드 @classmethod
cls를 첫 인수로. 클래스 자체를 다룬다. 팩토리 메서드로 자주 사용.
class Date:
def __init__(self, y, m, d):
self.y, self.m, self.d = y, m, d
@classmethod
def from_string(cls, s):
y, m, d = map(int, s.split("-"))
return cls(y, m, d) # 서브클래스 대응
d = Date.from_string("2026-06-20")
정적 메서드 @staticmethod
인스턴스도 클래스도 안 받는다. 사실상 네임스페이스 정리 목적.
class MathUtil:
@staticmethod
def clamp(x, lo, hi):
return max(lo, min(hi, x))
MathUtil.clamp(5, 0, 10)
대부분의 경우 모듈 수준 함수가 더 Pythonic. staticmethod는 클래스 안에 두는 게 의미적으로 명확할 때만.
속성 접근과 동적 속성
Python 객체는 기본적으로 속성 동적 추가가 가능하다.
class Empty:
pass
e = Empty()
e.x = 1
e.name = "alice"
print(e.__dict__){'x': 1, 'name': 'alice'}이를 막으려면 __slots__ (메모리 절감 효과도).
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x, self.y = x, y
p = Point(1, 2)
p.z = 3 # AttributeError
repr vs str
class User:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"User({self.name!r})" # 개발자용 (재현 가능)
def __str__(self):
return f"User: {self.name}" # 사용자용 (보기 좋음)
u = User("Alice")
print(repr(u)) # User('Alice')
print(str(u)) # User: Alice
print(u) # User: Alice (str 우선)
__str__이 없으면 __repr__을 폴백한다. 항상 __repr__을 정의하는 게 좋다 (디버깅, 로그, 리스트 출력 시 사용됨).
비교와 해싱
기본 ==는 객체 동일성. 값 비교를 원하면 __eq__ 구현.
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
__eq__ 정의하면 __hash__가 자동으로 None이 되어 hashable이 아니게 된다. set/dict 키로 쓰려면 같이 정의.
대부분 @dataclass로 자동 생성하는 게 편함.
클래스도 객체
class C: pass
print(type(C)) # <class 'type'>
print(isinstance(C, type)) # True
# 동적 생성
D = type("D", (), {"x": 1})
print(D().x) # 1
클래스는 type의 인스턴스. metaclass 개념의 출발점.
💬 댓글