파이썬도 객체지향 프로그래밍이 가능

상속, 다중상속, 다형성, 정보 은닉, 추상화, 부모클래스, 자식클래스, 클래스, 인스턴스


클래스 선언

- 클래스의 구성: 데이터, 메서드 (반드시 필요한 것은 아님)

- 클래스 객체: 클래스 선언과 동시에 클래스 객체가 생성, 클래스 선언을 통해 새로운 이름 공간이 생성 됨


1. 데이터와 메소드가 없는 간단한 클래스

class MyClass:

    '''

    simple class

    '''

    pass

print('dir():', dir())

print('type(MyClass):', type(MyClass))

dir(): ['MyClass', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']

type(MyClass): <class 'type'>


2. 멤버 변수와 멤버 메서드를 가지고 있는 클래스

- 클래스를 정의하면 클래스 객체가 생성되고 독립적인 이름공간이 만들어짐

- 인스턴스 객체 생성은 클래스 이름을 사용하여 함수를 호출하는 형태를 갖게되며, 클래스와 동일하게 인스턴스 객체가 생성되고 독립적인 이름공간이 생성

class Person:

    Name = "Default Name"

    def Print(self):

        print("My Name is {0}".format(self.Name))


p1 = Person()   # 인스턴스 객체 생성 

p1.Print()

My Name is Default Name



3. 인스턴스 객체의 이름공간에 변경된 데이터를 저장

- 인스턴스 객체가 변경되기 전까지는 클래스 객체와 동일한 메서드를 가리킴

- 인스턴스 객체의 데이터가 변경되면, 클래스 객체의 데이터와 구분하기 위하여 인스턴스 객체 이름 공간에 변경된 데이터를 저장

- 아직 변경되지 않은 데이터와 메서드는 클래스 객체와 공유

p1.Name = "New Name"

p1.Print()

My Name is New Name


4. 속성 접근자

- 클래스 객체와 인스턴스 객체 모두 각 멤버 변수와 멤버 메서드에 접근하기 위해서는 속성 접근자를 사용

- 객체이름.멤버메서드, 객체이름.멤버 변수

- 기본적으로 접근 권한은 public, 외부에서 모든 클래스의 내용을 쉽게 확인/변경이 가능


5. self

- 현재 인스턴스 객체를 가리키는 것(예약어로 지정되어 있지는 않음)

- 명시적으로 메서드의 첫 인자는 인스턴스 객체가 됨 (단, 클래스 공간의 정적 메서드나 클래스 메서드는 제외)

- self가 아닌 다른 표현이 가능하지만 관용적으로 사용


6. 바운드 메서드, 언바운드 메서드

p1.Print()          # 바운드 메서드 

Person.Print(p1)    # 언바운드 메소드 

My Name is New Name

My Name is New Name

1) 바운드 메서드

- 메서드 호출시 암묵적으로 첫 인자로 인스턴스 객체를 넘기는 호출 방식

- 메서드 정의시 첫 인자가 인스턴스 객체임을 선언, 호출시에는 자동으로 반영되기에 명시적으로 입력하지 않음 

2) 언바운드 메서드

- 메서드 호출시 명시적으로 첫 인자로 인스턴스 객체를 넘기는 호출 방식

- 클래스 객체를 통하여 메서드를 호출, 첫 인자로 인스턴스 객체를 입력


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

- 클래스 객체와 인스턴스 객체의 이름공간이 다름

- 인스턴스 객체를 통하여 변수나 함수의 이름을 찾는 순서: 인스턴스 객체 영역-> 클래스 객체 영역-> 전역 영역

- 찾지 못하는 경우 AttributeError 예외가 발생


1. 클래스 데이터를 참조하는 인스턴스 객체

class Person1:

    name = "Default Name"


p1 = Person1()

p2 = Person1()

print("p1's name: ", p1.name)

print("p2's name: ", p2.name)

p1's name:  Default Name

p2's name:  Default Name


2. 인스턴스 객체의 특화된 데이터는 인스턴스 이름공간에 저장

p1.name = "p1"

print("p1's name:", p1.name)

print("p2's name:", p2.name)

p1's name: p1

p2's name: Default Name


3. 실행시간에 동적으로 각 클래스와 인스턴스 이름공간에 멤버 변수를 추가/삭제 가능

- 클래스와 인스턴스 이름공간이 분리되어 있어 멤버 변수의 추가/삭제가 가능

- 클래스 객체의 데이터는 모든 인스턴스 객체에서도 접근가능


1) 클래스 객체에 새로운 멤버 변수 추가

Person1.title = "New title"                 # 클래스 객체에 새로운 멤버 변수 title 추가 

print("p1's title:", p1.title)              # 두 인스턴스 객체에서 모두 접근이 가능 

print("p2's title:", p2.title)

print("Person1's title:", Person1.title)    # 클래스 객체에서도 접근이 가능 

p1's title: New title

p2's title: New title

