# 상속과 오버라이딩

 

1. 상속

 

상속은 사전적 정의에 따르면 부모나 친족적 관계에 있는 사람의 유산을 물려 받는 제도입니다. 클래스에서의 상속은 부모의 유산을 자식이 물려 받듯이 부모 클래스의 멤버와 메소드를 자식 클래스가 물려받을 수 있습니다. 이런 경우는 흔히 있습니다. 예를 들어 라이브러리를 제작할 때 특정 자료구조를 만들어서 모든 클래스가 이 자료구조를 이용하게 하고 싶다면 클래스의 상속을 이용하게 하면 됩니다.  추가적으로 파이썬에서 기본 제공되는 자료형 또한 클래스이기 때문에 자신만의 독특한 자료형을 만들 수 도 있겠죠.

 

상속을 이해하고자 할 때 가장 쉬운방법은 기존의 클래스를 이용하되, 내가 원하는 멤버와 메소드를 추가하는 것으로 이해 하는 것입니다.

 

클래스의 상속

먼저 총이라는 간단한 클래스가 있다고 가정해 보겠습니다. 클래스라는 것은 유형, 무형으로 존재하는 어떤 것을 코드로 나타내는 것입니다.  그러면 총(Rifle)이라는 객체는 파괴력, 탄창, 현재 남은 총알 등의 속성을 가지고 있을 테고, 기능으로는 장전이나 발사가 있을 수 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Rifle:
    def __init__(self,Mag, atk):
        self.__Mag = Mag
        self.__leftMag = Mag
        self.__atk = atk
    
    @property
    def Mag(self):
         return self.__Mag
    
    @property
    def leftMag(self):
        return self.__leftMag
    
    @leftMag.setter
    def leftMag(self, num):
        self.__leftMag = num
    
    @property
    def atk(self):
        return self.__atk
    
    @atk.setter
    def atk(self,num):
        self.__atk = num
        
    def Reload(self):
        self.__leftMag = self.__Mag
        
    def Shoot(self):
        if self.__leftMag:
            self.__leftMag -= 1
        else:
            print("장전하세요")
cs

 

이 클래스는 Rifle입니다. 제가 앞서 언급한 대로 생성자에 탄창, 남은 총알, 파괴력등의 요소를 만들었습니다. 또한 상속을위해 각 클래스멤버의 property와 setter를 작성하여 직접 멤버변수에는 접근하지 못하지만, 메소드를 통하여 가능하게 구현하였습니다. 탄창(Mag)은 처음 인스턴스 생성 이후 오염을 방지하기위해 setter에서 제외했습니다. Rifle클래스의 메소드로는 총이 실제로 가지고있는 기능인 발사(Shoot), 장전(Reload) 등을 포함 시켰습니다. 같은 클래스 안에서는 property나 setter를 이용하여 멤버에 접근할 필요가 없기때문에 직접접근하는 방식으로 구현하였습니다.(장전과 발사에 있는 self.__leftMag)

 

만약 총이라는 클래스를 상속받아 저격총이라는 클래스를 만들고자 하면 총이 가지고 있는 속성은 그대로 유지할 것입니다. 하지만 총이라는 클래스에는 망원경이라는 요소가 없기 때문에 속성을 하나 추가하고 망원경 조준이라는 기능을 추가해야겠죠.  그런 의미에서 멤버 변수에 Zoom을 추가하였고 메소드로는 increaseAtk을 넣어 Zoom과 파괴력을 곱해 다시 파괴력에 저장하는 코드를 작성하였습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ScopeRifle(Rifle):
    def __init__(self,Mag,atk):
        super().__init__(Mag,atk)
        self.__Zoom = 1
    
    @property
    def Zoom(self):
        return self.__Zoom
    
    @Zoom.setter
    def Zoom(self,num):
        self.__Zoom *= num
    
    def increaseAtk(self):
        self.atk *= self.__Zoom
cs

 

__init__을 보면 super().__init__이라는 구문이 있습니다. 이는 부모클래스의 생성자 때문에 필요한 구문입니다. 생성자 규칙에 따라 부모클래스는 탄창과 파괴력이라는 멤버를 필요로 합니다. 이 멤버를 채워주지 않으면 에러가 발생합니다. 

아래의 코드를 실행시키고 각각의 메소드를 테스트 해보겠습니다.

 

1
2
rifle = Rifle(5,10)
sniper = ScopeRifle(5,10)
cs

 

 

Rifle 클래스에 정의되어 있는 내용은 잘 동작하는 것을 확인하였습니다.  rifle을 상속받은 sniper도 잘 동작하는 것을 확인 할 수 있습니다. 

이번에는 sniper클래스의 고유 기능을 테스트 해보도록 하겠습니다.

 

 

제가 의도한 대로 잘 동작하고 있습니다.  atk는 부모클래스의 멤버이면서 property지만 setter를 같이 작성했기 때문에 sniper에서 접근하고 같을 변경시킬 수 있었습니다. public 하게 구현하면 사실 이런 고생은 안해도 됩니다. 하지만 객체지향 프로그래밍에서는 getter setter를 제외하고는 클래스 멤버에대한 외부 클래스의 접근을 금하고 있기때문에 property 설정을 꼭 하셔야 합니다.

 

 


 

 

