
파이썬을 깊이 있게 공부하다 보면 "객체는 어떻게 만들어지는가?"라는 본질적인 질문에 마주하게 됩니다. 단순히 ClassName()을 호출하는 행위 이면에는 파이썬의 데이터 모델이 정의한 정교한 메커니즘이 숨어 있습니다. 특히 __new__와 __init__은 그 중심에 있는 핵심 메서드입니다. 본 가이드는 단순한 문법 설명을 넘어, 실무에서 마주치는 복잡한 인스턴스화 문제를 해결하기 위한 전문적인 지식을 제공합니다.
1. 객체 생성의 2단계: __new__ vs __init__
흔히 입문자들은 __init__이 생성자(Constructor)라고 배우지만, 엄밀히 말하면 __init__은 초기화자(Initializer)입니다. 실제 객체를 메모리에 할당하고 생성하는 생성자 역할은 __new__가 담당합니다.
| 비교 항목 | __new__ (생성자) | __init__ (초기화자) |
|---|---|---|
| 호출 시점 | 인스턴스 생성 시 가장 먼저 호출 | 인스턴스가 생성된 후 호출 |
| 메서드 성격 | 정적 메서드 (암시적 staticmethod) | 인스턴스 메서드 |
| 첫 번째 인자 | cls (클래스 자신) |
self (생성된 인스턴스) |
| 반환 값 | 생성된 인스턴스 객체 (반드시 반환 필요) | 반환값 없음 (None만 허용) |
| 주요 목적 | 불변 객체 상속 및 메타프로그래밍 | 인스턴스 속성 설정 및 상태 초기화 |
2. Python 인스턴스화의 내부 흐름 (Flow)
파이썬에서 obj = MyClass()가 실행될 때 내부적으로 발생하는 순서는 다음과 같습니다.
MyClass.__new__(MyClass, *args, **kwargs)가 호출되어 새로운 객체 인스턴스를 생성합니다.__new__가MyClass의 인스턴스를 반환하면, 비로소 파이썬 인터프리터가__init__을 호출합니다.- 이때
__new__가 반환한 객체가__init__의self인자로 전달됩니다.
3. 실무 중심의 7가지 활용 Example
현업 시니어 개발자들이 __new__를 활용해 복잡한 설계 문제를 해결하는 방식들을 코드로 살펴보겠습니다.
Example 1: 싱글톤(Singleton) 패턴 구현
애플리케이션 전체에서 단 하나의 인스턴스만 유지해야 하는 경우, __new__가 최적의 해결책입니다.
class DatabaseConnection:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
print("Creating new instance...")
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, db_url):
self.db_url = db_url
# 실행 테스트
db1 = DatabaseConnection("url_1")
db2 = DatabaseConnection("url_2")
print(db1 is db2) # True
Example 2: 불변 클래스(Immutable Class)의 상속 및 변형
tuple이나 int 같은 불변 객체를 상속받아 수정하고 싶을 때는 __init__이 아닌 __new__에서 값을 결정해야 합니다.
class PositiveInteger(int):
def __new__(cls, value):
# 음수 값이 들어오면 절댓값으로 변환하여 생성
return super().__new__(cls, abs(value))
# 실행 테스트
p_int = PositiveInteger(-10)
print(p_int) # 10
Example 3: 팩토리(Factory) 패턴을 통한 동적 인스턴스 반환
입력 값에 따라 서로 다른 서브클래스의 인스턴스를 반환하도록 제어할 수 있습니다.
class Shape:
def __new__(cls, sides):
if sides == 3:
return super().__new__(Triangle)
elif sides == 4:
return super().__new__(Square)
return super().__new__(cls)
class Triangle(Shape): pass
class Square(Shape): pass
# 실행 테스트
s1 = Shape(3)
print(type(s1)) # <class '__main__.Triangle'>
Example 4: 객체 생성 캐싱(Caching)을 통한 리소스 최적화
동일한 인자로 생성 요청이 올 경우 기존 객체를 재사용하여 메모리를 아낍니다.
class UserProfile:
_cache = {}
def __new__(cls, user_id):
if user_id not in cls._cache:
cls._cache[user_id] = super().__new__(cls)
return cls._cache[user_id]
def __init__(self, user_id):
self.user_id = user_id
Example 5: 입력 값에 따른 유효성 검사 및 생성 거부
특정 조건이 만족되지 않을 때 아예 객체 생성을 막는 로직을 구현합니다.
class ValidatedUser:
def __new__(cls, username):
if not username:
print("Invalid Username. Access Denied.")
return None
return super().__new__(cls)
# 실행 테스트
user = ValidatedUser("")
print(user) # None
Example 6: API 응답 모델의 자동 매핑
수신된 데이터 포맷에 따라 동적으로 속성을 제어하는 고도화된 방식입니다.
class ApiResponse:
def __new__(cls, data):
instance = super().__new__(cls)
if isinstance(data, dict):
for key, value in data.items():
setattr(instance, key, value)
return instance
# 실행 테스트
res = ApiResponse({"status": "success", "code": 200})
print(res.status) # success
Example 7: 메타클래스를 활용한 클래스 생성 로직 제어
클래스가 만들어지는 과정 자체를 추적하여 디버깅 정보를 남깁니다.
class DebugMeta(type):
def __call__(cls, *args, **kwargs):
print(f"[LOG] Instantiating {cls.__name__}...")
return super().__call__(*args, **kwargs)
class MyService(metaclass=DebugMeta):
pass
# 실행 테스트
service = MyService() # 로그 출력
4. 주의사항: __new__를 남용하지 마세요
대부분의 비즈니스 로직은 __init__만으로 충분합니다. __new__는 코드의 복잡도를 높이고 가독성을 떨어뜨릴 수 있으므로, 반드시 필요한 경우(싱글톤, 불변 상속, 팩토리 등)에만 제한적으로 사용하는 것이 파이써닉(Pythonic)한 개발 방법입니다.
5. 내용의 출처 및 참고 자료
- Python Software Foundation. "Data Model - Customizing instance creation." Official Docs.
- Fluent Python: Clear, Concise, and Effective Programming by Luciano Ramalho.
- Effective Python: 90 Specific Ways to Write Better Python by Brett Slatkin.
- Real Python - "Python's __new__ vs __init__: What's the Difference?"
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] 파이썬 메모리 누수 해결을 위한 7가지 핵심 디버깅 도구와 최적화 방법 (0) | 2026.03.30 |
|---|---|
| [PYTHON] 성능 최적화의 핵심, cProfile로 코드 병목 현상을 해결하는 7가지 방법 (0) | 2026.03.30 |
| [PYTHON] 성능 한계 해결을 위한 Cython과 PyPy 도입 시 2가지 핵심 차이와 최적화 방법 (0) | 2026.03.30 |
| [PYTHON] 거대 루프 내 enumerate()와 zip()의 3가지 오버헤드 분석 및 해결 방법 (0) | 2026.03.30 |
| [PYTHON] CPU 바운드 연산 해결을 위한 threading과 multiprocessing의 2가지 근본적 차이와 선택 방법 (0) | 2026.03.30 |