티스토리 뷰

Python

Python, 클래스(Class) - 상속

hwangyoungjae 2016. 5. 9. 11:08
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

상속이란?

'상속'을 이용하면 부모클래스(super class)의 모든 속성(데이터,메서드)를 자식클래스(sub class)로 물려줄수 있다.

이렇게 함으로써 클래스에서는 그에 맞는 특화된 메서드와 데이터를 정의할수 있다그 결과 각 클래스마다 동일한 코드가 적성되는 것을 방지하고부모클래스에 공통된 속성을 두어 코드의 유지보수가 쉬워진다.

또한 부모클래스(super class)에 정의된 인터페이스만을 알고 호출함으로써 각 개별 클래스에 특화된 기능을 공통된 인터페이스로 접근할수 있게 된다.

 

아래 예제를 통해 상속을 알아보겠다.

기본이 되는 부모클래스는 person으로이름(name)과 전화번호(number)를 데이터멤버로 가지고 있으며데이터를 출력하기 위한 메서드(printpersondata(), printinfo())와 생성자메서드를 가지고 있다.

class person:

    'Super Class'

    def __init__(self,name,number):

        self.name=name

        self.number=number

 

    def printinfo(self):

        print("info(name: {0}, number: {1})".format(self.name,self.number))

 

    def printpersondata(self):

        print("info(name: {0}, number: {1})".format(self.name,self.number))

 

아래는 자식클래스의 예이다자식클래스 선언부의 헤더에 상속받을 부모클래스리스트를 괄호사이에 기입한다.

만약 1개 이상 다중상속 받는경우 ','로 구분하여 기입한다.

class student(person):

    'Sub Class'

    def __init__(self,name,number,subject,studentID):

        self.name=name

        self.number=number

        self.subject=subject

        self.studentID=studentID

 

실행

>>> p = person("Derick","012-345-6789")

>>> s = student("Marry","987-654-3210","Computer Science","990999")

>>> p.__dict__ #person인스턴스객체

{'name': 'Derick', 'number': '012-345-6789'}

>>> s.__dict__ #student인스턴스객체

{'studentID': '990999', 'subject': 'Computer Science', 'name': 'Marry', 'number': '987-654-3210'}

클래스의 정보는 내무적으로 '__dict__'라는 이름의 사전객체로 관리된다.

 

>클래스간의 관계확인

상속관계인 두 클래스간의 관계를 확인하기 위하여 issubclass()내장함수를 이용할수 있다.

사용 예 : issubclass(자식클래스,부모클래스)

앞에서 예제로 든 person,student클래스를 사용하여 확인을 해보겠다.

>>> issubclass(student,person) #student person의 자식클래스인지 확인

True

>>> issubclass(person,student) #person student의 자식클래스인지 확인

False

>>> issubclass(person,person) #자기자신은 항상 True를 반환

True

>>> issubclass(person,object) #모든 클래스는 object를 암묵적으로 상속받음

True

 

※ __bases__

어떤 클래스의 부모클래스를 알기위해서는 '__bases__'속성을 사용하여 알아낼수 있다이 속성은 직계의 부모클래스를 튜플로 반환한다.

>>> person.__bases__

(<class 'object'>,)

>>> student.__bases__

(<class '__main__.person'>,)

 

>부모클래스의 생성자호출

앞의 student클래스의 생성자를 보면, person클래스의 멤버변수를 초기화하는 부분이 있다이는 이미 person클래스에 정의되어있는것으로 중복된 코드이다이 부분을 부모클래스인 person의 생성자를 호출하는것으로 수정하면 아래와 같다.

class student(person):

    'Sub Class'

    def __init__(self,name,number,subject,studentID):

        person.__init__(self,name,number)

        self.subject=subject

        self.studentID=studentID

 

def printstudentdata(self): #새로운 메서드를 추가

        print("student(subject:{0},student ID:{1})".format(self.subject,self.studentID))

student클래스의 인스턴스 객체인 s를 통해 메서드를 호출하는 경우 person클래스로부터 상속받은 메서드와 student클래스에 추가된 메서드 모두 동일한 방법으로 호출한다.

>>> s=student("Derick","012-345-6789","Computer","990999")

>>> s.printpersondata() #person으로부터 상속받은 메서드호출

