15wk-1: 클래스공부 5단계 – 상속

Author

최규빈

Published

June 12, 2023

강의영상

youtube: https://youtube.com/playlist?list=PLQqh36zP38-zY0ccMvo01wQXc1Ht9H2TA

클래스공부 5단계: 상속

상속의 사용방법

- 클래스를 조금 수정하고 싶을때, 아래와 같은 문법을 이용하면 편리하다.

class 새로운_클래스_이름(수정할_클래스_이름): 
    def 수정_및_추가할_함수이름(self,...):
        ...

간단한 사용예시

class Init:
    def __init__(self,value):
        ## 여기는 Init 클래스야 
        print("Init클래스에서 정의된 __init__메소드를 실행합니다")        
        self.value = value
class Show(Init):
    def show(self):
        ## 여기는 Show 클래스야
        print("Show클래스에서 정의된 show메소드를 실행합니다")
        print('value={}'.format(self.value))
a = Show(5)
Init클래스에서 정의된 __init__메소드를 실행합니다
a.show()
Show클래스에서 정의된 show메소드를 실행합니다
value=5
a.show??
Signature: a.show()
Docstring: <no docstring>
Source:   
    def show(self):
        ## 여기는 Show 클래스야
        print("Show클래스에서 정의된 show메소드를 실행합니다")
        print('value={}'.format(self.value))
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-342-5ac798f79ec3>
Type:      method
a.__init__??
Signature: a.__init__(value)
Docstring: Initialize self.  See help(type(self)) for accurate signature.
Source:   
    def __init__(self,value):
        ## 여기는 Init 클래스야 
        print("Init클래스에서 정의된 __init__메소드를 실행합니다")        
        self.value = value
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-341-3a7b8109daf8>
Type:      method

슈퍼클래스 메소드 재활용

- 방법1: 직접 슈퍼클래스 명시

class Deco(Show):
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        Show.__init__(self,value)
        print("짠짠~!!")
a=Deco(5)
짜라란~~
Init클래스에서 정의된 __init__메소드를 실행합니다
짠짠~!!
a.show??
Signature: a.show()
Docstring: <no docstring>
Source:   
    def show(self):
        ## 여기는 Show 클래스야
        print("Show클래스에서 정의된 show메소드를 실행합니다")
        print('value={}'.format(self.value))
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-342-5ac798f79ec3>
Type:      method
a.__init__??
Signature: a.__init__(value)
Docstring: Initialize self.  See help(type(self)) for accurate signature.
Source:   
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        Show.__init__(self,value)
        print("짠짠~!!")
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-348-8837bbd4ea3b>
Type:      method

- 방법2: super() 이용 (생략안한버전)

class Deco(Show):
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        super(Deco,self).__init__(value)
        print("짠짠~!!")
a=Deco(5)
짜라란~~
Init클래스에서 정의된 __init__메소드를 실행합니다
짠짠~!!
a.show??
Signature: a.show()
Docstring: <no docstring>
Source:   
    def show(self):
        ## 여기는 Show 클래스야
        print("Show클래스에서 정의된 show메소드를 실행합니다")
        print('value={}'.format(self.value))
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-342-5ac798f79ec3>
Type:      method
a.__init__??
Signature: a.__init__(value)
Docstring: Initialize self.  See help(type(self)) for accurate signature.
Source:   
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        super(Deco,self).__init__(value)
        print("짠짠~!!")
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-361-5ba5667ac964>
Type:      method

- 방법3: super() 이용 (생략한버젼) <– 이렇게 쓰세요!

class Deco(Show):
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        super().__init__(value)
        print("짠짠~!!")
a=Deco(5)
짜라란~~
Init클래스에서 정의된 __init__메소드를 실행합니다
짠짠~!!
a.show??
Signature: a.show()
Docstring: <no docstring>
Source:   
    def show(self):
        ## 여기는 Show 클래스야
        print("Show클래스에서 정의된 show메소드를 실행합니다")
        print('value={}'.format(self.value))
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-342-5ac798f79ec3>
Type:      method
a.__init__??
Signature: a.__init__(value)
Docstring: Initialize self.  See help(type(self)) for accurate signature.
Source:   
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        super().__init__(value)
        print("짠짠~!!")
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-365-9c3108795053>
Type:      method

- 방법4: super() 이용, 방법3을 이해하기 위한 코드

class Deco(Show):
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        super(__class__,self).__init__(value)
        print("짠짠~!!")
