본문 바로가기

Back-end/Forum with Django

32. __init__과 super()

이전 차시에서 우리는 회원탈퇴 시 사용자에게 비밀번호를 입력하게 하는 안전장치를 만들었습니다.
그때 사용했던 __init__, super메서드에 대해 알아봅시다.

사용했던 예제 코드

class DeleteReasonForm(forms.ModelForm):
    password = forms.CharField(label="비밀번호 확인", widget=forms.PasswordInput)
    class Meta:
        ...

    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = user

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        if password and not check_password(password, self.user.password):
            self.add_error('password', '비밀번호가 일치하지 않습니다.')

1. __init__()메서드

  • 인스턴스 초기화(생성자) 메서드
    • 객체가 만들어 질 때(인스턴스) 자동으로 호출됩니다.
  • 위의 예제에서는 폼을 만들때 해당하는 user객체를 받아 self.user에 저장해 두는 용도로 활용합니다.

매개변수

  • self: 파이썬의 클래스에서 꼭 들어가는 자신(인스턴스) 참조
  • user: 사용하는 뷰에서 request.user를 전달
  • *args: 추가 위치 인자들(튜플 형태)
  • **kwargs: 추가 키워드 인자들(딕셔너리 형태)

super()를 사용하여, 쉽게 설명하면 부모 클래스(ModelForm)에게 user를 제외한 전달인자들을 넘겨줍니다.
(장고 폼의 기본 기능을 사용하기 위해 필요한 기본 인자들(부모 클래스가 필요한)을 전달해 줍니다.)


2. clean()메서드

  • 장고 폼의 유효성 검사 로직을 커스텀할 때 사용하는 메서드 입니다.
    • 뷰에서 is_valid()메서드를 통해 유효성 검사를 진행할 때 정의된 clean()메서드가 실행됩니다.
  • 위의 예제에서는 폼 데이터 내의 입력받은 비밀번호가 사용자의 비밀번호와 일치하는 지를 판단하는 역할을 수행합니다.

부모 클래스(super() = ModelForm)의 clean()메서드로 부터 폼 입력값 데이터를 딕셔너리 형태로 받아옵니다.
auth.hashers의 check_password메서드를 사용하여 기존의 비밀번호와 입력된 비밀번호의 값을 비교합니다.


3. args, *kwargs 매개변수

args (arguments, 가변 위치 인자)

  • 여러개의 추가 위치 인자를 순서대로 '튜플'로 전달받습니다.
  • 메서드에서 여러개의 '이름이 없는' 인자를 받을 때 사용합니다.
def argsFunc(*args):
  print(args)

argsFunc(2, 1, 4)

=> (2, 1, 4)

kwargs (keyword arguments, 가변 키워드 인자)

  • 여러개의 추가 키워드 인자를 '딕셔너리'로 전달받습니다.

  • 메서드에서 여러개의 'key=value'형태의 인자를 받을 때 사용합니다.
