21. 상속과 다형성




1 클래스 상속


1-1 클래스 상속과 이름 공간의 관계

  • 상속의 이유
    • 코드의 재사용
    • 상속받은 자식 클래스는 상속을 해준 부모 클래스의 모든 기능을 그대로 사용
    • 자식 클래스는 필요한 기능만을 정의하거나 기존의 기능을 변경할 수 있음
In [1]:
class Person:
    def __init__(self, name, phone=None):
        self.name = name
        self.phone = phone
    def __str__(self):
        return '<Person %s %s>' % (self.name, self.phone)
In [2]:
class Employee(Person):                    # 괄호 안에 쓰여진 클래스는 슈퍼클래스를 의미한다.
    def __init__(self, name, phone, position, salary):
        Person.__init__(self, name, phone) # Person클래스의 생성자 호출
        self.position = position
        self.salary = salary
  • 이름 공간의 포함관계
    • 자식 클래스 > 부모 클래스

inheritance

In [3]:
p1 = Person('홍길동', 1498)
print p1.name
print p1

print

m1 = Employee('손창희', 5564, '대리', 200)
m2 = Employee('김기동', 8546, '과장', 300)
print m1.name, m1.position  # 슈퍼클래스와 서브클래스의 멤버를 하나씩 출력한다.
print m1
print m2.name, m2.position
print m2
홍길동
<Person 홍길동 1498>

손창희 대리
<Person 손창희 5564>
김기동 과장
<Person 김기동 8546>

inheritance

1-2 생성자 호출

  • 서브 클래스의 생성자는 슈퍼 클래스의 생성자를 자동으로 호출하지 않는다.
In [4]:
class Super:
    def __init__(self):
        print 'Super init called'

class Sub(Super):
    def __init__(self):
        print 'Sub init called'
    
s = Sub()
Sub init called
  • 서브 클래스의 생성자에서 슈퍼 클래스의 생성자를 명시적으로 호출해야 한다.
In [3]:
class Super:
    def __init__(self):
        print 'Super init called'
        
class Sub(Super):
    def __init__(self):
        Super.__init__(self)   # 명시적으로 슈퍼클래스의 생성자를 호출한다.
        print 'Sub init called'
    
s = Sub()
Super init called
Sub init called
  • 서브 클래스에 생성자가 정의되어 있지 않은 경우에는 슈퍼 클래스의 생성자가 호출된다.
In [66]:
class Super:
    def __init__(self):
        print 'Super init called'
        
class Sub(Super):
    pass

s = Sub()
Super init called

1-3 메쏘드의 대치 (메소드 오버라이드 - Override)

  • 서브 클래스에서 슈퍼 클래스에 정의된 메소드를 재정의하여 대치하는 기능
In [1]:
class Person:
    def __init__(self, name, phone=None):
        self.name = name
        self.phone = phone
    def __str__(self):
        return '<Person %s %s>' % (self.name, self.phone)
    
class Employee(Person):
    def __init__(self, name, phone, position, salary):
        Person.__init__(self, name, phone)
        self.position = position
        self.salary = salary
        
p1 = Person('gslee', 5284)
m1 = Employee('kslee', 5224, 'President', 500)

print p1
print m1
<Person gslee 5284>
<Person kslee 5224>
In [2]:
class Employee(Person):
    def __init__(self, name, phone, position, salary):
        Person.__init__(self, name, phone)
        self.position = position
        self.salary = salary
    def __str__(self):
        return '<Employee %s %s %s %s>' % (self.name, self.phone, self.position, self.salary)
    
p1 = Person('gslee', 5284)
m1 = Employee('kslee', 5224, 'President', 500)

print p1
print m1
<Person gslee 5284>
<Employee kslee 5224 President 500>

1-4 다형성(Polymorphism)

  • 상속 관계 내의 다른 클래스들의 인스턴스들이 같은 멤버 함수 호출에 대해 각각 다르게 반응하도록 하는 기능

    • 연산자 오버로딩도 다형성을 지원하는 중요한 기술
      • 예를 들어, a와 b의 객체 형에 따라 a + b의 + 연산자 행동 방식이 변경되는 것
  • 다형성의 장점

    • 적은 코딩으로 다양한 객체들에게 유사한 작업을 수행시킬 수 있음
    • 프로그램 작성 코드 량이 줄어든다.
    • 코드의 가독성을 높혀준다.
  • 파이썬에서 다형성의 장점

    • 형 선언이 없다는 점에서 파이썬에서는 다형성을 적용하기가 더욱 용이하다.
    • 실시간으로 객체의 형이 결정되므로 단 하나의 메소드에 의해 처리될 수 있는 객체의 종류에 제한이 없다.
      • 즉, 다른 언어보다 코드의 양이 더욱 줄어든다.
In [57]:
class Animal:
    def cry(self):
        print '...'
        
class Dog(Animal):
    def cry(self):
        print '멍멍'
        