a=Deco(5)
짜라란~~
Init클래스에서 정의된 __init__메소드를 실행합니다
짠짠~!!
a.show??
Signature: a.show()
Docstring: <no docstring>
Source:   
    def show(self):
        ## 여기는 Show 클래스야
        print("Show클래스에서 정의된 show메소드를 실행합니다")
        print('value={}'.format(self.value))
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-342-5ac798f79ec3>
Type:      method
a.__init__??
Signature: a.__init__(value)
Docstring: Initialize self.  See help(type(self)) for accurate signature.
Source:   
    def __init__(self,value):
        ## 여기는 Deco클래스야~
        print("짜라란~~")
        super(__class__,self).__init__(value)
        print("짠짠~!!")
File:      ~/Dropbox/07_lectures/PP2023/posts/03_Class/<ipython-input-375-4e2b30e113c0>
Type:      method

- 이때 방법2-4는 완전히 동일한 코드이다, 방법1-4는 이 예제에서 같은효과이다.

다중상속

일반적인 다중상속

- Add 클래스선언

class Add:
    def __init__(self,value):
        self.value = value 
    def __add__(self,value2):
        return self.value + value2
a=Add(2)
a+5
7
a*2 # 곱하기는 정의한적없음
TypeError: unsupported operand type(s) for *: 'Add' and 'int'

- Mul 클래스선언

class Mul:
    def __init__(self,value):
        self.value = value 
    def __mul__(self,value2):
        return self.value * value2
a = Mul(5)
a.value
5
a+2 #정의한적 없음
TypeError: unsupported operand type(s) for +: 'Mul' and 'int'
a*2 
10

- AddMul 클래스를 선언 (기존의 Add, Mul 상속받아서 이용)

class AddMul(Add,Mul):
    pass 
a = AddMul(5)
a.value
5
a+2
7
a*5
25

다중상속 우선순위 (__init__이 겹치는뎅?)

class Add:
    def __init__(self,value):
        print("Add클래스에서 정의된 __init__ 메소드가 실행됩니다")
        self.value = value 
    def __add__(self,value2):
        return self.value + value2
              
class Mul:
    def __init__(self,value):
        print("Mul클래스에서 정의된 __init__ 메소드가 실행됩니다")        
        self.value = value 
    def __mul__(self,value2):
        return self.value * value2        
    
class AddMul(Add,Mul):
    pass     
a = AddMul(5)
Add클래스에서 정의된 __init__ 메소드가 실행됩니다

믹스인 클래스 (\(\star\star\star\))

class Init:
    def __init__(self,value):
        ## 여기는 Init 클래스야 
        print("Init클래스에서 정의된 __init__메소드를 실행합니다")        
        self.value = value

class Add(Init):
    def __add__(self,value2):
        return self.value + value2
              
class Mul(Init):
    def __mul__(self,value2):
        return self.value * value2        
    
class AddMul(Add,Mul):
    pass     
a = AddMul(5)
Init클래스에서 정의된 __init__메소드를 실행합니다
a+2
7
a*5
25

다중상속시 super()의 활용

super()를 쓰지 않은 나쁜사용예시

- 초기값을 설정하는 클래스 만듬

class Init:
    def __init__(self,value):
        ## 여기는 Init 클래스야 
        print("Init클래스에서 정의된 __init__메소드를 실행합니다")        
        self.value = value

- init을 상속받아서

  • \({\tt 초기값}= {\tt 초기값} \times 2\)
  • \({\tt 초기값}= {\tt 초기값} + 5\)

를 인스턴스 생성과 동시에 수행하는 클래스를 각각 만듦

class Times2(Init):
    def __init__(self,value):
        Init.__init__(self,value)
        self.value = self.value * 2
a=Times2(5)
a.value
Init클래스에서 정의된 __init__메소드를 실행합니다
10
class Plus5(Init):
    def __init__(self,value):
        Init.__init__(self,value)
        self.value = self.value + 5
a=Plus5(5)
a.value
Init클래스에서 정의된 __init__메소드를 실행합니다
10

- 지나고 보니까 “\({\tt 초기값} = {\tt 초기값} \times 2 + 5\)” 를 인스턴스 생성과 동시에 수행해주는 클래스를 만들고 싶음.

class Times2Plus5(Times2,Plus5):
    def __init__(self,value):
        Times2.__init__(self,value)
        Plus5.__init__(self,self.value)
a = Times2Plus5(5)
Init클래스에서 정의된 __init__메소드를 실행합니다
Init클래스에서 정의된 __init__메소드를 실행합니다
a.value 
15

- 싫은이유1: 코드가 지저분하다.

- 싫은이유2: 진정한 의미의 상속이 아닌것 같다.

class Times2Plus5():
    def __init__(self,value):
        Times2.__init__(self,value)
        Plus5.__init__(self,self.value)