```python
def kwargsFunc(**kwargs):
    print(kwargs)

kwargs(a=2, b=1, c=4)

=> { 'a': 2, 'b': 1, 'c': 4 }

두 방식 모두 메서드에 넘길 파라미터의 개수가 정해지지 않았거나, 다양한 인자를 받아야 할 때 사용합니다.
특히 프레임워크 코드에서 인자를 전부 받아, 상속받는 부모 클래스에 그대로 전달하는 용도로 사용됩니다.

두 매개변수를 동시에 사용할 때는 항상 (args, *kwargs)의 순서로 써야 합니다.

example

def func(a, b, *args, **kwargs):
    print(a, b)
    print(args)
    print(kwargs)

func(2, 1, 4, 3, e=5, f=6)

=> 2 1
=> (4, 3)
=> { 'e': 5, 'f': 6}

클래스 내에서 활용

  • 생성자(__init__) or 메서드에서 args, kwargs를 받으면 인자의 개수나 종류에 상관없이 유연하게 처리할 수 있습니다.
  • 자식 클래스에서 커스텀 로직을 정의할 때, 부모 클래스의 초기화 및 동작을 유지하기 위해 부모가 요구하는 인자들을 동일하게 수신하고, super()를 통해 부모 클래스에 전달합니다.
    • 우리가 clean()메서드에서 super().clean()을 사용할 수 있는 것도 부모 클래스에게 'request.POST' args를 넘겨주었기 때문에 사용할 수 있는 것입니다
class Parent:
    def __init__(self, a, b, **kwargs):
        print(kwargs)
        ...

# 자식 클래스가 Parent를 상속받는다면, 자식 클래스에서도 init에 같은 인자를 받을 수 있는데,
# 이때 부모 클래스가 정의한(필요한) 인자를 부모 클래스에 넘겨주어 부모 클래스의 init이 정상 작동할 수 있도록 합니다.
class Child(Parent):
    # 커스텀 인자를 앞에 적고, 순서에 유의하여 args와 *kwargs를 인자로 받습니다.
    def init(self, user, args, *kwargs):
        self.user = user
        super().init(args, *kwargs)
#forms.py
class DeleteReasonForm(forms.ModelForm):
    def __init__(self, user, *args, **kwargs)
        super.__init__(*args, **kwargs)
        ...

#views.py
def function(request):
    ...
    form = DeleteReasonForm(request.user, request.POST)

# 위의 예제에서는 첫 번쨰 인자(request.user)는 자식클래스 생성자의 user매개변수 값으로 전달됩니다.
# 그 이후에 전달되는 인자(들)은(request.POST)부모 클래스(ModelForm)가 필요로 하는 데이터입니다.
# 그 데이터들은 부모 클래스의 기능을 사용하기 위해 *args 튜플에 담아 부모 클래스에게 그대로 전달해 줍니다.

# 부모클래스에 정의되어 있는 인자들 예시: request.POST, request.FILES, initial ...

4. super()메서드

super()메소드는 부모 클래스를 가리키는, 부모 클래스의 메서드나 생성자를 사용(호출)할 수 있게 해주는 메서드입니다.
다만 완벽히 '부모 클래스를 가리킨다'라고 말하기에는 무리가 있습니다.

MRO(Method Resolution Order)

super()는 단순히 직계 부모 클래스만을 가리키는 것이 아닙니다. 'MRO'라는 파이선의 상속 탐색 순서를 따라 다음 클래스를 참조하는 것 입니다.

class A:
    def func(self):
        print("A Class")

class B(A):
    def func(self):
        print("B Class")
        super().func()

class C(A):
    def func(self):
        print("C Class")
        super().func()

class D(B, C):
    def func(self):
        print("D Class")
        super().func()

d = D()
d.func()
=> D Class
=> B Class
=> C Class
=> A Class

print(D.mro())
=> [D, B, C, A, object]

Python의 MRO를 설명하는 예제입니다.

클래스를 상속받을 때 super()를 사용하게 되면, MRO 순서에 따라 상위 클래스들를 탐색하게 됩니다.
이때 깊은 상속관계에서 여러 조상 클래스들이 같은 메서드(func())를 가지고 있다면 이 MRO순서에 따라 중복이 없이 클래스를 탐색하고, 해당 메서드들을 실행하게 됩니다.

  • B, C, A, object 클래스를 차례대로 탐색하고 내부에 func()메서드가 있다면 호출하게 됨

D에서 super()를 사용하면 mro 순서에 따라 B클래스, C클래스를 참조하게 되어 결국 부모 클래스를 가리키는 것이 됩니다.

만약 B, C클래스에 func()메서드가 존재하지 않는다면 super()는 MRO순서에 따라 B, C를 탐색 후 func()메서드가 존재하지 않으니 그대로 다음 순서인 A클래스로 넘어가 내부의 func()메서드를 호출하게 됩니다.

super()를 사용하는 이유

  • 상위 클래스의 명확한 이름을 하드코딩하여 작성하지 않아도 됩니다.
    • MRO상 호출한 클래스의 바로 다음 순서는 부모 클래스를 가리키고 있습니다.
    • 이를 이용하여 부모 클래스명을 직접 작성하지 않아 유지보수에 이점, 상속 구조의 확장/변경 시 코드 변경 최소화
  • 생성자/메서드 중복 호출 방지
    • 위의 예제에서도 볼 수 있듯이 D는 B, C클래스를 상속받고, B와 C는 A클래스를 상속받습니다. 이때 여러 부모 클래스에 같은 메서드가 존재한다면 A클래스는 B에서 올라갈 때 한번, C에서 올라갈 때 한번, 총 두번의 중복 실행이 될 우려가 있습니다.
      • 만약 B, C클래스에서 super()가 아닌 A.func()으로 호출했다면 A클래스의 func()이 두번 실행되게 됩니다.
    • 이런 상황을 방지하기 위하여 super()메서드는 다중 상속 시 트리 탐색 순서를 C3 선형화 알고리즘을 사용하여 중복없이 탐색할 수 있도록 도와줍니다.
  • 결론적으로 'super()는 단순히 직계 상위 클래스를 호출'하는 메서드가 아니라, 상속 구조에서 메서드 탐색 순서(MRO)에 따라 중복 없이 상위 클래스들을 탐색할 수 있도록 도와주는 파이썬 객체 모델의 기능입니다.

위와 같은 이유로 Parent, Child로 이루어진 단일 상속 클래스 구조에서도 부모 클래스의 메서드를 호출하고 싶다면 super()를 쓰는 것이 권장 패턴입니다.

조금 더 자세히

  • MRO는 현재 클래스를 기준으로 해당 클래스와 그 모든 부모(조상)들 까지 탐색하는 상속구조를 파악하여 생성됩니다.
    • 즉, '내가 상속받은 클래스의 체인 전체'를 계산하여 중복이 없는 하나의 리스트로 정렬합니다.
  • MRO는 클래스가 정의될 때 생성되며, 클래스명.mro() 또는 클래스명.mro로 조회하실 수 있습니다.
  • C3 선형화 알고리즘을 통한 MRO는 '다중 상속 구조를 어떤 순서로 중복없이 탐색하는가'를 위한 리스트 입니다.
    • 리스트만을 보고 어떤 클래스의 부모가 누구인지, 어떤 클래스의 자식 클래스가 누구인지는 파악할 수 없습니다.

'Back-end > Forum with Django' 카테고리의 다른 글

34. 마크다운 에디터  (1) 2025.09.08
33. 조회수  (1) 2025.09.08
31. 회원 탈퇴  (0) 2025.08.12
30. 프로필 페이지  (2) 2025.08.12
29. 비밀번호 변경  (0) 2025.08.12