Person1's title: New title

2) 한 인스턴스 객체에만 새로운 멤버 변수 추가 

- 인스턴스 객체에 동적으로 멤버 변수를 추가하는 경우, 추가한 인스턴스 객체를 통해서만 접근이 가능

- 다른 인스턴스 객체에서 멤버 변수를 접근하면 인스턴스 객체의 이름공간과 클래스 객체 이름공간에서 변수 이름을 찾을 수 없고, AttributeError 예외 발생

p1.age = 20

print("p1's age:", p1.age)

print("p2's age:", p2.age)

p1's age: 20

    print("p2's age:", p2.age)

AttributeError: 'Person1' object has no attribute 'age'


4. 멤버 메서드에서 self가 누락되어 잘못된 출력 결과를 나타내는 예제

- 전역 영역과 클래스 영역에 동일한 이름인 str 변수가 존재, 의도와 다르게 출력 결과는 전역 변수 str값이 출력됨

str = "Not Class Member"        # 전역변수 

class GString:

    str = ""                    # 클래스 객체 멤버 변수 

    def Set(self, msg):

        self.str = msg

    def Print(self):

        print(str)              # self를 이용하여 클래스 멤버를 접근하지 않는 경우 

                                # 이름이 동일한 전역 변수에 접근하여 출력


g = GString()

g.Set("First Message")

g.Print()

print("g's str:", g.str)


Not Class Member

g's str: First Message


5. 인스턴스 객체의 내장 속성 __class__

- 인스턴스 객체가 자신을 생성한 클래스 객체를 참조하기 위해 클래스 영역에 모든 인스턴스 객체에 공통된 데이터를 참조하기 위함

- 인스턴스 객체 영역과 클래스 객체 영역이 별도로 존재함, 인스턴스 객체에 특화된 변수는 우선적으로 인스턴스 이름공간을 탐색하기 때문에 인스턴스 이름공간의 데이터를 출력

- 인스턴스 객체에 동일 이름의 데이터가 존재하더라도 __class__를 이용하여 클래스 이름공간의 데이터에 접근이 가능

class Test:

    data = "Default"

i2 = Test()

i1 = Test()

i1.__class__.data = "클래스 데이터가 변경됩니다."      # __class__속성을 이용하여 클래스 데이터를 변경 

#Test.data = "클래스 데이터가 변경됩니다."

print("i1.data:", i1.data)

print("i2.data:", i2.data)

i2.data = "i2의 데이터만 변경됩니다."                 # i2 인스턴스 객체의 데이터만 변경 

print("i1.data:", i1.data)

print("i2.data:", i2.data)

print("i2.__class__.data:", i2.__class__.data)  # i2 클래스 객체의 데이터는 변경되지 않음 


i1.data: 클래스 데이터가 변경됩니다.

i2.data: 클래스 데이터가 변경됩니다.

i1.data: 클래스 데이터가 변경됩니다.

i2.data: i2의 데이터만 변경됩니다.

i2.__class__.data: 클래스 데이터가 변경됩니다.


클래스 객체와 인스턴스 객체의 관계

- isinstance(): 인스턴스 객체가 어떤 클래스로부터 생성되었는지 확인하는 방법, boolean으로 반환됨

isinstance(인스턴스 객체, 클래스 객체)

- 클래스간의 상속관계가 있는 경우에도 자식 클래스의 인스턴스 객체는 부모클래스의 인스턴스로 평가됨

- 클래스 객체 정의시 어떠한 상속을 받지 않더라도 버전 3이후로는 암묵적으로 object객체를 상속 받음

- 자료형도 object 객체에서 파생됨

class Person2:

    pass

class Bird:

    pass

class Student(Person2):

    pass

p, s = Person2(), Student()

print("p is instance of Person:", isinstance(p, Person2))

print("s is instance of Person:", isinstance(s, Person2))

print("p is instance of Student", isinstance(p, Student))

print("p is instance of object:", isinstance(p, object))

print("p is instance of Bird:", isinstance(p, Bird))

print("int is instance of object:", isinstance(int, object))


p is instance of Person: True

s is instance of Person: True

p is instance of Student False

p is instance of object: True

p is instance of Bird: False

int is instance of object: True

* 2.2 버전 이전에는 isinstance() 함수가 지원되지 않아 type(p) == Person2 이런식으로 확인함


생성자, 소멸자 메서드

1. 생성자

- 클래스 생성시 초기화 작업을 위한 메서드

- 인스턴스 객체가 생성될 때 자동으로 호출

- __init__()으로 정의됨

- 인스턴스 객체 생성시 초기화할 멤버 변수 값을 전달 할 수 있음


2. 소멸자

- 메모리 해제등의 종료 작업을 위한 메서드

- 인스턴스 객체의 참조 카운터가 0이될 때 호출

- __del__()으로 정의됨