a = Times2Plus5(5)
Init클래스에서 정의된 __init__메소드를 실행합니다
Init클래스에서 정의된 __init__메소드를 실행합니다
a.value 
15

super()를 활용한 좋은사용예시

- 아키텍처를 아래와 같이 바꾸자

class Init(object):
    def __init__(self,value):
        ## 여기는 Init 클래스야 
        print("Init클래스에서 정의된 __init__메소드를 실행합니다")        
        self.value = value
        
class Times2(Init):
    def __init__(self,value):
        super().__init__(value)
        self.value = self.value * 2
        
class Plus5(Init):
    def __init__(self,value):
        super().__init__(value)
        self.value = self.value + 5
        
class Times2Plus5(Plus5,Times2):
    def __init__(self,value):
        super().__init__(value)

- 써보자

a=Times2Plus5(5)
Init클래스에서 정의된 __init__메소드를 실행합니다
a.value
15
(5*2)+5
15

- 이것이 왜 가능?

원리: mro상으로 상위에 있는 순서대로 타고 올라간 뒤, mro순서대로 한번씩만 __init__을 실행함!!

- 소감: 코드가 깔끔하긴해 + 진정한 상속의 느낌도 있어 (그렇지만 사용하고 싶지는 않음)

super()의 사용방법

- 이제 “\(({\tt 초기값} \times 2 + 5)\times 2\)” 를 수행해주는 클래스를 만들고 싶음.

class Init(object):
    def __init__(self,value):
        ## 여기는 Init 클래스야 
        print("Init클래스에서 정의된 __init__메소드를 실행합니다")        
        self.value = value
        
class Times2(Init):
    def __init__(self,value):
        super().__init__(value)
        self.value = self.value * 2
        
class Plus5(Init):
    def __init__(self,value):
        super().__init__(value)
        self.value = self.value + 5
        
class Times2Plus5Times2(Plus5,Times2):
    def __init__(self,value):
        super().__init__(value)
        super(Plus5,self).__init__(self.value)
a=Times2Plus5Times2(5)
Init클래스에서 정의된 __init__메소드를 실행합니다
Init클래스에서 정의된 __init__메소드를 실행합니다
a.value
30
Times2Plus5Times2.mro()
[__main__.Times2Plus5Times2,
 __main__.Plus5,
 __main__.Times2,
 __main__.Init,
 object]

- 코드해석

  • super().__init__(value)Times2Plus5Times2의 MRO순서로 상위인 클래스 Init,Times2,Plus__init__을 순서대로 실행하되 중복실행은 하지 않음.
  • super(Plus5,self).__init__(self.value)Plus5보다 MRO순서로 상위인 클래스 Init,Times2__init__을 순서대로 실행하되 중복실행은 하지 않음.

- 그냥 이게 낫지 않나?

class Init(object):
    def __init__(self,value):
        ## 여기는 Init 클래스야 
        print("Init클래스에서 정의된 __init__메소드를 실행합니다")        
        self.value = value
        
class Times2(Init):
    def times2(self):
        self.value = self.value * 2
        
class Plus5(Init):
    def plus5(self):
        self.value = self.value + 5
        
class Times2Plus5Times2(Plus5,Times2):
    def times2plus5times2(self):
        self.times2()
        self.plus5()
        self.times2()
a = Times2Plus5Times2(5)
Init클래스에서 정의된 __init__메소드를 실행합니다
a.times2plus5times2()
a.value
30

- 그래도 이 문법을 알아야 한다. 왜??

단계 인터넷밈 클래스 레포트표지
1단계: 구상 \(\bullet\) 이거 재미있다.
\(\bullet\) 밈화하자.
\(\bullet\) 이 코드 반복해서 자주 쓸 것 같다.
\(\bullet\) 이 코드를 쉽게 찍어내는 (복사할 수 있는) 클래스를 만들자
\(\bullet\) 레포트 표지를 자주 만들 것 같음
\(\bullet\) 양식파일을 만들까?
2단계: 틀생성 \(\bullet\) “밈틀”: 복사하고 싶은 속성을 추려 밈을 생산하기에 유리한 틀을 만듬 \(\bullet\) 클래스의 선언 \(\bullet\) REPORT_2023_최규빈.hwp 양식파일을 생성
3단계: 틀 \(\to\) 복제 \(\bullet\) 밈화: “밈틀”에서 다양한 밈을 만들고 놈 \(\bullet\) 인스턴스화: 클래스에서 인스턴스를 생산 \(\bullet\) 레포트 양식표지에서 다양한 레포트를 냄
4단계: 틀 \(\to\) 틀변경 \(\to\) 복제 \(\bullet\) 생각해보니까 초기 밈틀은 시시함.
\(\bullet\) 초기 밈틀을 수정해 새로운 밈틀을 만들고 더 재미있는 밈을 만들고 놈
\(\bullet\) 초기클래스와 비슷한 클래스를 선언할 일이 생김
\(\bullet\) 상속,오버라이딩: 초기클래스를 상속받아 클래스를 새롭게 정의하고 인스턴스를 재 생산
\(\bullet\) 공모전에 참가하여 결과보고서를 작성할 일이 생김.
\(\bullet\) REPORT_2023_최규빈.hwp 를 적당히 변형하여 수정된 틀을 만들고 결과보고서 생산.

