python class를 생성할 때 __new__, __init__, __call__ 를 활용하는 방법에 대해서 먼저 알아본 후, 메타클래스에 생성자를 활용하여 클래스를 커스터마이징 하는 방법에 대하여 알아보았다.
파이썬의 생성자 2가지
python에서 객체를 생성할 때는 __new__와 __init__ 가 사용된다.
- __new__ 는 생성자이다. 클래스의 인스턴스를 만들기만 한다.
- __init__ 는 클래스의 속성을 초기화한다.(initialize)
new 를 실행할 때 주의점
- 클래스 자기 자신을 인자로 받는다. 주로
cls
이라고 씀.def __new__(cls, *args, **kwargs)
- 보통 instance(주로
self
라고 쓰는 그것)를 인자로 받는 것과 차이가 있다.
- return이 반드시 인스턴스가 아니어도 된다.
- 단, 인스턴스를 반환하지 않을 경우는 인스턴스화가 되지 않음에 유의.
init 를 실행하는 2가지 방법
- 클래스에서 실행하는 방법
cls.__init__(instance, *args, **kwargs)
- 클래스에서 실행할 때에는 instance를 지정해줘야한다. (보통 self로 쓰는 그것)
- 인스턴스에서 실행하는 방법
instance.__init__(*args, **kwargs)
(instance는 인스턴스 객체)- 인스턴스에서 실행할 때에는 self에 인스턴스 자기자신이 자동으로 들어간다.
__call__의 역할
- __new__ 가 인스턴스 객체를 반환하지 않을 경우, __call__은 __init__를 실행하지 않는다.
- __call__ 을 오버라이딩할 경우, __new__와 __init__를 따로 실행해줘야 한다.
- __new__는 __init__를 자동으로 실행하지 않음
__new__(cls, [...])
,__init__(instance, [...])
⇒ __new__와 __init__는 첫번째 인자만 다르고 나머지는 동일하게 입력한다.
메타 클래스에서 생성자 자유로이 사용하기
※ 클래스에서 생성자를 사용하는 것과 헷갈릴 수 있으므로 주의하기.
- 위 설명에서 cls는 meta, self는 cls로 바꿔서 이해하면 된다. (메타클래스는 클래스의 클래스이므로)
파이썬의 최상위 메타클래스인 type을 상속 받아서 __new__, __init__, __call__ 를 오버라이딩한다.
class MetaType(type): # type을 상속 받아서 metaclass를 생성
def __new__(meta, metaname, bases, attrs): # cls가 아니라 meta를 받음
print('MetaType is used')
return super(MetaType, meta).__new__(meta, metaname, bases, attrs)
# return type.__new__(meta, metaname, bases, attrs) # 이렇게도 가능
def __init__(cls, *args, **kwargs):
print("MyClass is initialized")
cls.a = 'class attribute from meta' # 메타클래스에서 클래스의 속성을 정의 가능
def __call__(cls, *args, **kwargs):
print('MyClass is called')
instance = cls.__new__(cls, *args, **kwargs) # 인스턴스만 생성
# instance.__init__(*args, **kwargs) # 이렇게도 가능
cls.__init__(instance, *args, **kwargs) # 인스턴스 속성 초기화
return instance # class에 call 했을때 인스턴스 객체를 반환
메타클래스를 지정해서 클래스를 생성한다. 클래스는 메타클래스의 인스턴스이므로 이는 곧 메타클래스를 인스턴스화하는 것과 같다. 따라서 메타클래스 MetaType
의 __new__ 와 __init__가 실행되고 프린트문이 출력된다.
class MyClass(metaclass=MetaType):
b = 'class attribute'
def __init__(self, d):
self.c = 'instance attribute'
self.d = d
# print
# MetaType is used
# MyClass is initialized
a 속성은 MetaType
의 __init__에서 정의하였다. 메타클래스의 __init__에서 속성을 정의하면 클래스의 속성이 초기화된다.(클래스에서 __init__를 정의하면 인스턴스의 속성이 초기화되는 것과 같다.) b 속성은 MyClass
를 선언하면서 직접 정의하였다.
print(MyClass.a)
print(MyClass.b)
# print
# class attribute from meta
# class attribute
MyClass
를 인스턴스화한 myInstance
를 생성하였다. 메타클래스에서 정의한 __call__은 클래스를 호출할 때의 동작이다. 따라서 MetaType
에서 정의한 __call__ 안에 있는 print문이 실행된다.
myInstance = MyClass(1)
# print
# MyClass is called
MyClass
의 __init__에서 정의한 c 속성과, 클래스를 인스턴스할때 인자로 사용한 d 속성 모두 myInstance
의 속성으로 포함되어 있다.
vars(myInstance)
# output
# {'c': 'instance attribute', 'd': 1}