class MyClass1:

    def __init__(self, value):                      # 생성자 

        self.Value = value                          # 이 라인은 왜 있는지 모르겠네;;;

        print("Class is created! Value =", value)

    

    def __del__(self):                              # 소멸자 

        print("Class is deleted!")


def foo():

    d = MyClass1(10)    # 함수 foo블록안에서만 인스턴스 객체 d가 존재 


foo()


Class is created! Value = 10

Class is deleted!


* 명시적으로 del구문을 사용한다고 클래스 객체의 소멸자 함수가 항상 호출 되는 것은 아님

- 인스턴스 객체를 생성한 이후 참조 카운터가 1이상 존재한다면 del 구문을 사용하여도 소멸자는 호출 되지 않음

- 예제에서 del c에서는 참조카운터가 1이상이므로 소멸자가 호출되지 않아야 되는데. 호출됨 뭔가 이상함...

그렇다고 del c_copy를 수행했을때 소멸자가 또 불리는 건 아님. 아마 객체의 복사가 얕은 복사라 그런듯...으로 추측만..

c_copy = c

del c

del c_copy


Class is created! Value = 10

Class is deleted!


정적 메서드, 클래스 메서드

<호출할 메서드 이름> = staticmethod(클래스내 정의한 메서드 이름)

<호출할 메서드 이름> = classmethod(클래스내 정의한 메서드 이름)


1. 정적 메서드

- 인스턴스 객체를 통하지 않고 클래스를 통해 직접 호출할 수 있는 메서드

- 메서드 정의시 인스턴스 객체를 참조하는 self라는 인자를 선언하지 않음


2. 클래스 메서드

- 암묵적으로 첫 인자로 클래스 객체가 전달됨, 관용적으로 cls라는 이름으로 사용


1) 인스턴스 개수를 관리하는 예제 (에러발생 코드)

- 인스턴스 영역의 값을 참조하지 않기 때문에 메서드의 정의시 첫 인자로 암묵적으로 받는 인스턴스 객체(self)를 사용하지 않음

- CounterManager.printInstanceCount(): 클래스를 통하여 호출하는 경우는 정상적으로 수행됨

b.printInstanceCount():인스턴스 객체를 이용하여 호출하는 경우 TypeError 발생

class CounterManager:

    insCount = 0

    def __init__(self):

        CounterManager.insCount += 1

    

    def printInstanceCount():

        print("Instance Count: ", CounterManager.insCount)


a, b, c = CounterManager(), CounterManager(), CounterManager()

CounterManager.printInstanceCount()

b.printInstanceCount()

Instance Count:  3

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Class/src/Class.py", line 125, in <module>

    b.printInstanceCount()

TypeError: printInstanceCount() takes 0 positional arguments but 1 was given


2) 정적 메서드, 클래스 메서드를 이용한 예제

- 이것때문에 한참 삽질했다...책에 있는 예제 대로 따라 했는데 syntax error가 뜨고,, 그러나 아이러니 한건..실행은 되니.

 pydev의 문제인지.. python 버전의 문제인지... 어쨌든 해결.


a. 책의 예제 코드: 정적 메서드, 클래스 메서드 정의에서 에러가 난다.

- 첫번째 인자로 self인자가 없다는 에러가 난다.

class CounterManager:

    insCount = 0

    def __init__(self):

        CounterManager.insCount += 1

    

    # 정적 메서드 정의

    def staticPrintCount():

        print("Instance Count: ", CounterManager.insCount)

    # 정적 메서드로 등록 

    SPrintCount = staticmethod(staticPrintCount)

    

    # 클래스 메서드 정의 

    def classPrintCount(cls):

        print("Instance Count: ", cls.insCount)

    # 클래스 메서드로 등록 

    CPrintCount = classmethod(classPrintCount)


a, b, c = CounterManager(), CounterManager(), CounterManager()


CounterManager.SPrintCount()

b.SPrintCount()


CounterManager.CPrintCount()

b.CPrintCount()

Instance Count:  3

Instance Count:  3

Instance Count:  3

Instance Count:  3


b. 수정한 코드

- 정적 메서드, 클래스 메소드 등록 절차 대신 @staticmethod, @classmethod를 사용하여 해결

- 데코레이터라는 개념을 알아야 되는것 같은데... 나중에 알도록 해야겠다.

class CounterManager:

    insCount = 0

    def __init__(self):

        CounterManager.insCount += 1

    

    # 정적 메서드 정의

    @staticmethod

    def staticPrintCount():

        print("Instance Count: ", CounterManager.insCount)

    

    # 클래스 메서드 정의 

    @classmethod

    def classPrintCount(cls):

        print("Instance Count: ", cls.insCount)


a, b, c = CounterManager(), CounterManager(), CounterManager()


CounterManager.staticPrintCount()

b.staticPrintCount()


CounterManager.classPrintCount()

b.classPrintCount()

Instance Count:  3

Instance Count:  3

Instance Count:  3

Instance Count:  3

<참고>

1. staticmethod

2. classmethod

