파이썬에서 클래스 객체 생성은 두 단계로 이루어집니다:
__new__가 호출되어 새로운 클래스 인스턴스를 할당하고 반환합니다.__init__이 호출되어 이미 생성된 객체를 초기화합니다.__new__ — 생성 (임의의 새로운 객체를 반환할 수 있음; 필수 매개변수는 클래스).__init__ — 초기화 (self와 이미 할당된 인스턴스와 함께 작동).__new__는 불변형 타입 (tuple, str, int)의 자식 클래스, 싱글톤 생성, "팩토리" 패턴을 위해 재정의됩니다.class MyStr(str): def __new__(cls, value): print("__new__가 호출됨") instance = super().__new__(cls, value.upper()) # 생성 전에 값을 변경 return instance def __init__(self, value): print("__init__가 호출됨", value) s = MyStr('abc') # __new__가 호출됨 -> __init__가 호출됨 abc print(s) # 'ABC'
클래스에 __new__가 구현되지 않은 경우 불변 객체를 __init__을 통해 초기화할 수 있나요?
아니요! 예를 들어, 불변형 타입 (str, int, tuple 등)에서는 필드/값에 대한 모든 변경이 __new__에 있어야 하며, 그렇지 않으면 __init__에서 값을 변경할 수 없습니다.
class MyTuple(tuple): def __init__(self, items): print(f'__init__! {items}') def __new__(cls, items): print(f'__new__! {items}') return super().__new__(cls, map(str, items)) t = MyTuple([1, 2, 3]) print(t) # ('1', '2', '3')
이야기
Django 대형 프로젝트에서 특별한 문자열을 저장하기 위해 str에서 상속을 구현했으나, __init__에서 값을 수정하려고 했으나 str는 불변이기 때문에 결과가 변하지 않았습니다. __new__를 изуч한 후 수정했습니다.
이야기
싱글톤 패턴을 구현할 때 __new__에서 기존 인스턴스를 반환하는 로직을 잊어버려서 클래스 호출이 여러 번 발생해도 새로운 객체가 생성되어 메모리 조각화가 발생했습니다.
이야기
데이터 직렬화 라이브러리에서 캐싱을 위해 __init__을 재정의한 클래스를 사용했지만, 캐싱을 위해서는 반드시 __new__를 통해 동일한 객체를 반환해야 한다는 것을 놓쳐서 매 호출마다 다른 객체가 생성되어 캐시가 깨졌습니다.