출처: https://meyouus.tistory.com/64 [정보 공유 - For Me For You For Us]
본문으로 바로가기

이번에는 class 에서 사용하는 super에 대해서 알아보도록 하겠다.

 

파이썬을 하다 보면 다음과 같은 형식의 코드를 본 적이 있을 것이다:

class PersonalInfoForm(forms.Form):

    username = forms.CharField(required=True)
    email = forms.EmailField(required=True)

    def __init__(self, *args, **kwargs):
        user_details = kwargs.pop('user_details', None)
        super(PersonalInfoForm, self).__init__(*args, **kwargs)
        if user_details:
            self.fields['username'].initial = user_details[0]['username']

여기서 super(PersonalInfoForm, self).__init__(*args, **kwargs)와 같이 super가 붙어 있는 것을 본 적 있을 것이다.

 

super는 기반 class, 즉 superior class, 부모 class,를 찾는 과정이라고 생각하면 된다.

 

 

※ 기본적인 상속 예제(1)

다음의 예제를 보자:

class Human:
	def __init__(self):
		self.a = 'It is A'

class Man(Human):
	def __init__(self):
		super().__init__()
		self.b = 'It is B'
        
        
>>> Steve=Man()
>>> print(Steve.b)
'It is B'
>>> print(Steve.a)
'It is A'

우선 Steve=Man()을 통해 Man class에 부여를 해준다. 이때 print(Steve.b)를 통해 self.b = 'It is B'라고 정의 해놓은 문자열을 출력해준다. 또한, print(Steve.a)를 통해 Human class에 있는 a 변수를 불러오는 것이다.

 

 

※ 기본적인 상속 예제(2)

그렇다면 다음의 예제를 한번 보자:

class addition:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def add(self):
        result = self.a + self.b
        return result

class calculator(addition):
    def __init__(self):
    	super().__init__()
        self.cal = 'I am a Calculator'


>>> tool=calculator()
Traceback (most recent call last):
  File "<pyshell#145>", line 1, in <module>
    tool=calculator()
  File "<pyshell#144>", line 3, in __init__
    super().__init__()
TypeError: __init__() missing 2 required positional arguments: 'a' and 'b'

여기서 단순하기 tool=calculator()을 한다면 위와 같은 에러가 날 것이다. 이유는 a, b를 통과시켜줘야 하는데, 통과시키지 않았기 때문이다. (addition class에는 __init__(self, a, b)에 따라, a, b를 무조건 통과를 시켜줘야 하기 때문이다.) 그렇다면 다음과 같이 class를 불러와보자:

>>> tool=calculator(1,2)
Traceback (most recent call last):
  File "<pyshell#173>", line 1, in <module>
    tool=calculator(1,2)
TypeError: __init__() takes 1 positional argument but 3 were given

>>> tool=calculator(a=1, b=2)
Traceback (most recent call last):
  File "<pyshell#174>", line 1, in <module>
    tool=calculator(a=1, b=2)
TypeError: __init__() got an unexpected keyword argument 'a'

여기서도 마찬가지로 에러가 난다. 결국엔 우리가 처음에 짰던 calculator class가 문제가 있다는 것을 알 수 있다. 

 

따라서 다음과 같이 한번 바꿔보았다:

class addition:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def add(self):
        result = self.a + self.b
        return result

class calculator(addition):
    def __init__(self, *args, **kwargs):
    	super().__init__(*args, **kwargs)
        self.cal = 'I am a Calculator'


>>> tool1=calculator(1,2)
>>> tool1.add()
3
>>> tool2=calculator(a=1,b=2)
>>> tool2.add()
3

여기서는 어떻게 된 것일까? calculator(1,2)calculator(a=1, b=2)가 둘 다 잘 된다. 이유는 우리가 __init__(self, *args, **kwargs)처럼 *args, **kwargs를 통과시켜줬기 때문이다. *argskey가 없는 argument를 통과시켜주고, **kwargskey가 있는 argument를 통과시켜주기 때문이다. 결국 calculator(1,2)*args가, calculator(a=1, b=2)**kwargs가 통과시켜준 것이다.

 

여기서 또 하나 알 수 있는 것은, __init__(self)의 괄호 안에 통과시키는 것은 무조건 정의를 해주어야 한다는 것이다. (self는 자신을 지칭하는 것이므로 신경쓸 필요 없다) 예를 들어 __init__(self, water) 를 통과시켜줬는데 self.water를 정의를 해주지 않으면 에러가 나는 것이다.

 

 

※ 기본적인 상속 예제(3)

비슷한 예시를 들어보자:

