이번에는 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를 통과시켜줬기 때문이다. *args는 key가 없는 argument를 통과시켜주고, **kwargs는 key가 있는 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__() 의 괄호 안에 정의하는 변수는, 부모 클래스에서 정의해야만 하는 변수들을 넣어줘야 한다.
- *args는 tuple 형태로, **kwargs는 dictionary 형태로 변수들을 저장시켜주는 역할을 한다. 하나만 쓸 경우, 그것에 맞는 형태로 변수들을 통과시켜줘야 한다.
- 특정 클래스를 상속할 경우, 자식 클래스에 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 공부는 나도 잘 몰랐던 부분이었기 때문에 이곳 저곳을 찾아가면서 분주하게 공부한 내용을 정리해서 올리는 것이다. 모두 다 한번에 연관이 있던 이유로 이해하는데 오랜 시간이 걸렸다. 누군가는 이것을 보면서 이해하는데 도움이 됐으면 좋겠다.
'Python 공부하기' 카테고리의 다른 글
zip 내장 함수에 대하여 (0) | 2020.11.27 |
---|---|
isdecimal(), isdigit(), isnumeric()에 대하여 (0) | 2020.11.25 |
Enumerate 함수에 대하여 (0) | 2020.11.25 |
Regex(Regular Expression)에 대하여 (0) | 2020.11.15 |
Methods에 대한 고찰 (__init__, if __name__ == "__main__") (0) | 2020.10.23 |