3. decorator


3. Private 멤버 변수

1) 인스턴스 객체 개수를 관리하는 예제 코드

class CounterManager:

    insCount = 0

    def __init__(self):

        CounterManager.insCount += 1

    

    # 정적 메서드 정의

    @staticmethod

    def staticPrintCount():

        print("Instance Count: ", CounterManager.insCount)

    

    # 클래스 메서드 정의 

    @classmethod

    def classPrintCount(cls):

        print("Instance Count: ", cls.insCount)


a, b, c = CounterManager(), CounterManager(), CounterManager()


CounterManager.staticPrintCount()

b.staticPrintCount()


CounterManager.classPrintCount()

b.classPrintCount()

- 위의 예제 코드에서 insCount 멤버변수는 인스턴스 객체의 개수를 저장하는 것으로 매우 중요한 변수

- 파이썬에서는 기본적으로 멤버 변수가 public 접근 권한을 갖기 때문에 외부에서 접근, 변경이 가능


2) 인스턴스 객체 개수 멤버 변수를 외부에서 접근/변경

print('CounterManager.insCount:', CounterManager.insCount)

CounterManager.insCount = 0

print('CounterManager.insCount:', CounterManager.insCount)


3) private 접근 권한

- 파이썬에서는 기본적으로 private 접근 권한이 존재 하지 않는다. 그러나 이름 변경(Naming Mangling)개념으로 문제를 해결

- 클래스 내의 멤버 변수나 함수를 정의할때 __로 시작하는 경우, 클래스 외부에서 참조할 때 자동적으로 __[클래스이름]__[멤버 이름]으로 변경됨

- 클래스 내에서는 __[멤버이름] 만으로 사용 가능

- 외부에서 변경된 멤버 변수 이름으로 접근하는 경우 읽기, 쓰기가 가능


a. 이름 변경을 적용한 예제 코드

class CounterManager:

    __insCount = 0

    def __init__(self):

        CounterManager.__insCount += 1

    

    # 정적 메서드 정의

    @staticmethod

    def staticPrintCount():

        print("Instance Count: ", CounterManager.__insCount)

    

    # 클래스 메서드 정의 

    @classmethod

    def classPrintCount(cls):

        print("Instance Count: ", cls.__insCount)


a, b, c = CounterManager(), CounterManager(), CounterManager()


CounterManager.staticPrintCount()

b.staticPrintCount()


CounterManager.classPrintCount()

b.classPrintCount()

Instance Count:  3

Instance Count:  3

Instance Count:  3

Instance Count:  3

b. 외부에서 멤버 변수 접근 시도 (error 발생)

- __insCount 멤버 변수로 접근 시 AttributeError발생

print('CounterManager.__insCount:', CounterManager.__insCount)

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Class/src/Class.py", line 186, in <module>

    print('CounterManager.__insCount:', CounterManager.__insCount)

AttributeError: type object 'CounterManager' has no attribute '__insCount'

 c. 변경된 멤버 변수 이름 확인

- __insCount 멤버 변수 이름이 _CounterManager_insCount로 변경 되있는것을 확인

print('dir(CounterManager):', dir(CounterManager))

dir(CounterManager): ['_CounterManager__insCount', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'classPrintCount', 'staticPrintCount']

<참고 - dir()>

d. 외부에서 변경된 이름으로 접근


print('CounterManager._CounterManager__insCount', CounterManager._CounterManager__insCount)

print('b._CounterManager__insCount', b._CounterManager__insCount

CounterManager._CounterManager__insCount 3

b._CounterManager__insCount 3

* 정보 은닉 기능으로 이름 변경을 제공하기 보다는 개발자의 의도를 나타내기 위한 의도로 사용, 변경된 이름으로 변수에 접근하여 사용하는 것을 장려 하지는 않는다고함..


연산자 중복 정의

1. 연산자 중복(Operator Overloading) 정의

- 사용자 정의 객체에 대하여 필요한 연산자를 내장타입과 형태와 동작이 유사하도록 재정의 하는것

- 연산자 중복은 일반적으로 벡터나 행렬과 같은 수치 연산에서 자주 사용

- 연산자 오버로딩 같은 개념인가?

- __NAME__와 같이 두개의 밑줄 문자가 앞뒤로 있는 메서드

- 연산자가 클래스에서 사용되는 경우 맵핑된 매서드가 호출, 개발자가 정의한 동작을 수행


1) '-'연산자 중복 정의 예제

class GString1:

    def __init__(self, init=None):

        self.content = init

        print("self.content", self.content)

    

    def __sub__(self, str):

        for i in str:

            self.content = self.content.replace(i, '')

        return GString1(self.content)  #이건...왜하는지 모르겠음..

            

    def Remove(self, str):

        return self.__sub__(str)


g = GString1("ABCDEFGabcdefg")

g.Remove('Adg') 

g - "Adg"


self.content ABCDEFGabcdefg