class Duck(Animal):
    def cry(self):
        print '꽥꽥'
        
class Fish(Animal):
    pass

for each in (Dog(), Duck(), Fish()):
    each.cry()
멍멍
꽥꽥
...

2 내장 자료형과 클래스의 통일


  • 내장 자료형(list, dict, tuple, string)을 상속하여 사용자 클래스를 정의하는 것
    • 내장 자료형과 사용자 자료형의 차이를 없에고 통일된 관점으로 모든 객체를 다룰 수 있는 방안
  • 클래스 정의는 새로운 자료형의 정의임

2-1 리스트 서브 클래스 만들기

In [11]:
a = list()
print a
print dir(a)
[]
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
  • 아래 예제는 내장 자료형인 list를 상속하여 뺄셈 연산(-)을 추가함
In [51]:
class MyList(list):
    def __sub__(self, other):   # '-' 연산자 중복 함수 정의
        for x in other:
            if x in self:
                self.remove(x)     # 각 항목을 하나씩 삭제한다.
        return self

L = MyList([1, 2, 3, 'spam', 4, 5])
print L
print

L = L - ['spam', 4]
print L
[1, 2, 3, 'spam', 4, 5]

[1, 2, 3, 5]

1) Stack 클래스 정의 예

  • 슈퍼 클래스로 list 클래스를 지닌다.
  • 즉, list 클래스를 확장하여 Stack 클래스를 정의함
In [12]:
class Stack(list):  # 클래스 정의
    push = list.append
    
s = Stack()         # 인스턴스 생성

s.push(4)
s.push(5)
print s
print

s = Stack([1,2,3])
s.push(4)
s.push(5)
print s
print

print s.pop()       # 슈퍼 클래스인 리스트 클래스의 pop() 메소드 호출
print s.pop()
print s
[4, 5]

[1, 2, 3, 4, 5]

5
4
[1, 2, 3]

2) Queue 클래스 정의 예

  • 슈퍼 클래스로 역시 list를 지닌다.
  • 즉, list 클래스를 확장하여 Queue 클래스를 정의함
In [13]:
class Queue(list):
    enqueue = list.append
    def dequeue(self):
        return self.pop(0)
    
q = Queue()
q.enqueue(1)      # 데이터 추가
q.enqueue(2)
print q

print q.dequeue() # 데이터 꺼내기
print q.dequeue()
[1, 2]
1
2

2-2 사전 서브 클래스 만들기

In [6]:
a = dict()
print a
print dir(a)
{}
['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values', 'viewitems', 'viewkeys', 'viewvalues']
  • 아래 예제는 keys() 메소드를 정렬된 키값 리스트를 반환하도록 재정의한다.
In [56]:
class MyDict(dict):
    def keys(self):
        K = dict.keys(self) # 언바운드 메소드 호출 --> K = self.keys() 라고 호출하면 무한 재귀 호출
        K.sort()
        return K

d = MyDict({'one':1, 'two':2, 'three':3})
print d.keys()
print

d2 = {'one':1, 'two':2, 'three':3}
print d2.keys()
['one', 'three', 'two']

['three', 'two', 'one']

3 상속 관계에 있는 클래스들의 정보 획득


3-1 객체가 어떤 클래스에 속해 있는지 확인하기

  • 객체의 자료형 비교 방법 I (전통적 방법)
In [5]:
import types

print type(123) == types.IntType
print type(123) == type(0)
True
True
  • 객체의 자료형 비교 방법 II (새로운 방법)
    • isinstance() 내장 함수와 기본 객체 클래스 사용
In [10]:
print isinstance(123, int)
print int
 True
<type 'int'>
  • 서브 클래스의 인스턴스는 슈퍼 클래스의 인스턴스이기도 하다.
In [18]:
class A:
    pass

class B:
    def f(self):
        pass
    
class C(B):
    pass

def check(obj):
    print obj, '=>',
    if isinstance(obj, A):
        print 'A',
    if isinstance(obj, B):
        print 'B',
    if isinstance(obj, C):
        print 'C',
    print
    
a = A()
b = B()
c = C()

check(a)
check(b)
check(c)
<__main__.A instance at 0x10de34e60> => A
<__main__.B instance at 0x10de34e18> => B
<__main__.C instance at 0x10de34cf8> => B C

3-2 클래스 간의 상속 관계 알아내기

  • issubclass() 내장 함수 활용
In [20]:
class A:
    pass

class B:
    def f(self):
        pass
    
class C(B):
    pass

def check(obj):
    print obj, '=>',
    if issubclass(obj, A):
        print 'A',
    if issubclass(obj, B):
        print 'B',
    if issubclass(obj, C):
        print 'C',
    print
    
check(A)
check(B)
check(C)
__main__.A => A
__main__.B => B
__main__.C => B C

참고 문헌: 파이썬(열혈강의)(개정판 VER.2), 이강성, FreeLec, 2005년 8월 29일