Python 클래스의 기본 구조부터 생성자, 메서드, 상속, 소멸자까지 핵심 개념을 정리했습니다.
실무에 바로 적용 가능한 예제와 함께 __init__, self, __del__, __enter__, __exit__까지 전체 흐름을 이해할 수 있습니다.
Python 클래스 정리: 클래스, 상속, 메서드, 접근제어
목차2. 생성자(__init__), 소멸자(__del__), self 이해하기
7. 특수 메서드(__str__, __repr__, __eq__, __lt__, __len__, __contains__)
1. Python 클래스 기본 구조
Python 클래스는 관련 있는 변수와 메서드를 하나로 묶는 사용자 정의 자료형입니다.
class MyClass:
def __init__(self, name): # 생성자(Constructor)
self.name = name # 인스턴스 변수
def greet(self): # 인스턴스 메서드
print(f"Hello, {self.name}!")
user = MyClass("Alice") # 인스턴스 생성
user.greet() # Hello, Alice!
요소 | 설명 |
class | 클래스를 정의하는 키워드 |
__init__() | 인스턴스가 생성될 때 자동 호출되는 생성자 메서드 |
self | 인스턴스 자신을 가리키는 참조자 |
self.name | 인스턴스 변수 (객체마다 고유한 데이터 저장) |
greet() | 클래스에 소속된 메서드 (함수) |
◆ 흐름
- MyClass("Alice") → 클래스 생성자 __init__() 자동 호출
- name 인자를 받아 self.name에 저장
- user.greet() 호출 시 self.name을 참조하여 메시지 출력
◆ 클래스 vs 인스턴스
클래스(Class) | 객체를 생성하기 위한 설계도 또는 청사진 |
인스턴스(Instance) | 클래스 기반으로 생성된 실제 객체 |
class Dog:
pass
d1 = Dog() # d1은 Dog 클래스의 인스턴스
d2 = Dog() # d2도 Dog 클래스의 인스턴스 (서로 다른 객체)
2. 생성자(__init__), 소멸자(__del__), self 이해하기
Python 클래스의 핵심 구성 요소 중 가장 중요한 세 가지는 생성자 __init__, 소멸자 __del__, 그리고 self 키워드입니다.
◆ 생성자: __init__()
__init__()은 객체가 생성될 때 자동으로 호출되는 초기화 메서드입니다.
이 메서드를 통해 인스턴스 변수의 초기값을 설정할 수 있습니다.
class Person:
def __init__(self, name, age):
self.name = name # 인스턴스 변수 설정
self.age = age
def introduce(self):
print(f"{self.name}, {self.age}세입니다.")
#
p = Person("Tom", 30)
p.introduce() # Tom, 30세입니다.
이 예제에서 p는 Person 클래스의 인스턴스로, 생성 시 __init__()이 호출되어 name, age가 초기화됩니다.
◆ self
self는 인스턴스 자신을 가리키는 참조자입니다.
클래스 내부의 메서드에서 인스턴스 변수나 다른 메서드에 접근할 때 반드시 사용해야 합니다.
class Sample:
def set_data(self, value):
self.data = value # self를 통해 인스턴스에 속성 추가
def get_data(self):
return self.data
#
s = Sample()
s.set_data(10)
print(s.get_data()) # 10
self는 함수 정의 시 첫 번째 인자로 사용되지만, 인스턴스에서 호출할 때는 생략됩니다.
(s.get_data() → 내부적으로 Sample.get_data(s) 호출)
◆ 소멸자: __del__()
__del__()은 객체가 소멸될 때 자동 호출되는 메서드입니다.
주로 파일 닫기, 리소스 해제와 같은 정리 작업에 사용됩니다.
class TempFile:
def __init__(self, filename):
self.file = open(filename, 'w')
print(f"{filename} 파일 열림")
def __del__(self):
print("파일 닫기")
self.file.close()
f = TempFile("test.txt")
del f # __del__() 호출 → 파일 닫기
__del__() 사용시 주의사항
요소 | 설명 |
호출 시점 예측 불가 | Python의 **가비지 컬렉터(GC)**가 객체의 참조가 모두 사라졌을 때 호출하므로 타이밍 보장 X |
순환 참조 문제 | 객체가 서로를 참조할 경우, GC가 __del__()을 호출하지 못하는 경우가 발생 |
예외 안전성 부족 | 예외 상황에서 소멸자가 실행되지 않아 자원 누수 발생 가능성 있음 |
실제 파일, 네트워크, DB와 같이 정확한 타이밍에 리소스를 정리해야 하는 경우, __del__() 대신 컨텍스트 매니저를 사용하는 것이 좋습니다.
3. 인스턴스 변수 vs 클래스 변수
Python 클래스 내부의 변수는 선언 위치와 사용 방법에 따라 3가지 종류로 나뉩니다:
구분 | 선언위치 | 접근방법 | 특징 |
클래스 변수 | 클래스 정의 블록 내부 | 클래스이름.변수, self.변수 | 모든 인스턴스가 공유하는 변수 |
인스턴스 변수 | __init__() 또는 메서드 내부 | self.변수 | 각 인스턴스마다 독립적인 값 가짐 |
지역 변수 | 메서드 내부 | 메서드 내부에서만 사용 | 함수/메서드가 실행될 때만 생성, 종료 시 사라짐 |
class Dog:
species = "Canine" # 클래스 변수
def __init__(self, name):
self.name = name # 인스턴스 변수
def bark(self):
sound = "Woof!" # 지역 변수
return f"{self.name} says {sound}"
dog1 = Dog("Buddy")
dog2 = Dog("Charlie")
print(dog1.species) # Canine (클래스 변수)
print(dog1.name) # Buddy (인스턴스 변수)
print(dog1.bark()) # Buddy says Woof! (지역 변수 사용)
- dog1.species는 클래스 전체에서 공유되는 값 "Canine"을 참조합니다.
- dog1.name은 Buddy, dog2.name은 Charlie처럼 각각의 인스턴스에 따라 다르게 설정됩니다.
- bark() 내부의 sound는 지역 변수로, 함수가 실행될 때만 존재합니다.
◆ 주의 사항: mutable 한 클래스 변수의 공유
클래스 변수는 모든 인스턴스에서 공통으로 공유되기 때문에,
list, dict 같은 변경 가능한 객체를 클래스 변수로 선언하면 의도치 않은 결과가 발생할 수 있습니다.
잘못된 예시:
class MyClass:
items = [] # 클래스 변수 (mutable)
def add_item(self, item):
self.items.append(item)
a = MyClass()
b = MyClass()
a.add_item("apple")
b.add_item("banana")
print(a.items) # ['apple', 'banana']
print(b.items) # ['apple', 'banana'] (같은 객체!)
- 모든 인스턴스가 동일한 리스트 객체를 공유하고 있기 때문에, 한 인스턴스의 변경이 다른 인스턴스에도 영향을 줍니다.
해결 방법: 인스턴스 변수로 선언:
class MyClass:
def __init__(self):
self.items = [] # 인스턴스마다 별도의 리스트 생성
def add_item(self, item):
self.items.append(item)
- 이제 self.items는 인스턴스마다 독립적으로 존재하므로 문제가 없습니다.
4. 메서드 종류(인스턴스, 클래스, 정적)
Python 클래스 안에서 정의되는 메서드는 용도와 호출 방식에 따라 다음 3가지로 나눌 수 있습니다:
- 인스턴스 메서드
- 클래스 메서드
- 정적 메서드
이들은 메서드의 첫 번째 인자와 접근 범위, 역할이 서로 다릅니다.
class Example:
def instance_method(self): # ① 인스턴스 메서드
print(f"[인스턴스] self: {self}")
@classmethod
def class_method(cls): # ② 클래스 메서드
print(f"[클래스] cls: {cls}")
@staticmethod
def static_method(): # ③ 정적 메서드
print("[정적] 클래스와 인스턴스와 무관")
ex = Example()
ex.instance_method() # [인스턴스] self: <__main__.Example object at ...>
ex.class_method() # [클래스] cls: <class '__main__.Example'>
ex.static_method() # [정적] 클래스와 인스턴스와 무관
Example.class_method() # 클래스에서도 호출 가능
Example.static_method() # 인스턴스 없이도 호출 가능
◆ 인스턴스 메서드
- 가장 일반적인 메서드 형태
- self를 첫 번째 인자로 받으며, 인스턴스의 상태(속성 등)에 접근할 수 있음
- 객체의 행동/기능을 구현하는 데 사용됨
class User:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f"안녕하세요, 저는 {self.name}입니다.")
#
u = User("Tom")
u.say_hello() # 안녕하세요, 저는 Tom입니다.
◆ 클래스 메서드(@classmethod)
- 클래스 전체에 영향을 미치는 작업에 사용
- 첫 번째 인자가 cls이며, 클래스 객체 자체를 가리킴
- 인스턴스 없이도 호출 가능
- 주로 클래스 수준에서 데이터 초기화, 팩토리 메서드 구현에 쓰임
팩토리 메서드 구현 예시
- 팩토리 메서드는 클래스의 생성 방식을 다양화할 수 있어서 매우 유용합니다.
class User:
def __init__(self, name):
self.name = name
@classmethod
def from_email(cls, email):
name = email.split("@")[0]
return cls(name) #cls("alice") → User("alice") 객체 생성
#
u = User.from_email("alice@example.com")
클래스 수준의 설정/초기화 관리
- @classmethod는 클래스 자체에 대한 정보나 상태를 활용할 때 유용합니다.
class Config:
_env = "dev"
@classmethod
def set_env(cls, env):
cls._env = env
#
Config.set_env("prod")
print(Config._env) # prod
◆ 정적 메서드(@staticmethod)
- self, cls를 받지 않음 → 인스턴스나 클래스 상태에 전혀 접근하지 않음
- 일반적인 함수와 동일하게 작동하지만 클래스 이름 공간 내에 존재
- 보통 클래스와 논리적으로 관련은 있지만 독립적인 유틸리티 함수를 만들 때 사용
class Math:
@staticmethod
def add(a, b):
return a + b
#
print(Math.add(2, 3)) # 5
외부 함수처럼 보이지만, 의미상 클래스에 소속된 기능일 때 정적 메서드로 정의합니다.
5. 상속과 메소드 오버라이딩
◆ 상속(Inheritance)
상속은 기존 클래스의 속성과 메서드를 새로운 클래스에 물려주는 객체지향 프로그래밍(OOP)의 핵심 개념입니다.
코드 재사용성을 높이고, 기능 확장에 매우 유용합니다.
class Animal:
def sound(self):
print("동물 소리")
class Dog(Animal): # Animal을 상속받음
def sound(self):
print("멍멍!")
class Cat(Animal):
def sound(self):
print("야옹!")
dog = Dog()
cat = Cat()
dog.sound() # 멍멍!
cat.sound() # 야옹!
- Dog, Cat 클래스는 Animal 클래스의 속성과 메서드를 그대로 사용할 수 있음
- sound() 메서드를 자식 클래스에서 재정의(오버라이딩) 할 수 있음
◆ 메서드 오버라이딩(Method Overriding)
오버라이딩이란 부모 클래스에 정의된 메서드를 자식 클래스에서 같은 이름으로 다시 정의하는 것입니다.
자식 클래스의 인스턴스에서 해당 메서드를 호출하면, 부모가 아닌 자식의 메서드가 실행됩니다.
class Animal:
def sound(self):
print("동물이 소리낸다")
class Dog(Animal):
def sound(self): # 오버라이딩
print("멍멍!")
◆ super()를 사용한 부모 메서드 호출
오버라이딩하면서도 부모 클래스의 기능을 일부 재사용하고 싶다면 super()를 사용합니다.
class Animal:
def sound(self):
print("기본 동물 소리")
class Dog(Animal):
def sound(self):
super().sound() # 부모 메서드 호출
print("멍멍!")
dog = Dog()
dog.sound()
#
기본 동물 소리
멍멍!
- super()는 현재 클래스의 부모를 가리키며,
- super().메서드()를 호출하면 부모의 기능을 그대로 사용할 수 있습니다.
6. 캡슐화와 접근 제어(_와 __ 차이)
◆ 캡슐화란?
캡슐화(encapsulation)는 객체의 내부 상태(데이터)를 외부에서 직접 접근하지 못하도록 보호하고, 공개된 인터페이스(메서드)를 통해서만 조작할 수 있도록 하는 객체지향 설계 원칙입니다.
Python은 Java, C++처럼 강력한 접근 제한자(private, protected 등)를 지원하지 않습니다.
대신 개발자 관례와 특수 문법(Name Mangling)을 통해 이를 우회적으로 구현합니다.
표기법 | 의미 및 용도 |
name | public (공개 속성) |
_name | protected (내부 사용 권장, 강제는 아님) |
__name | private (Name Mangling 적용, 외부 접근 차단) |
@property | 속성을 안전하게 읽고 쓰기 위한 우아한 방법 |
class Account:
def __init__(self):
self.name = "홍길동" # 공개 속성
self._balance = 1000 # 보호 속성 (접근 권장 X)
self.__password = "1234" # 비공개 속성 (Name Mangling 적용)
self.name | public | 누구나 접근 가능. 공식 인터페이스로 제공되는 속성 |
self._balance | protected | 관례상 내부용. 외부에서 접근 가능하지만 사용 자제 권장 |
self.__password | private | 이름 변경(Name Mangling)을 통해 외부 접근 차단 |
◆ Name Mangling이란?
Python에서 __이름 형태로 정의된 속성은 클래스 내부에서 자동으로 이름이 바뀝니다.
class Test:
def __init__(self):
self.__secret = "비밀"
t = Test()
# print(t.__secret) # AttributeError
print(t._Test__secret) # 비밀
__secret은 실제로는 _Test__secret이라는 이름으로 저장됩니다.
이를 통해 우연한 접근이나 오용을 막는 것이지, 절대적 차단은 아닙니다.
◆ 캡슐화 + 접근 메서드(getter/setter) 조합
접근을 제한하면서, 공식 인터페이스로 값을 읽거나 변경하는 방법:
class User:
def __init__(self):
self.__age = 0
def get_age(self):
return self.__age
def set_age(self, value):
if value >= 0:
self.__age = value
else:
print("나이는 음수일 수 없습니다.")
#
u = User()
u.set_age(25)
print(u.get_age()) # 25
◆ @property를 활용한 캡슐화(Python 3.6+)
class Product:
def __init__(self, price):
self._price = price
@property
def price(self): # getter
return self._price
@price.setter
def price(self, value): # setter
if value >= 0:
self._price = value
else:
raise ValueError("가격은 0 이상이어야 합니다.")
#
p = Product(10000)
p.price = 12000
print(p.price) # 12000
읽기/쓰기 로직을 메서드로 제어하면서도, 사용자 입장에서는 속성처럼 .으로 접근 가능
7. 특수 메서드(__str__, __repr__, __eq__, __lt__, __len__, __contains__)
◆ __str__() : 사용자 친화적인 출력
- 목적: 일반 사용자에게 친숙한 형식으로 객체 표현
class Product:
def __init__(self, name):
self.name = name
def __str__(self):
return f"제품명: {self.name}"
#
p = Product("노트북")
print(p) # 제품명: 노트북
print(str(p)) # 제품명: 노트북
- repr() 함수 또는 인터프리터에서 객체를 직접 출력할 때 호출됩니다.
- 목적: 개발자용 디버깅에 적합한 형식, 가능하다면 해당 문자열을 eval()로 객체 복원 가능하게 작성하는 것이 이상적입니다.
class Product:
def __init__(self, name):
self.name = name
def __repr__(self):
return f"Product('{self.name}')"
#
p = Product("노트북")
print(repr(p)) # Product('노트북')
p # 인터프리터에서도 같은 결과
◆ __eq__() : 동등성 비교 (==)
class User:
def __init__(self, username):
self.username = username
def __eq__(self, other):
return self.username == other.username
#
u1 = User("alice")
u2 = User("alice")
print(u1 == u2) # True
- __eq__()을 구현하지 않으면, 기본은 객체 주소(id) 비교로 처리됩니다.
- 필요하다면 __hash__()도 함께 정의해야 set, dict에서 동작이 일관됩니다.
◆ __lt__ : 순서 비교 (<)
class Box:
def __init__(self, volume):
self.volume = volume
def __lt__(self, other):
return self.volume < other.volume
#
b1 = Box(10)
b2 = Box(20)
print(b1 < b2) # True
#함께 사용되는 관련 메서드:
# __le__: <=
# __gt__: >
# __ge__: >=
- 객체 간 대소 비교를 할 때 호출됩니다.
- < 연산자에 대응합니다.
◆ __len__ : 길이 반환
- len(obj) 호출 시 실행됩니다.
- 리스트나 문자열처럼 객체의 크기나 개수를 제공하고자 할 때 사용합니다.
class Cart:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
#
c = Cart(["apple", "banana"])
print(len(c)) # 2
◆ __contains__ : 포함 여부 검사
- in 연산자 또는 item in obj 표현에서 호출됩니다.
- 내부 컨테이너(리스트, 집합 등)에 포함 여부 판단 로직을 직접 정의할 수 있습니다.
class Bag:
def __init__(self, items):
self.items = items
def __contains__(self, item):
return item in self.items
#
b = Bag(["pen", "notebook"])
print("pen" in b) # True
print("eraser" in b) # False
__contains__()이 없으면 __iter__()를 통해 순회하며 검사합니다.
8. 컨텍스트 매니저 __enter__, __exit__ 사용하기
Python의 with 문은 자원(resource)의 획득과 해제 과정을 자동화해주는 문법입니다.
이를 가능하게 해주는 것이 바로 컨텍스트 매니저(Context Manager)이며, 클래스 내부에 __enter__()와 __exit__() 메서드를 정의함으로써 구현할 수 있습니다.
class FileHandler:
def __init__(self, name):
self.name = name
def __enter__(self):
self.file = open(self.name, 'w') # 자원 획득
print("파일 열림")
return self.file # with문의 as 변수로 전달됨
def __exit__(self, exc_type, exc_val, exc_tb):
print("파일 닫힘")
self.file.close() # 자원 해제
with FileHandler("log.txt") as f:
raise RuntimeError("문제 발생!") # 예외 발생!
f.write("이 코드는 실행되지 않음")
이 코드는 다음과 같이 내부적으로 처리됩니다:
# 내부적으로는 아래와 같은 동작
f = FileHandler("log.txt").__enter__()
try:
f.write("로그 기록")
finally:
FileHandler("log.txt").__exit__(None, None, None)
with 문 내부에서 에러가 발생해도 __exit__()는 무조건 호출되기 때문에, 파일, DB, 소켓 등의 자원을 안정적으로 정리할 수 있습니다.
◆ __exit__() 메서드
인자 | 설명 |
exc_type | 발생한 예외 클래스 (예: ZeroDivisionError) |
exc_val | 예외 객체 자체 (예: ZeroDivisionError('...')) |
exc_tb | 예외 traceback 객체 |
필요하다면 __exit__() 안에서 예외를 로깅하거나 무시할 수도 있습니다.
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
print("예외 발생:", exc_val)
self.file.close()
return True # 예외를 무시함
◆ 활용 예시
1. 파일 열기/닫기 자동화
with open("sample.txt", "r") as file:
data = file.read()
2. DB 연결과 트랜잭션 관리
import sqlite3
class DB:
def __enter__(self):
self.conn = sqlite3.connect(":memory:")
return self.conn
def __exit__(self, exc_type, exc_val, exc_tb):
self.conn.close()
with DB() as conn:
conn.execute("CREATE TABLE test (id INT)")
3. Lock 처리 (멀티스레딩)
from threading import Lock
lock = Lock()
with lock:
# 임계영역
pass
◆ 컨텍스트 매니저의 구현 방법 2가지
클래스 기반 | __enter__, __exit__ 직접 구현 |
데코레이터 기반 | @contextmanager 사용 (from contextlib) |
예: 데코레이터 방식
from contextlib import contextmanager
@contextmanager
def file_handler(name):
f = open(name, 'w')
try:
yield f
finally:
f.close()
with file_handler("test.txt") as f:
f.write("Hello")
관련 글 링크
2. Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수
2. Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수
Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수 목차 1. 지역변수 vs 전역변수 2. global 키워드의 역할과 주의사항 3. nonlocal 키워드 4. 변수처럼 다루는 함수-일급객체로서의 함수 5. 클래스
quadcube.tistory.com
'3.개발언어&라이브러리 > Python' 카테고리의 다른 글
6. Python 병렬 처리 : GIL, threading, asyncio, multiprocessing (0) | 2025.05.22 |
---|---|
5. Python 예외(Exception) 처리 : try-except-finally, with (0) | 2025.05.22 |
4. Python 함수구조, 함수인자, 람다함수, 클로저, 고차함수, 데코레이터 (0) | 2025.05.22 |
2. Python 변수 정리: 지역변수, 전역변수, global, 클래스 변수 (0) | 2025.05.21 |
1. Python 모듈, 패키지, import 개념 정리: __init__.py (0) | 2025.05.20 |