self.content BCDEFGabcef

self.content BCDEFGabcef


- 출력하는 부분이 없어서 생성자에 출력문 임의 추가

- 연산자 중복 개념은 이해가 가나 __sub__함수에서 생성자 리턴문이 왜 들어가 있는지 모르겠음..

2) 명시적으로 중복하지 않은 연산자를 사용하는 경우

- 파이썬에서는 기본적으로 제공되는 연산자 중복 정의가 없음

TypeError 발생

g + "apple"

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Class/src/Class.py", line 216, in <module>

    g + "apple"

TypeError: unsupported operand type(s) for +: 'GString1' and 'str'


2. 미리 정의된 메서드

1) 수치 연산자


 a. 기본 연산자 지원

- '-='와 같은 확장 연산자가 존재하는 경우, 기본 연산자를 통하여 연산이 가능하기에 중복된 기본 연산으로 대치되어 수행됨

class GString2:

    def __init__(self, init=None):

        self.content = init

    

    # '-' 연산자를 중복 

    def __sub__(self, str):

        for s in str:

            self.content = self.content.replace(s, '')

        return GString2(self.content)

    

    # 'abs()' 내장 함수를 중복 

    def __abs__(self):

        return GString2(self.content.upper())

    

    def Print(self):

        print(self.content)

    

g2 = GString2("aBcdef")

# '-'연산자가 중복된 경우 '-='도 지원 

g2 -= "df"

g2.Print()

g2 = abs(g2)

g2.Print()

aBce

ABCE

b. 확장 연산자를 위한 메서드 지원 예제

- 확장 연산자와 기본 연산자의 동작을 구분하는 경우가 필요함, 확장 연산자만을 위한 메서드를 지원

class GString3:

    def __init__(self, init=None):

        self.content = init

    

    def __sub__(self, str):

        print("- operator is called!")

    

    def __isub__(self, str):

        print("-= operator is colled!")

    

g3 = GString3("aBcdef")

g3 - "a"

g3 -= "a"

- operator is called!

-= operator is colled!

c. 피연산자의 순서가 변경되는 경우 TypeError가 발생 예제

"a" - g3

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Class/src/Class.py", line 261, in <module>

    "a" - g3

TypeError: unsupported operand type(s) for -: 'str' and 'NoneType'


2) 시퀀스형 연산자

- 사용자로부터 초기값을 받아서 그 범위 안에서 인덱스로 전달 받은 값으니 10배를 반환하는 객체를 생성하는 예제

class Sequencer:

    def __init__(self, maxValue):

        # maxValue: index 최대 값 

        self.maxValue = maxValue

    

    def __len__(self):

        return self.maxValue

    

    def __getitem__(self, index):

        if(0 < index <= self.maxValue):

            return index*10

        else:

            raise IndexError("Index out of range")

    

    def __contains__(self, item):

        return (0 < item <= self.maxValue)


s = Sequencer(5)

print('s[1]:', s[1])

print('s[3]:', s[3])

print('[s[i] for i in range(1, 6)]:', [s[i] for i in range(1, 6)])

print('len(s):', len(s))

print('3 in s:', 3 in s)

print('7 in s', 7 in s)

print(s[7])


s[1]: 10

s[3]: 30

[s[i] for i in range(1, 6)]: [10, 20, 30, 40, 50]

len(s): 5

3 in s: True

7 in s False

Traceback (most recent call last):

  File "/Users/eunguru/Source/Python/Class/src/Class.py", line 292, in <module>

    print(s[7])

  File "/Users/eunguru/Source/Python/Class/src/Class.py", line 280, in __getitem__

    raise IndexError("Index out of range")

IndexError: Index out of range

* raise는 사용자가 의도적으로 예외를 발생시키는 구문으로 예제에서는 IndexError를 발생시킴, 자세한 내용은 예외처리 쪽에서 학습


상속

1. 상속

- 부모클래스: 구현해야 하는 여러 클래스의 공통된 속성을 부모 클래스에 정의

- 자식클래스(하위클래스): 특화된 메서드와 데이터를 정의

- 각 클래스마다 동일한 코드가 작성되는 것을 방지, 부모 클래스에 공통된 속성을 두어 코드의 유지보수가 쉬워짐, 부모 클래스에 정의된 인터페이스만을 알고 호출함으로써 각 개별 클래스에 특화된 기능을 공통된 인터페이스로 접근 할 수 있게 됨

- __dict__: 클래스 정보를 내부적으로 관리하는 사전 객체

# 부모 클래스 

class Person3:

    def __init__(self, name, phoneNumber):

        self.Name = name

        self.PhoneNumber = phoneNumber

        

    def PrintPersonData(self):

        print("Person(Name:{0}, Phone Number: {1})".format(self.Name, self.PhoneNumber))

    

    def PrintInfo(self):

        print("Info(Name:{0}, Phone Number: {1})".format(self.Name, self.PhoneNumber))


# 자식 클래스 

