파이썬도 객체지향 프로그래밍이 가능
상속, 다중상속, 다형성, 정보 은닉, 추상화, 부모클래스, 자식클래스, 클래스, 인스턴스
클래스 선언
- 클래스의 구성: 데이터, 메서드 (반드시 필요한 것은 아님)
- 클래스 객체: 클래스 선언과 동시에 클래스 객체가 생성, 클래스 선언을 통해 새로운 이름 공간이 생성 됨
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 |