이 글은 파이썬 중급자를 위한 글입니다.
오늘의 의문
왜 파이썬 클래스의 __init__ 메서드는 반환이 없을까?
class MyClass:
def __init__(self, name):
print("Does this function really create an instance?")
self.name = name
my_instance = MyClass("init_mumu")
print(my_instance.name)
아무리 생각해도 이상하다. 위의 코드는 분명 my_instance 변수에 인스턴스를 대입한다. 그런데, __init__ 메서드는 반환값이 없으니 말이다.
코드 실행 흐름 분석
그래서 대입 시점을 찾기 위해 빨간콩을 코드에 죄다 찍고 Line by Line으로 코드를 실행해보았다.
MyClass는 알다시피 클래스의 이름이다. 그런데, 대입이 일어나기 전에 먼저 호출되었고 MyClass가 호출 스택에서 사라지자 코드 흐름이 변수 선언부로 갔다가 다음 단계에서 __init__이 호출 스택에 등장하였다.
지금까지 알게 된 정보는 인스턴스를 생성하면 클래스를 호출하고 그 다음 __init__이 호출된다는 사실이다.
__new__ Method
나는 더 자세히 알고 싶었다. "그렇다면 클래스를 호출하면 인스턴스를 반환하는 것인가?"
이 질문의 답은 내가 찾을 수 없었고, 해답은 공식문서에서 발견하였다.
파이썬 문서에 의하면 클래스는 호출이 가능하고, 자신의 새 인스턴스를 만드는 팩토리역할을 수행한다고 한다.
정리하자면, 클래스를 호출할 때 입력한 인자들은 __new__ 메서드에 전달되고, 이는 오버라이드를 통해 변형이 가능하다, 또한 일반적인 경우 __init__이 호출되어 생성된 인스턴스를 초기화한다고 한다.
나는 __new__ 메서드에 대해서 더 알아보기로 하였다.
한글 번역이 되다 말았다 ㅋㅋ
정리하자면 __new__ 메서드는 클래스의 새 인스턴스를 생성하는 메서드이며, Static Method(인스턴스를 생성하지 않아도 호출이 가능한 메서드)이다. 첫 번째 인자(cls)는 생성하려는 인스턴스의 클래스가 전달되고, 나머지 인자는 생성자를 호출할 때 전달한 인자들이다. 그리고...
"__new__()의 반환 값은 새 객체 인스턴스이어야 합니다."
여기서 내 의문이 해결되었다. 기존에 생성자라고 배웠던 __init__ 메서드가 인스턴스를 생성하는 것이 아니라, __new__ 메서드가 인스턴스의 생성을 담당하고 있었다.
super 클래스(사용자가 정의하는 모든 클래스의 부모는 object 클래스이다.)의 __new__ 메서드를 호출하고 새로운 인스턴스를 수정하여 반환하고 클래스의 인스턴스가 반환되면 __init__ 메서드가 호출된다고 한다.
__new__ 메서드가 클래스의 인스턴스를 반환하지 않으면 인스턴스가 __init__을 호출하지 않는다.
__new__ Customize
이제 모든 사건의 전말을 알았으니 __new__ 메서드를 커스터마이징 해보자.
class MyClass:
def __new__(cls, name):
print("__new__ Method create an instance!!")
new_instance = super().__new__(cls)
return new_instance
def __init__(self, name):
print("__init__ Method doesn't create an instance...")
self.name = name
my_instance = MyClass("init_mumu")
print(my_instance.name)
처음에 제시한 코드를 약간 수정해보았다. 공식 문서의 가르침대로 하니 정상적으로 잘 작동하였다.
그렇다면 생뚱 맞은 객체를 반환하면?
class MyClass:
def __new__(cls, name):
print("__new__ Method create an instance!!")
new_instance = [1,2,3,4,5]
return new_instance
def __init__(self, name):
print("__init__ Method doesn't create an instance...")
self.name = name
my_instance = MyClass("init_mumu")
print(type(my_instance), my_instance)
이번엔 리스트 객체를 반환해보았는데, MyClass의 인스턴스가 아닌 리스트 객체가 반환되었다.
"또한, __init__ 메서드는 호출되지 않았다."
활용은 어디다 할까?
__new__ 메서드를 통해 인스턴스 생성을 커스터마이징 할 수 있으니, 다양한 것을 할 수 있다.
예를 들어, 인스턴스의 생성을 제한(싱글턴 패턴)한다거나 인스턴스의 속성을 미리 제어한다거나..
싱글턴 패턴
싱글턴 패턴은 클래스가 인스턴스를 단 하나를 생성하며, 해당 인스턴스를 전역에서 사용하는 패턴이다.
데이터베이스 연결이나 캐싱 같은 객체 하나만 사용해도 되는 경우에 사용되는 패턴이다.
class MySingleToneClass:
_instance = None
def __new__(cls):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
my_instance_1 = MySingleToneClass()
my_instance_2 = MySingleToneClass()
print(my_instance_1 is my_instance_2)
위의 코드는 싱글턴 패턴의 예시이다. 클래스의 Static 변수 _instance에 인스턴스가 할당되어 있지 않을 때만 인스턴스를 생성한다.
my_instance_1과 my_instance_2가 인스턴스를 생성했음에도 밑의 출력 결과는 True이다.
즐거우셨나요?