class Student3(Person3):

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

        self.Name = name

        self.PhoneNumber = phoneNumber

        self.Subject = subject

        self.StudentID = studentID


P = Person3("gatsby", "010-1234-5678")

S = Student3("nick", "010-2345-6789", "computer science", "20141020")

print("P.__dict__:", P.__dict__)

print("S.__dict__:", S.__dict__)

 

P.__dict__: {'Name': 'gatsby', 'PhoneNumber': '010-1234-5678'}

S.__dict__: {'StudentID': '20141020', 'PhoneNumber': '010-2345-6789', 'Name': 'nick', 'Subject': 'computer science'}


2. 클래스간의 관계확인

1) issubclass(자식클래스, 부모클래스)

print("issubclass(Student3, Person3):", issubclass(Student3, Person3))

print("issubclass(Person3, Student3):", issubclass(Person3, Student3))

print("issubclass(Person3, Person3):", issubclass(Person3, Person3))

print("issubclass(Person3, object):", issubclass(Person3, object))

print("issubclass(Student3, object):", issubclass(Student3, object))


class Dog:

    pass

print("issubclass(Student3, Dog):", issubclass(Student3, Dog))

print("issubclass(Dog, Person3):", issubclass(Dog, Person3))

print("issubclass(Dog, object):", issubclass(Dog, object))

issubclass(Student3, Person3): True

issubclass(Person3, Student3): False

issubclass(Person3, Person3): True

issubclass(Person3, object): True

issubclass(Student3, object): True

issubclass(Student3, Dog): False

issubclass(Dog, Person3): False

issubclass(Dog, object): True

2) __bases__

- 어떤 클래스의 부모 클래스를 알아내는 속성, 직계의 부모클래스를 튜플로 반환

print("Person3.__bases__:", Person3.__bases__)

print("Student3.__bases__:", Student3.__bases__)

Person3.__bases__: (<class 'object'>,)

Student3.__bases__: (<class '__main__.Person3'>,)


3. 부모 클래스의 생성자 호출

- 자식 클래스에서 부모 클래스의 멤버 변수를 초기화 해야되는 부분이 있을 경우 부모 클래스의 생성자를 호출함

- 명시적으로 부모클래스의 생성자를 호출하며, 언바운드 메서드 방법으로 self 객체를 전달해야함


1) Before

class Person3:

    def __init__(self, name, phoneNumber):

        self.Name = name

        self.PhoneNumber = phoneNumber

... 중략 ...


class Student3(Person3)

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

        self.Name = name

        self.PhoneNumber = phoneNumber

        self.Subject = subject

        self.StudentID = studentID

... 중략 ...

2)  After

class Person3:

    def __init__(self, name, phoneNumber):

        self.Name = name

        self.PhoneNumber = phoneNumber

... 중략 ...


class Student3(Person3)

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

        # 명시적으로 Person3 생성자를 호출 

        Person3.__init__(self, name, phoneNumber)   #언바운드 메서드 방법으로 self 객체를 함꼐 전달해야함 

        self.Subject = subject

        self.StudentID = studentID

    ... 중략 ...


4. 메서드 추가하기

class Person3:

... 중략 ...

class Student3(Person3)

    '''

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

        self.Name = name

        self.PhoneNumber = phoneNumber

        self.Subject = subject

        self.StudentID = studentID

    '''

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

        # 명시적으로 Person3 생성자를 호출 

        Person3.__init__(self, name, phoneNumber)   #언바운드 메서드 방법으로 self 객체를 함꼐 전달해야함 

        self.Subject = subject

        self.StudentID = studentID

        

    def PrintStudentData(self):

        print("Student(Subject: {0}, Student ID: {1})".format(self.Subject, self.StudentID))


S = Student3("nick", "010-2345-6789", "computer science", "20141020")

S.PrintPersonData()

S.PrintStudentData()

print("dir(S):", dir(S))


Person(Name:nick, Phone Number: 010-2345-6789)

Student(Subject: computer science, Student ID: 20141020)

dir(S): ['Name', 'PhoneNumber', 'PrintInfo', 'PrintPersonData', 'PrintStudentData', 'StudentID', 'Subject', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']



5. 메서드 재정의하기

- 메서드 재정의(Method Overriding): 부모 클래스의 매서드에 대하여 자식 클래스에서 새롭게 재정의 하는것

class Person3:

... 중략 ...

    def PrintInfo(self):

        print("Info(Name:{0}, Phone Number: {1})".format(self.Name, self.PhoneNumber))

... 중략 ...


class Student3(Person3): 

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

        # 명시적으로 Person3 생성자를 호출 

        Person3.__init__(self, name, phoneNumber)   #언바운드 메서드 방법으로 self 객체를 함꼐 전달해야함 

        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}, Phone Number: {1})".format(self.Name, self.PhoneNumber))

        print("Info(Subject:{0}, Student ID:{1})".format(self.Subject, self.StudentID))


