본문으로 건너뛰기
김신건의 로그

[Python] class 기초: 정의, __init__, self

· 수정 · 📖 약 1분 · 518자/단어 #python #class #oop #init #self #basics
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 = ...인스턴스별
python
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 객체는 기본적으로 속성 동적 추가가 가능하다.

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 개념의 출발점.

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기