상속은 위의 표에서 4단계에 해당한다. 즉 어떠한 클래스를 상속받을때는 “내가 만든 클래스”가 아닐 경우가 대부분이다. 따라서 “애초부터 메소드가 겹치지 않게 클래스들을 깔끔하게 디자인을 하는것” 은 불가능한 경우가 많다.

리스트의 상속

- list와 비슷한데 멤버들의 빈도가 계산되는 메소드를 포함하는 새로운 나만의 list를 만들고 싶다.

lst = list('asdfasssdfa')
lst 
['a', 's', 'd', 'f', 'a', 's', 's', 's', 'd', 'f', 'a']

- 각 원소들의 빈도를 구해보면 아래와 같다.

{s:lst.count(s) for s in set(lst)}
{'d': 2, 'a': 3, 's': 4, 'f': 2}
lst.freq() # 이렇게 실행하면 위의결과가 나왔으면 좋겠다.
AttributeError: 'list' object has no attribute 'freq'

- 이것을 내가 정의하는 새로운 list의 메소드로 넣고 싶다.

class List(list):
    def freq(self):
        return {s:self.count(s) for s in set(self)}
lst2 = List('asdfasssdfa')
lst2
['a', 's', 'd', 'f', 'a', 's', 's', 's', 'd', 'f', 'a']
lst
['a', 's', 'd', 'f', 'a', 's', 's', 's', 'd', 'f', 'a']
#lst2+lst ## 거의 lst2는 일반적인 lst와 같은역할

- 기존리스트에서 추가로 frequency() 메소드가 존재함.

lst2.freq()
{'d': 2, 'a': 3, 's': 4, 'f': 2}

숙제

import datetime

(1) 아래의 코드를 관찰하고 datetime의 동작을 유추하라.

current_time = datetime.datetime.now().strftime('%y-%m-%d %X')
current_time
'23-06-16 09:35:44'

(2) 아래의 코드를 관찰하고 Time 클래스의 기능을 추정하라.

class Time:
    def time(self):
        return datetime.datetime.now().strftime('%y-%m-%d %X')
time_ins = Time()
time_ins.time()
'23-06-16 09:35:45'
time_ins.time()
'23-06-16 09:35:45'

(3) Time을 상속받아 Init 클래스를 만들고 __repr__을 조작하여 아래와 같이 인스턴스 생성시점을 출력하는 기능을 구현하라.

a = Init()
a
인스턴스생성시점: 23-06-16 10:27:19
b = Init()
a,b
(인스턴스생성시점: 23-06-16 10:27:19, 인스턴스생성시점: 23-06-16 10:27:28)

(4) 아래는 Dummy 클래스이다.

class Dummy:
    def __init__(self):
        self.a = 1
        self.b = 2 
    def dummy(self):
        pass
dummy = Dummy()
dir(dummy)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'a',
 'b',
 'dummy']

dummy 클래스에서 숨겨지지 않은 attribute는 ‘a’, ‘b’, ‘dummy’ 이다. 이를 출력하는 코드를 작성하라.

hint: 숨겨지지 않은 attribute는 _로 시작하지 않는다.

[l for l in dir(dummy) if l[0]!='_']
['a', 'b', 'dummy']

hint2 (5)번의 코드를 잘 관찰하세요..

(5) 아래클래스를 관찰하고 check()의 기능을 유추하라.

class Check:
    def ckeck(self):
        return [l for l in dir(self) if l[0]!='_']
ck = Check()
ck.ckeck()
['ckeck']

(6) tuple 클래스와 Check를 상속받아 아래와 같은 역할을 하는 새로운 Tuple 클래스를 만들라.

tpl = Tuple('asdfassdfsasdf')
tpl # 값과 함께 사용가능한 메소드가 함께 출력 
('a', 's', 'd', 'f', 'a', 's', 's', 'd', 'f', 's', 'a', 's', 'd', 'f')

methods=['ckeck', 'count', 'freq', 'index']
tpl.freq()
{'d': 3, 'a': 3, 's': 5, 'f': 3}