2. 오버라이딩

 

총과 저격총 사이에는 명확하지 않지만 가끔씩  상속받은 클래스가 부모의 기능을 똑같이 가지고 있지만 구현 방법이 다를 때가 존재합니다. 이런 경우에는 같은 이름의 메소드를 작성하고 다른 기능을 수행하게 해야합니다. 예를 들어 산탄총의 경우를 생각해보겠습니다. 일반 총은 발사당 1개의 총알이 소비되지만 산탄총은 3발이 나간다고 칩시다. 산탄총도Shoot이라는 메소드가 당연히 필요하지만 부모 클래스의 Shoot과 다르게 동작해야 되겠죠? 파이썬에서는 부모 클래스의 메소드와 같은 이름의 메소드를 자식 클래스에 작성하여 부모 클래스의 메소드를 덮어버리는 방법이 있습니다. 이를 우리는 오버라이딩이라고 합니다.

 

1
2
3
4
5
6
7
class ShotGun(Rifle):
    def __init__(self,Mag,atk):
        super().__init__(Mag,atk)
    
    def Shoot(self):
        if self.leftMag:
            self.leftMag -= 3
cs

 

위의 코드는 Rifle클래스를 상속받아 만든 ShotGun클래스입니다.  ShotGun은 Rifle과 모든게 같지만 발사당 3개의 총알이 소비된다고 했었죠? 그 내용을 Shoot이라는 메소드를 정의하고 그 안에 구현하면 됩니다.

부모 클래스에 Shoot이라는 메소드가 있다고 하더라도 오버라이딩을 통해 이를 덮어 쓸 수 있습니다. 실제로 이 코드가 잘 작동하는지 확인 해보겠습니다.

 

 

코드가 정상적으로 작동하는 것을 확인 할 수 있습니다. 상속을 받을 때는 이런 식으로 기능을 고려해서 메소드를 다시 설계 해야할 필요가 있는 경우도 있습니다.

# 클래스

클래스란(Class),속성이나 기능을 공유하는 유사한 성질의 객체들을 하나로 그룹화한 것입니다. 클래스 내부에는 해당 클래스의 객체를 위한 클래스 멤버(Class Member)와 기능(method)의 구현 등 세부사항을 기술합니다.
클래스에 관하여 설명하기 전에 인스턴스(instance)에 관하여 알아보도록 하겠습니다.

 

 


 

 

1. 인스턴스

 

인스턴스란 클래스에 의해 만들어진 객체를 칭하는 말입니다. 예를 들어 클래스를 붕어빵 틀이라고 한다면, 인스턴스는 붕어빵이 되는 것입니다. 즉 클래스는 인스턴스를 만드는 하나의 틀입니다. 앞으로 클래스를 이용하여 인스턴스를 계속적으로 만들어 낼 수 있습니다.

캔디 클래스와 그 인스터스 캔디

 


 

 

2. 클래스 선언방법

 

클래스는 class 명령어 뒤에 클래스 이름을기술하여 선언합니다. 클래스 구문 하위에 인덴트(tab)을 넣어 데이터 구조나 함수등을 명시하면 됩니다. 예시를 보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person:
    # class Person의 멤버변수
    name = "홍길동"
    number = "01077499954"
    age = "20"
 
    # class Person의 메소드
    def info(self):
        print("제 이름은 " + self.name + "입니다.")
        print("제 번호는 " + self.number + "입니다.")
        print("제 나이는 " + self.age + "세 입니다.")
 
if __name__ == "__main__":# main namespace를 의미합니다.
    customer = Person()#Person의 객체 customer 생성
    customer.info()#info 함수 호출
 
#결과값
제 이름은 홍길동입니다.
제 번호는 01077499954입니다.
제 나이는 20세 입니다.
cs

위의 예시처럼 클래스를 구성하시면 됩니다. 클래스내의 name space에 사용할 변수를 만들어 주시면 되고 함수를 추가하여 사용하시면 됩니다. 그런데 함수에 뜬금 없는 self란 친구가 들어왔습니다. self는 현재 인스턴스 객체를 가리키는 기능을 하는 친구입니다. 쉽게 말하자면 사전적 의미대로 자기자신을 이야기 하 는 것입니다. 만약 customer라는 인스턴스를 만들었다면 self는 customer가 되는겁니다.(클래스내에 함수가 선언 될 때에는 첫번째 인자로 무조건 self가 와야합니다. 객체 사용유무와 상관없음)

 

객체에 클래스를 할당하고 싶으면 위 코드처럼 객체를 하나 만들어 클래스를 대입하면 됩니다.(customer = Person())