person(name: Derick, number: 012-345-6789)

>>> s.printstudentdata() #student에 추가된 메서드호출

student(subject:Computer,student ID:990999)

>>> dir(s) #상속받은 메서드와 추가된 메서드가 모두 접근 가능

[생략... , 'name', 'number', 'printinfo', 'printpersondata', 'printstudentdata', 'studentID', 'subject']

 

>메서드 재정의하기

부모클래스(Super Class)로부터 상속받은 메서드중 개발자가 원하는 대로 작동하지 않는것이 있을수 있다이러한 경우 부모클래스(Super Class)를 직접수정하여 해결할수도 있지만이 부모클래스(Super Class)가 다른 여러 자식클래스(Sub Class)의 기반이 되는경우즉 다른클래스에 대한 의존성이 너무 큰 경우 모든 자식클래스(Sub Class)를 고려하여 수정하는것은 매우 어려운일이다.

이를 위하여 파이썬에서는 부모클래스(Super Class)의 메서드에 대하여 자식클래스(Sub Class)에서 재정의하는 기능을 제공한다.

메서드재정의는 실제로는 자식클래스(Sub Class)의 메서드이름이 우선적으로 찾게 되기 때문이다.

 

아래는 person클래스의 printinfo()메서드를 student클래스에서 재정의하는 예제이다.

자식클래스인 student클래스의 정의부분에 재정할 person클래스의 메서드를 원하는 용도로 작성하면 된다.

class student(person):

    'Sub Class'

    def __init__(self,name,number,subject,studentID):

        person.__init__(self,name,number)

        self.subject=subject

        self.studentID=studentID

 

    def printstudentdata(self):

        print("student(subject:{0},student ID:{1})".format(self.subject,self.studentID))

 

    def printinfo(self):

        print("info(name:{0}, number:{1}".format(self.name,self.number))

        print("info(subject:{0}, studentID:{1}".format(self.subject,self.studentID))

 

실행

>>> s=student("Derick","012-345-6789","Computer","990999")

>>> s.printinfo() #재정의된 메서드를 호출