class addition:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def add(self):
        result = self.a + self.b
        return result

class calculator(addition):
    def __init__(self):
    	super().__init__(1, 2)
        self.cal = 'I am a Calculator'


>>> tool1=calculator()
>>> too1.add()
3
>>> tool1.a
1
>>> tool1.b
2

여기서는 어떻게 된것인가? 위와 다르게 def __init__(self)*args, *kwargs를 넣지 않고 super().__init__(1,2)를 넣어주었더니 calculator()에 숫자나 a, b값을 따로 통과시키지 않아도 됐다.

 

이유는 1, 2를 정의함으로써 a, b값을 이미 정의를 해주기 때문에 따로 정의하지 않아도 되는 것이다.

다만 이제 def __init__(self)*args, *kwargs (또는  a, b)를 정의해주지 않았기 때문에 tool1=calculator(2,3) 같이 새로 정의해주려고 하면 에러가 난다.

 

위의 방식과 변형시켜 이렇게도 나타낼 수 있다:

class addition:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def add(self):
        result = self.a + self.b
        return result

class calculator(addition):
    def __init__(self):
    	super().__init__(b=2, a=1)
        self.cal = 'I am a Calculator'


>>> tool1=calculator()
>>> tool1.add()
3
>>> tool1.a
1
>>> tool1.b
2

 

 

※ 기본적인 상속 예제(4)

마지막으로 다음 예제를  보자:

class addition:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def add(self):
        result = self.a + self.b
        return result

class calculator(addition):
    def __init__(self, *args, **kwargs):
    	super().__init__(1, 2)
        self.cal = 'I am a Calculator'

이번에는 def __init__(self, *args, **kwargs) 처럼 *args, *kwargs를 넣어주었다. 이렇게 된다면 어떻게 될까?

>>> tool1=calculator()
>>> tool1.add()
3
>>> tool1.a
1
>>> tool1.b
2
>>> tool2=calculator(2,3)
>>> tool2.add()
3
>>> tool2.a
1
>>> tool2.b
2
>>> tool3=calculator(a=2, b=3)
>>> tool3.add()
3
>>> tool3.a
1
>>> tool3.b
2

잘못 본 것이 아니다. calculator()에 값을 넣어도 통과는 되지만, a, b에 저장되지는 않아, 무조건 super().__init__(1, 2)에 정의된 a=1, b=2로 통과된다. 따라서 앞서 본 것과 같이, a, b값을 정의하고 싶다면 def __init__()super().__init__()에 *args, **kwargs를 통과시켜주는 것이 좋다.

 

 

※ 요약 및 정리

  • def __init__(self) 의 괄호 안에 들어가는 변수는, 무조건 정의를 해주어야 한다. 해주지 않으면 에러가 난다.
  • super().__init__() 의 괄호 안에 정의하는 변수는, 부모 클래스에서 정의해야만 하는 변수들을 넣어줘야 한다.
  • *argstuple 형태로, **kwargsdictionary 형태로 변수들을 저장시켜주는 역할을 한다. 하나만 쓸 경우, 그것에 맞는 형태로 변수들을 통과시켜줘야 한다.
  • 특정 클래스를 상속할 경우, 자식 클래스에 super().__init__()을 삽입하여야만 부모 클래스의 변수를 상속받을 수 있다
  • 다음 2가지는 모두 같은 뜻이다:
    • super().__init__()
    • super(ClassName, self).__init__()
    • ClassName은 지금 이 클래스의 이름을 쓰는 것인데, 이는 사실 python2.XX 에서의 문법이라 코드 자체의 범용성을 위해 사용하는 것이지, 안 써도 지금의 python3.XX 에서는 무방하다.
  • 다음 2가지는 모두 같은 뜻이다:
    • super().__init__(*args, **kwargs)
    • super(ClassName, self).__init__(*args, **kwargs)
  • 참고로 *args, **kwargs는 클래스 안에서 함수를 사용하려 할 때 필요한 것이다. 함수가 아닌 클래스 자체에서 사용할 때는 필요 없다:
# Django / forms.py
class CreateUserForm(UserCreationForm):
	class Meta:
		model = User
		fields = ['username', 'email', 'password1', 'password2']

 

이번 super / 상속자 / *args, **kwargs 공부는 나도 잘 몰랐던 부분이었기 때문에 이곳 저곳을 찾아가면서 분주하게 공부한 내용을 정리해서 올리는 것이다. 모두 다 한번에 연관이 있던 이유로 이해하는데 오랜 시간이 걸렸다. 누군가는 이것을 보면서 이해하는데 도움이 됐으면 좋겠다.