클래스에 있는 데이터에 접근하고자 할 때는 "."을 사용하면 됩니다. customer의 이름을 변경하고 싶을때에는 customer.name으로 접근해서 변경하면 되는겁니다. 함수의 이용도 마찬가지입니다. info 함수를 사용하고 싶을 때에는 customer.info로 사용할 수 있습니다. 하지만 이러한 접근방식은 OOP(Object Oriented Programming)의 캡슐화의 근간을 훼손하는 행위로서 getter나 setter를 이용하는것이 현명합니다.

 

파이썬에서는 모든 클래스가 public으로 작성되기 때문에 getter메소드나 setter 메소드가 없습니다. 대신 파이썬에서는 사용자가 속성에직접 접근하는 것을 막기 위해getter또는setter메서드 대신에프로퍼티(property)를 사용합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Person:
    # class Person의 멤버변수
    # __(double underscore)
    # __는 클래스외부에서 클래스 멤버에 접근하지 못하게 하기위함
    __name = "홍길동"
    __number = "01077499954"
    __age = "20"
 
    @property
    def name(self):
        return self.__name
 
    @name.setter
    def name(self, newName):
        self.__name = newName
 
    # class Person의 메소드
    def info(self):
        print("제 이름은 " + self.__name + "입니다.")
        print("제 번호는 " + self.__number + "입니다.")
        print("제 나이는 " + self.__age + "세 입니다.")
 
if __name__ == "__main__":# main namespace를 의미합니다.
    customer = Person()#Person의 객체 customer 생성
    customer.info()#info 함수 호출
    print(customer.name)
    customer.name="이태일"
    print(customer.name)
 
 # 결과 값
제 이름은 홍길동입니다.
제 번호는 01077499954입니다.
제 나이는 20세 입니다.
홍길동
이태일
cs

 

먼저 private하게 작성할 멤버변수의 이름앞에 더블 언더스코어(__, double underscore)를 붙여줍니다.그리고 아까와는 다르게 name 메소드위에 '**@'가 붙은 것을 볼 수 있습니다. 이를데코레이터(decorator)라고 하는데, 말그대로 뭔가를꾸며** 준다는 것을 의미합니다.

같은 name 이라는이름을 갖는 메소드이지만 데코레이터에 의해 서로 다른 역할을 합니다.클래스를 완성하고 __main__에서 테스트를 해보았습니다. 결과가 잘 나온 것을 볼 수 있죠? 그런데 여기에는 주의할 점이 있습니다.name은 customer 클래스의 메소드인데도 불구하고 호출 시 멤버변수처럼 접근합니다. 원래대로라면 customer.name()이 될텐데 customer.name 형식으로 호출하고 있는걸 볼 수 있죠? 코드의 생김새는 객체의 멤버에 직접 접근하는듯이 사용하지만**실제로는 메소드 호출을 통해 변수에 접근하게 됩니다.**

 


 

3. Initializing

 

자 클래스의 기본을 배웠으니 다음 예제로 넘어가겠습니다. Monster라는 클래스를 만들건데요 Monster는 체력,공격력,방어력을 가지는 클래스입니다. 공격이 가능하며 공격을 받았을 경우에 체력이 상대방의 공격력-자신의 방어력 만큼 감소한다고 칩시다. 이를 구현한 코드는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Monster:
    def __init__(self,hp,atk,dfn):
        self.hp = hp
        self.atk = atk
        self.dfn = dfn
 
    def attack(self,target):
        target.ReduceHP(self.atk)
 
    def ReduceHP(self,atk) :
        self.hp = self.hp - (atk-self.dfn)
 
if __name__ == "__main__":
    m1 = Monster(100,10,3)
    m2 = Monster(100,10,3)
    m1.attack(m2)
    print(m2.hp)
 
#결과값
93
cs

__init__이라는 메소드에 주목해주세요. __init__은 파이썬에서 기본적으로 제공하는 클래스 생성자입니다. 아까 전에는 클래스 멤버변수를 만들고 그 값을 클래스에 직접넣어 주는 형식으로 구현했는데요, 생성자를 쓰면 클래스의 인스턴스가 생성될 때마다 다른 멤버값을 갖도록 구현 할 수 있습니다.

Monster 클래스는 제가 상상속으로 생각해 낸 괴물에 대해 코드로 구현한 것입니다. 먼저 게임에서의 몬스터는 체력과 공격력 그리고 방어력을 갖고 있죠? 이를 각각 클래스 멤버로 할당하고 각 몬스터 마다 값을 다르게 하기 위하여 생성자를 이용하였습니다.

또한 공격 기능 , 체력 감소 기능을 attack과 ReduceHP라는 메소드를 통해 구현해 보았습니다. 이 메소드를 이용하면 상대 몬스터에게 공격을 가하거나, 공격을 받았을 시에 체력이 감소되는 모습을 확인 할 수 있습니다.

그리고 객체 2개를 만들어 공격하게끔 해 보았습니다. 그 후 피 공격 객체의 체력 값을 확인해 보았더니 상대방의 공격력-자신의 방어력 만큼의 체력이 감소하였습니다. 잘 동작하는것 같네요.

+ Recent posts