info(name:Derick, number:012-345-6789

info(subject:Computer, studentID:990999

이렇게 부모클래스에서 정의된 메서드를 자식클래스에서 새롭게 재정의하는것을 메서드재정의(Method Overriding)이라고 한다.

C++와 같은 언어에서는 메서드 재정의하기 위하여 부모클래스에 정의된 메서드와 자식클래스의 메서드 이름,매개변수,리턴값이 완전히 일치해야 하지만파이썬에서는 단순히 두 메서드의 이름만 같으면 된다.

그 이유는 이름공간의 속성정보가 내부적으로 사전형태로 관리되기 때문이다즉 키값인 메서드의 이름만 같으면 부모클래스의 메서드 대신에 자식클래스의 메서드를 호출한다이런 메서드 재정의를 통해 부모클래스의 동일한 인터페이스를 호출함으로써 자식 클래스의 특화된 기능을 이용할수 있다.

 

아래는 person, student인스턴스객체를 personlist리스트에 입력후 각각에 대해 동일한 인터페이스인 printinfo()를 호출하는 예제이다.

>>> p = person('Tim','010-123-456')

>>> s = student('Derick','010-234-567','Com','9909')

>>> personlist=[p,s]

>>> for item in personlist:

             item.printinfo() #동일 인터페이스인 printinfo()호출

 

info(name: Tim, number: 010-123-456) #p인스턴스의 printinfo()호출

info(name:Derick, number:010-234-567 #s인스턴스의 printinfo()호출

info(subject:Com, studentID:9909

 

>메서드 확장하기

상속받은 메서드중에 기능은 그럭저럭 만족하는데실제 사용하기 위해서는 일부 추가적인 기능이 필요한 경우가 있다이러한 경우 부모클래스 메서드는 그대로 이용하면서 자식클래스 메서드에서 필요한 기능만 정의할수 있다.

 

위의 메서드재정의 예제의 student클래스에서 메서드재정의를 한 printinfo()의 경우 사실 person.printinfo()의 확장판이라고 할수 있다.

우선 person인스턴스객체의 데이터를 출력하고그 이후 student 인스턴스 객체의 데이터를 출력하기 때문이다.

이미 person클래스의 printinfo()가 존재하기 때문에 student클래스에서 다시 person의 정보를 출력하는 메서드를 작성하는것은 중복된 코드를 또다시 작성하는 일이나 마찬가지이다.

따라서 이번에는 person클래스의 printinfo()를 이용하여 student.printinfo()메서드를 정의하도록 하겠다.

student클래스의 printinfo()메서드를 재정의할때명시적으로 person.printinfo()를 호출하면 된다.

이렇게 함으로써 부모클래스의 메서드는 그대로 이용하여 필요로 하는 새로운 기능을 최소한의 자겅ㅂ으로 추가할수 있다.

class student(person):

    'Sub Class'

    def __init__(self,name,number,subject,studentID):

        person.__init__(self,name,number)

        self.subject=subject

        self.studentID=studentID

 

    def printstudentdata(self):

        print("student(subject:{0},student ID:{1})".format(self.subject,self.studentID))

 

    def printinfo(self):

        person.printinfo(self) #명시적으로 person클래스의 printinfo()를 호출

        print("info(subject:{0}, studentID:{1}".format(self.subject,self.studentID))

 

>클래스상속과 이름공간

클래스객체와 인스턴스객체의 이름공간에서는

[인스턴스객체영역 -> 클래스객체영역 -> 전역영역이와 같은 규칙으로 멤버변수나 함수의 이름을 찾는다고 하였다.

여기서 클래스간의 상속관계가 포함되면 아래와 같이 규칙이 확장된다.

[인스턴스객체영역 -> 클래스객체간 상속을 통한영역(자식클래스영역 -> 부모클래스영역) -> 전역영역]

이러한 규칙을 "상속관계 검색의원칙(Princeples of the inheritance search)"이라고 한다.

클래스의 상속관계에서 속성(멤버데이터메서드)의 이름을 검색할때 개별적으로 독립된 영역을 가지고 있는 클래스간의 상속관계 순서로 이름을 찾는다또한 자식클래스가 상속받은 멤버 메서드에 대해서 재정의를 하지 않거나 멤버데이터에 새로운값을 할당하지 않은 경우자식클래스 내부의 이름공간에 그 데이터와 메서드를 위한 저장공간을 생성하는 대신에 단순히 부모클래스의 이름공간에 존재하는 데이터와 메서드를 참조한다.

이렇게 하는 이유는 중복된 데이터와 메서드를 최소화하여 메모리사용의 효율성을 높이기 위해서이다.

 

아래의 간단한 예를 통해 자세히 보도록 하겠다.

부모클래스'SUPER'는 멤버변수 'x','printX()'메서드를 가지고 있으며'SUPER'클래스를 상속받은 자식클래스 'SUB'는 멤버변수 'y','printY()'메서드를 가지고 있다또한 자식클래스를 통해 생성된 인스턴스객체 's'는 멤버변수 'a'를 인스턴스객체 이름공간에 가지고 있다.

그러므로 각 객체의 '__dict__'정보를 출력해본 결과'SUPER'객체에는 'printX','x''SUB'객체에는 'printY','y', , 's'객체에는 'a'가 존재하는 것을 확인할수 있다.

>>> class SUPER#부모클래스

             x=10

             def printX(self):

                           print(self.x)

 

>>> class SUB(SUPER): #자식클래스

             y=20

             def printY(self):

                           print(self.y)

 

>>> s = SUB()

>>> s.a = 30 #인스턴스객체's'에 멤버변수a를 정의

>>> SUPER.__dict__

dict_proxy({'__module__': '__main__', 'printX': <function printX at 0x01049B28>, '__dict__': <attribute '__dict__' of 'SUPER' objects>, 'x': 10, '__weakref__': <attribute '__weakref__' of 'SUPER' objects>, '__doc__': None})

>>> SUB.__dict__

dict_proxy({'y': 20, 'printY': <function printY at 0x01049A98>, '__module__': '__main__', '__doc__': None})

>>> s.__dict__

{'a': 30}

>>> 

그럼 인스턴스객체가 이러한 상속관계에서 속성(메서드,멤버변수)의 이름을 찾는 방법을 알아보겠다

 

's'라는 인스턴스객체를 통해 s.printX()를 호출하면's'객체의 이름공간에서 'printX'가 존재하는지 확인한다만약 존재하지 않으면 인스턴스객체를 생성한 클래스객체인 'SUB'객체의 이름공간을 확인하고 이번에도 존재하지 않으면 부모클래스인 'SUPER'객체의 이름공간을 검색한다이러한 검색이 끝나는 조건은 동일한 이름이 가장 먼저 나올때 까지이다이번 예제에서 'printX'라는 이름은 'SUPER'객체에서 정의가 되어 있는것을 찾을수 있다만약 이러한 순서로 검색을 하여도 속성의 이름을 찾을수 없는 경우 'AttributeError'에러를 반환한다.

 

아래는 's'객체에서 'SUPER'객체의 x변수의 값을 변경한후 이름공간의 변화에 대해서 확인하였다.

>>> class SUPER:

             x=10

             def printX(self):

                           print(self.x)

 

>>> class SUB(SUPER):

             y=20

             def printY(self):

                           print(self.y)

 

>>> s = SUB()

>>> s.__dict__

{}

>>> s.x #SUPER객체의 x를 참조하여 값을 가져옴

10

>>> s.x = 30 #x값을 재정의함으로써 s객체의 이름공간에 별도의 x변수를 생성함

>>> s.__dict__

{'x': 30}

>>> s.x

30

 

>다중상속

다중상속이란 2개 이상의 클래스를 상속받는 경우를 말한다그 결과 두 클래스의 모든 속성(변수와 메서드)를 물려받게 된다.

아래의 예를 보면서 확인해보자

class Tiger:

    def jump(self):

        print('호랑이처럼 멀리 점프하기')

 

class Lion:

    def bite(self):

        print('사자처럼 한입에 꿀꺽하기')

 

class Liger(Tiger,Lion): #다중상속

    def play(self):

        print('라이거만의 사육사와 재미있게 놀기')

 

실행

>>> l=Liger()

>>> l.jump()

호랑이처럼 멀리 점프하기

>>> l.bite()

사자처럼 한입에 꿀꺽하기

>>> l.play()

라이거만의 사육사와 재미있게 놀기

 

다중 상속받는 경우자식클래스 선언부 헤더에 상속받을 클래스들을 순차적으로 나열한다.

여기서 인스턴스객체나 자식클래스가 속성(멤버변수,메서드)의 이름을 상속관계에 따라서 검색할때다중상속된 클래스의 나열 순서가 검색결과에 영향을 준다.

아래의 코드는 Tiger와 Lion클래스에 모두 cry()메서드가 존재하고두 클래스를 상속받는 Liger클래스를 작성하였다.

class Tiger:

    def jump(self):

        print('호랑이처럼 멀리 점프하기')

    def cry(self):

        print('호랑이 : 어흥~')

 

class Lion:

    def bite(self):

        print('사자처럼 한입에 꿀꺽하기')

    def cry(self):

        print('사자 : 으르렁~')

 

class Liger(Tiger,Lion):

    def play(self):

        print('라이거만의 사육사와 재미있게 놀기')

 

실행

>>> l=Liger()

>>> l.cry()

호랑이 : 어흥~

 

실행결과를 보니 tiger cry()가 호출된것을 알수 있다그 이유는 Liger클래스 선언부에 상속받는 순서가 Tiger클래스부터 시작되었기 때문에 우선적으로 Tiger클래스의 이름공간에서 cry()를 찾기 때문이다.

 

다양한 상속구조에서 메서드의 이름을 찾는 순서는 '__mro__'에 튜플로 정의되어 있다.(MRO 'Method Resolution Order'의 약자이다.) 아래의 예제에서 Liger, Tiger, Lion, object순서로 검색한다상속하지 않은 object클래스는 파이썬3이후 모든 클래스에서 암묵적으로 상속받고 있다.

>>> Liger.__mro__

(<class '__main__.Liger'>, <class '__main__.Tiger'>, <class '__main__.Lion'>, <class 'object'>)

 

>super()를 이용한 상위클래스 메서드호출

아래의 코드는 Animal클래스에 Tiger,Lion의 공통된 속성을 정의하고 Tiger,Lion클래스는 Animal을 상속받아 개별클래스의 특화된 점을 정의한 후 Liger클래스는 Tiger,Lion을 다중상속받는 예제이다.

이러한 구조에서 각 하위 클래스의 생성자메서드에서는 부모클래스의 생성자메서드를 명시적으로 호출하도록 하였다.

class Animal:

    def __init__(self):

        print('Animal 생성자호출')

 

class Tiger(Animal):

    def __init__(self):

        Animal.__init__(self)

        print('Tiger 생성자호출')

 

class Lion(Animal):

    def __init__(self):

        Animal.__init__(self)

        print('Lion 생성자호출')

 

class Liger(Tiger,Lion):

    def __init__(self):

        Tiger.__init__(self)

        Lion.__init__(self)

        print('Liger 생성자호출')

 

실행

>>> l=Liger()

Animal 생성자호출

Tiger 생성자호출

Animal 생성자호출

Lion 생성자호출

Liger 생성자호출

이렇게 작성한 클래스는 위와같이 Animal클래스의 생성자가 두번 호출되는 문제가 있다그 이유는 Tiger클래스의 생성자에서 Animal클래스의 생성자가 호출되었음에도 Lion클래스에서 또 다시 Animal클래스의 생성자를 호출하기 때문이다이러한 문제는 위와 같이 다이아몬트형태로 상속되는 경우 발생한다.

 

이러한 문제를 해결하기 위해서 super()내장함수를 사용할수 있다.

이 함수의 반환값은 부모클래스의 객체를 반환하게 되며자바의 super, c# base키워드와 유사하다.

일반적으로 자식클래스에서 'super().메서드이름(인자)'형태로 부모클래스의 메서드를 호출하기 때문에명시적으로 부모클래스이름을 쓰는것보다 코드의 유지보수가 쉬워진다.

또한 다른 언어에서 찾아볼수 없는 동적 실행환경에서 클래스간에 상호동작으로 다중상속 문제를 해결할수 있다예를 들어 Liger클래스의 부모클래스인 Tiger Lion클래스가 모두 Animal클래스의 자식인것을 파이썬 인터프리터가 파악하여 Animal클래스의 생성자 메서드가 2번 호출되는것을 피할수 있다. super()함수를 이용하여 Animal클래스의 생성자가 한번만 호출되도록 작성한 코드는 아래와 같다.

class Animal:

    def __init__(self):

        print('Animal 생성자호출')

 

class Tiger(Animal):

    def __init__(self):

        super().__init__() #부모클래스의 생성자메서드 호출

        print('Tiger 생성자호출')

 

class Lion(Animal):

    def __init__(self):

        super().__init__() #부모클래스의 생성자메서드 호출

        print('Lion 생성자호출')

 

class Liger(Tiger,Lion):

    def __init__(self):

        super().__init__() #부모클래스의 생성자메서드 호출

        print('Liger 생성자호출')

 

실행

>>> l=Liger()

Animal 생성자호출

Lion 생성자호출

Tiger 생성자호출

Liger 생성자호출

각 클래스에서는 부모의 생성자를 호출하기 위하여 단일상속이나 다중상속을 고려하지 않고 단지 'super().__init__()'만 호출하면 된다.

이렇게 작성하여 Animal생성자는 한번만 호출되는 것을 알수 있다이러한 방법은 생성자뿐만 아니라 일반메서드에서도 마찬가지로 적용가능하다.

 

Liger클래스는 'super().__init__()'으로 모든 부모클래스의 생성자를 호출하게 되며이때 생성자호출순서는 MRO의 역순으로 상위클래스부터 호출된다.

>>> Liger.__mro__

(<class '__main__.Liger'>, <class '__main__.Tiger'>, <class '__main__.Lion'>, <class '__main__.Animal'>, <class 'object'>)

 

※ Liger super()메서드를 호출하는 경우아래와 같이 명시적으로 클래스의 이름과 인스턴스객체를 인자로 전달하여 호출할수 있다.

super(Liger,self).__init__()

 

 

참조 : 빠르게 활용하는 파이썬프로그래밍

'Python' 카테고리의 다른 글

Python, pickle  (0) 2016.05.17
Python, 모듈 사용하기  (0) 2016.05.17
Python, 클래스(Class) - 연산자중복정의  (0) 2016.05.09
Python, 클래스(Class)  (0) 2016.05.09
Python, 정적메소드,클래스메소드  (0) 2016.05.09
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
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
글 보관함