S.PrintPersonData()

S.PrintStudentData()

print("dir(S):", dir(S))

S.PrintInfo()


Person(Name:nick, Phone Number: 010-2345-6789)

Student(Subject: computer science, Student ID: 20141020)

dir(S): ['Name', 'PhoneNumber', 'PrintInfo', 'PrintPersonData', 'PrintStudentData', 'StudentID', 'Subject', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Info(Name:nick, Phone Number: 010-2345-6789)

Info(Subject:computer science, Student ID:20141020)


- 새롭게 작성되는 메서드의 이름은 재정의하는 메서드의 이름과 동일 해야함, 인수나 리턴형은 상관없음

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

- 이름공간의 속성 정보가 내부적으로 사전형태로 관리됨, 키값인 메서드의 이름만 같으면 부모 클래스의 메서드 대신에 자식 클래스의 메서드를 호출함

P = Person3("gatsby", "010-1234-5678")

S = Student3("nick", "010-2345-6789", "computer science", "20141020")

PersonList = [P, S]

for item in PersonList:

    item.PrintInfo()

Info(Name:gatsby, Phone Number: 010-1234-5678)

Info(Name:nick, Phone Number: 010-2345-6789)

Info(Subject:computer science, Student ID:20141020)


6. 메서드 확장하기

- 부모 클래스의 메서드는 그대로 이용하면서 자식 클래스 메서드에서 필요한 기능만 정의하는 것

class Person3:

... 중략 ...

    def PrintInfo(self):

        print("Info(Name:{0}, Phone Number: {1})".format(self.Name, self.PhoneNumber))

... 중략 ...


class Student3(Person3): 

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

        # 명시적으로 Person3 생성자를 호출 

        Person3.__init__(self, name, phoneNumber)   #언바운드 메서드 방법으로 self 객체를 함꼐 전달해야함 

        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}, Phone Number: {1})".format(self.Name, self.PhoneNumber))

        '''

        # 명시적으로 Person 클래스의 PrintInfo()를 호출 

        Person3.PrintInfo(self)

        print("Info(Subject:{0}, Student ID:{1})".format(self.Subject, self.StudentID))


S.PrintInfo()

Info(Name:nick, Phone Number: 010-2345-6789)

Info(Subject:computer science, Student ID:20141020)


7. 클래스 상속과 이름공간

- 인스턴스 객체를 통하여 변수나 함수의 이름을 찾는 규칙: 인스턴스 객체 영역 -> 클래스 객체 영역 -> 전역 영역

- 상속 관계 검색의 원칙(Principles of the inheritance search): 인스턴스 객체 영역 -> 클래스 객체간 상속을 통한 영역(자식 클래스 영역 -> 부모 클래스 영역) -> 전역 영역

- 자식 클래스가 상속 받은 메서드에 대해서 재정의를 하지 않거나 멤버 데이터에 새로운 값을 할당 하지 않은 경우, 자식 클래스 내부의 이름 공간에 그 데이터와 메서드를 위한 저장 공간을 생성하는 대신에 단순히 부모 클래스의 이름 공간에 존재하는 데이터와 메서드를 참조

- 중복된 데이터와 메서드를 최소화하여 메모리 사용의 효율성을 높이기 위함


1)각 클래스 객체 영역과 인스턴스 영역에서 속성(메서드, 멤버 변수)을 찾을 수 있음

class SuperClass:

    x = 10

    def printX(self):

        print(self.x)


class SubClass(SuperClass):

    y = 20

    def printY(self):

        print(self.y)


S1 = SubClass()

S1.a = 30

print("SuperClass: ", SuperClass.__dict__)

SuperClass:  {'__dict__': <attribute '__dict__' of 'SuperClass' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'SuperClass' objects>, 'printX': <function SuperClass.printX at 0x1025548c8>, 'x': 10, '__module__': '__main__'}


print("SubClass: ", SubClass.__dict__)

SubClass:  {'__doc__': None, '__module__': '__main__', 'printY': <function SubClass.printY at 0x102554950>, 'y': 20}


print("S1: ", S1.__dict__)

S1:  {'a': 30}

2) 인스턴스 객체가 상속 관게에서 속성(메서드, 멤버 변수)의 이름을 찾는 방법

- 인스턴스 객체 영역 -> 자식 클래스 영역 -> 부모 클래스 영역 -> 전역 영역

- 찾을 수 없는 경우 AttributeError 발생

class SuperClass:

    x = 10

    

    def printX(self):

        print(self.x)

    

class SubClass(SuperClass):

    y = 20

   

    def printY(self):

        print(self.y)


S1 = SubClass()

S1.a = 30

S1.x = 50

print("SuperClass: ", SuperClass.__dict__)

SuperClass:  {'__weakref__': <attribute '__weakref__' of 'SuperClass' objects>, '__doc__': None, '__dict__': <attribute '__dict__' of 'SuperClass' objects>, 'printX': <function SuperClass.printX at 0x101d648c8>, '__module__': '__main__', 'x': 10}


print("SubClass: ", SubClass.__dict__)

SubClass:  {'printY': <function SubClass.printY at 0x101d64950>, '__doc__': None, 'y': 20, '__module__': '__main__'}


print("S1: ", S1.__dict__)

S1:  {'a': 30, 'x': 50}


S1.printX()

50

3) 부모 클래스의 메서드를 재정의 하고 멤버 데이터에 값을 할당 하는 경우

class SuperClass:

    x = 10

    

    def printX(self):

        print(self.x)

    

class SubClass(SuperClass):

    y = 20

    

    def printX(self):

        print("SubClass:", self.x)

    

    def printY(self):

        print(self.y)


S1 = SubClass()

S1.a = 30

S1.x = 50

print("SuperClass: ", SuperClass.__dict__)

SuperClass:  {'x': 10, '__dict__': <attribute '__dict__' of 'SuperClass' objects>, 'printX': <function SuperClass.printX at 0x101d548c8>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'SuperClass' objects>, '__doc__': None}


print("SubClass: ", SubClass.__dict__)

SubClass:  {'printY': <function SubClass.printY at 0x101d549d8>, 'printX': <function SubClass.printX at 0x101d54950>, 'y': 20, '__module__': '__main__', '__doc__': None}


print("S1: ", S1.__dict__)

S1:  {'a': 30, 'x': 50}


S1.printX()

SubClass: 50


8. 다중 상속

- 2개 이상의 클래스를 상속 받는 경우, 두 클래스의 모든 속성(변수, 메서드)을 물려 받게 됨

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

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

1) 다중 상속 예제

class Tiger:

    def Jump(self):

        print("호랑이처럼 미리 점프하기")


class Lion:

    def Bite(self):

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


class Liger(Tiger, Lion):

    def Play(self):

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


l = Liger()

l.Bite()

l.Jump()

l.Play()

사자처럼 한입에 꿀꺽하기

호랑이처럼 미리 점프하기

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


2) 다중 상속시 메서드의 이름 검색 방법

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()

호랑이: 어흥~


3) __mro__

- 다중 상속 구조에서 메서드의 이름을 찾는 순서는 __mro__에 튜플로 정의되어 있음

- MRO: Method Resolution Order

>>> Liger.__mro__

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

>>> 

* eclipse+pydev console상에서는 __mro__ 결과가 출력 안됨, 이유는 모르겠음.

idle에서 실행한 결과를 일단 써놓음..


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


1) 다이아몬드형 상속, Animal 클래스의 생성자가 두번 호출되는 예제


class Animal:

    def __init__(self):

        print("Animal __init__()")

        

class Tiger:

    def __init__(self):

        Animal.__init__(self)

        print("Tiger __init__()")


class Lion:

    def __init__(self):

        Animal.__init__(self)

        print("Lion __init__()")


class Liger(Tiger, Lion):

    def __init__(self):

        Tiger.__init__(self)

        Lion.__init__(self)

        print("Liger __init__()")


l = Liger()


Animal __init__()

Tiger __init__()

Animal __init__()

Lion __init__()

Liger __init__()

2) Animal 클래스의 생성자가 한번 호출되는 예제

- super(): 부모 클래스의 객체를 반환

- super().메서드 이름(인자) 형태로 부모 클래스의 메서드를 호출

- 명시적으로 부모 클래스 이름을 쓰는 것보다 코드의 유지보수가 쉬워짐

- 동적 실행 환경에서 클래스간에 상호 동작으로 다중 상속 문제를 해결, 인터프리터가 자동으로 생성자 두번 호출을 방지

- super().__init__(): 부모클래스의 생성자를 호출, 생성자 호출 순서는 MRO의 역순으로 상위 클래스 부터 호출됨

class Animal:

    def __init__(self):

        print("Animal __init__()")

        

class Tiger:

    def __init__(self):

        super().__init__()

        print("Tiger __init__()")


class Lion:

    def __init__(self):

        super().__init__()

        print("Lion __init__()")


class Liger(Tiger, Lion):

    def __init__(self):

        super().__init__()

        print("Liger __init__()")


l = Liger()

Lion __init__()

Tiger __init__()

Liger __init__()


'컴&프로그래밍 > Python' 카테고리의 다른 글

8. 입출력  (0) 2015.01.14
7. 예외처리  (0) 2015.01.02
6. 모듈  (0) 2014.12.29
Mac OS X Yosemite에서 PyCharm 설치 후 실행  (0) 2014.12.27
4. 제어  (0) 2014.12.19
Google Python Tutorial - Basic Python Exercises #2 List  (0) 2014.12.11
Google Python Tutorial - Basic Python Exercises #1 String  (0) 2014.12.05
Python 2.x에서 3.x로 변경  (0) 2014.12.03
튜플 자료형 특이사항  (0) 2014.10.19