Search
🌍

bdd.1. title: 객체 생성은 new 메서드와 init 메서드의 순차적인 호출이다. new 메서드의 인자는 init 메서드와 인자를 공유하기 때문에 args, kwargs 외에 명시적으로 지정하고자 할 때에는 신중히 생각하라. 특히, new 메서드에서 init 메서드를 명시적으로 호출하지 마라.

생성
🚀 prev note
♻️ prev note
🚀 next note
♻️ next note
14 more properties
정적(static) 변수를 만들기 위해 아래와 같은 베이스 클래스 BaseFormState를 만들었다.
class BaseFormState(): def __new__( cls, name: str, se = None, ): k = f'{name}_session_state' if k not in st.session_state: obj = super().__new__(cls) obj.__init__(name, se) st.session_state[k] = obj return st.session_state[k] def __init__( self, name: str, se = None ): # ...
Python
복사
BaseFormState
여기까지는 문제가 없다. BaseFormState를 상속하는 클래스 OptionFormStateManager에서는 BaseFormState 의 생성자 프로토타입만을 보고 아래와 같은 생성자를 만들었다.
class OptionFormStateManager(BaseFormState): def __init__( self, name: str, options: list[Option], max_n: int = 1 ): super().__init__(name) self.options = options self.max_n = max_n
Python
복사
OptionFormStateManager
애플리케이션은 OptionFormStateManager의 생성자를 다음과 같이 호출한다.
options = [ Option('Math'), Option('English'), ] option_manager = OptionFormStateManager( 'score_option', options, max_n = 3 )
Python
복사
애플리케이션
하지만 여기서 문제가 생긴다. 이 코드는 OptionFormStateManager.__init__() 을 호출하기 이전에 OptionFormStateManager.__new__() 를 호출한다. OptionFormStateManager.__new__ 는 자동으로 부모 클래스의 BaseFormState.__new__()를 호출한다. 이것은 아래와 같은 오류를 만든다.
TypeError: __new__() got an unexpected keyword argument 'max_n'
Python
복사
이런 현상이 생기는 이유는 __new__의 인자는 __init__의 인자와 자동으로 동일하게 전달되기 때문에, BaseFormState__new__에서 OptionFormStateManagermax_n키워드 인자를 받을 수 없었기 때문이다.
이 문제를 해결하기 위해 아래와 같이 BaseFormState__new__를 수정할 수 있다.
class BaseFormState(): def __new__( cls, name: str, *args, se = None, **kwargs ): k = f'{name}_session_state' if k not in st.session_state: obj = super().__new__(cls) obj.__init__(name, se) st.session_state[k] = obj return st.session_state[k]
Python
복사
BaseFormState.__new__
이렇게 된다면 더 넓은 범위의 인자들이 입력되더라도 슬기롭게 처리할 수 있을 것이다. 스크립트를 실행해도 별 문제 없이 동작하는 것처럼 보인다. 하지만 실상은 그렇지 않다. OptionFormStateManager__init__ 메서드에 전달된 값을 로깅해 보자.
class OptionFormStateManager(BaseFormState): def __init__( self, name: str, options: list[Option], max_n: int = 1 ): print(f'__init__(): {name}, {options}, {max_n}') super().__init__(name) self.options = options self.max_n = max_n
Python
복사
OptionFormStateManager.__init__
놀랍게도 options에 할당된 값은 None임을 알 수 있다. 실제로 프로그래밍을 하다가 이와 같은 순간을 마주하게 되면 정말 당황스러울 것이다.
그 이유는 바로 BaseFormStateobj.__init__(name, se) 때문이다. 이것이 디버깅하기 어려운 이유는 파이썬의 유연성 때문에 프로그램이 명시적으로 오류를 내뱉지 않을지도 모르기 때문이다. BaseFormState의 생성자에서 se는 키워드 인자이기 때문에, obj.__init__(name, se=se) 이어야 한다. 하지만 개발 단계에서 위치 인자와 키워드 인자가 뒤바뀌는 일은 흔하다. 키워드 인자와 위치 인자가 명시적으로 구분되지 않아도 파이썬은 유연하게 실행되므로 내가 잘못된 코드를 작성하고 있는지 모를 위험이 도사린다. 이것이 바로 __new__ 메서드에서 __init__ 메서드를 명시적으로 호출하지 말아야 하는 첫 번째 이유이다.
두 번째 이유는 파이썬이 __new__ 메서드를 실행한 이후 자동으로 __init__ 메서드를 호출하기 때문이다. 따라서 __new__ 메서드에서 __init__ 메서드를 실행하면 __init__ 메서드가 여러 번 실행하는 꼴이 된다. OptionFormStateManager 의 객체를 생성하는 경우 BaseFormState 클래스의 __new__ 메서드에 전달된 cls 변수에는 OptionFormStateManager이 들어 있으므로, 아래와 같은 스크립트가 실행된다고 하더라도 실행되는 __init__ 메서드는 BaseFormState의 메서드가 아닌 하위 클래스 OptionFormStateManager 의 메서드이다. 솔직히, 이런 상황을 프로그래밍할 일이 많지 않다.
class BaseFormState(): def __new__( cls, name: str, *args, se = None, **kwargs ): # ... obj = super().__new__(cls) obj.__init__(name, se)
Python
복사
BaseFormState.__new__
객체 생성은 __new__ 메서드와 __init__ 메서드의 순차적인 호출이다. __new__ 메서드의 인자는 __init__ 메서드와 인자를 공유하기 때문에 *args, **kwargs 외에 명시적으로 지정하고자 할 때에는 신중히 생각하라. 특히, __new__ 메서드에서 __init__ 메서드를 명시적으로 호출하지 마라.
parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료을 보관해 두는 영역입니다.
1.
None
from : 과거의 어떤 원자적 생각이 이 생각을 만들었는지 연결하고 설명합니다.
1.
supplementary : 어떤 새로운 생각이 이 문서에 작성된 생각을 뒷받침하는지 연결합니다.
1.
None
opposite : 어떤 새로운 생각이 이 문서에 작성된 생각과 대조되는지 연결합니다.
1.
None
to : 이 문서에 작성된 생각이 어떤 생각으로 발전되거나 이어지는지를 작성하는 영역입니다.
1.
None
ref : 생각에 참고한 자료입니다.
1.
None