SWEA 파이썬 프로그래밍 기초(2) 파이썬의 기본 응용 #43
객체를 이용해 문제를 해결하는 프로그래밍
- 상태와 행위로 이루어진 객체 형성
- 객체 조립
- 프로그램 형성
부품화와 재사용성에 좋다.
- 같은 문제 도메인에 속하는 속성(attribute)과 행위(behavior)를 정의
- 객체지향 프로그래밍의 기본적인 사용자 정의 데이터 타입
- 인스턴스 : 메모리에 로딩된 클래스를 통해 클래스를 템플릿으로 하여 메모리 상에 생성된 정보
- 자신 고유의 속성을 가지며 클래스에서 정의한 행위 수행
- 객체의 행위는 클래스에서 정의된 행위에 대한 정의를 공유함으로써 메모리를 효율적으로 사용
- 메시지(Message)라고도 부름
- 클래스로부터 생성된 객체 사용 시 객체에 명령을 내리는 행위
- 객체가 가지고 있는 메서드를 호출한다 -> 객체에 메시지를 전달한다
- 한 객체의 속성을 조작할 목적으로 사용
- 객체 간의 통신은 메시지 전달을 통해 이루어짐
-
객체에서 공통된 속성과 행위를 추출하는 것
-
ex. 홍길동, 이순신 강감찬 등의 학생들이 있다면, 학생으로 추상화
- 공통속성 : 학번, 이름, 주민번호, 학과, 주소, 전화번호
- 공통행위 : 수강신청, 수강취소, 휴학신청, 복학신청 등
-
여기서 학생 -> 추상 데이터 타입
추상 데이터 타입
- 데이터 타입의 표현과 연산을 캡슐화
- 접근 제어를 통해 데이터의 정보를 은닉
객체지향 프로그래밍에서는
- 클래스 : 추상 데이터 타입
- 객체 : 추상 데이터 타입의 인스턴스
- 메서드 : 추상 데이터 타입에서 정의된 연산
- 새로운 클래스가 기존의 클래스의 데이터와 연산을 이용할 수 있게 해주는 속성
- 하위 클래스를 이용해 프로그램의 요구에 맞추어 클래스 수정 가능
- 클래스 간의 종속 관계를 형성하여 객체 조직화
상속의 효과
- 재사용으로 인해 코드가 줄어듦(부모 클래스의 속성 재사용)
- 범용적인 사용 가능 : object 타입의 매개변수에는 string이나 int의 객체 쓰여도 상관 X
- 자료와 메서드의 자유로운 사용 및 추가 가능
- 다양한 형태로 나타날 수 있는 특징
- 상위 클래스의 행위를 하위 클래스에서 재정의하기 때문에 생김
- 어떤 한 요소에 여러 개념을 넣어 놓는 것
오버라이딩
같은 이름의 메서드가 여러 클래스에서 다른 기능을 하는 것
메서드 오버라이딩
- 상속으로 물려 받은 자료나 메서드를 그대로 사용하지 않고, 하위 클래스에서 새로 정의해 사용하는 기법
- 상위 클래스의 메서드와 동일한 서명(매개변수의 타입, 개수, 리턴 타입)을 가져야 함
- 코드의 재사용성 향상
오버로딩
같은 이름의 메서드가 인자의 개수나 자료형에 따라서 다른 기능을 하는 것
메서드 오버로딩
- 클래스 내부에 동일한 이름의 행위를 여러 개 정의하는 것
- 메서드의 이름이 같고, 매개변수의 타입과 수는 서로 달라야 함
- 리턴 타입은 관계하지 않음
- 메서드 이름을 하나로 통일 가능하며, 같은 이름의 메서드에 여러 종류의 매개 변수를 받을 수 있음
멤버의 정보를 관리
- 개개인을 딕셔너리 객체로 저장
- 이 딕셔너리 객체들을 모두 저장하는 리스트 객체 필요
members = [ {"name": "홍길동", "age": 20}, {"name": "이순신", "age": 45}, {"name": "강감찬", "age": 35}, ] for member in members: print("{0}\t{1}".format(member["name"], member["age"])) [결과] 홍길동 20 이순신 45 강감찬 35
# 딕셔너리 객체 생성 함수 def create(name, age): return {"name": name, "age": age} # 딕셔너리 정보를 문자열로 변환 def to_str(person): return "{0}\t{1}".format(person["name"], person["age"]) # 딕셔너리 객체를 가진 members 리스트 객체 생성 members = [create("홍길동", 20), create("이순신", 45), create("강감찬", 35)] # 함수 문자열 반복 출력 for member in members: print(to_str(member)) [결과] 홍길동 20 이순신 45 강감찬 35
객체 생성을 위한 청사진 또는 템플릿
멤버와 관련된 추상 데이터 타입이 필요하다면
- 멤버 클래스 설계
- 멤버 클래스 제작
- 객체 생성
-
클래스 정의 class 클래스명:
-
객체 생성 변수 = 클래스명() -> 생성자 메서드(클래스 이름과 동일한 메서드)
class Person: pass # 멤버 객체 생성하는 생성자 메서드 member = Person() # 첫 번째 인자인 객체가, 두 번째인자인 클래스의 인스턴스인지 검사 if isinstance(member, Person): print("member는 Person 클래스의 인스턴스입니다.") [결과] member는 Person 클래스의 인스턴스입니다.
- 생성자 메서드
-
객체를 생성하기 위해 호출
-
__init__메서드 실행 -
정의
class 클래스명: def __init__(self, 매개변수목록): ...
- 소멸자 메서드
- 객체가 소멸되기 전에 호출
__del__메서드 실행- 정의
class 클래스명: ... def __del__(self): # self를 제외한 매개변수 사용 X ...
- self
- 객체 공간을 가리키는 식별자
- 객체공간의 필드와 메서드에 접근할 경우 self.식별자 형식 이용
class Person: # self가 가리키는 객체 공간에 name, age 필드 생성 def __init__(self, name, age): self.name = name self.age = age print("{0} 객체가 생성되었습니다.".format(self.name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.name)) member = Person("홍길동", 20) print("{0}\t{1}".format(member.name, member.age)) print(dir(member)) [결과] 홍길동 객체가 생성되었습니다. 홍길동 20 ['__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'name'] 홍길동 객체가 제거되었습니다.
- self가 가리키는 객체의 필드 정보에 접근해 특정 목적의 기능을 수행하도록 정의된 메서드
- 정의
ex. to_str(self) 메서드
class Person: def __init__(self, name, age): self.name = name self.age = age print("{0} 객체가 생성되었습니다.".format(self.name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.name)) def to_str(self): # 인스턴스 메서드이므로 self 매개변수 필수 return "{0}\t{1}".format(self.name, self.age) members = [Person("홍길동", 20), Person("이순신", 45), Person("강감찬", 35)] for member in members: print(member.to_str()) [결과] 홍길동 객체가 생성되었습니다. 이순신 객체가 생성되었습니다. 강감찬 객체가 생성되었습니다. 홍길동 20 이순신 45 강감찬 35 강감찬 객체가 제거되었습니다. 이순신 객체가 제거되었습니다. 홍길동 객체가 제거되었습니다.
- 클래스 내에서 self.변수 형태를 가지는 변수
- 객체마다 가지고 있는 개체 고유의 정보
멤버 필드의 접근 제한이 이루어지지 않을 경우
class Person: def __init__(self, name, age): self.name = name self.age = age # 캡슐화된 필드로 만드는 것이 필요 # 입력 시 유효성 검사를 할 수 없으므로 잘못된 값 저장 가능성 # ex. age필드에 200, -20 등의 값 입력 print("{0} 객체가 생성되었습니다.".format(self.name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.name)) def to_str(self): return "{0}\t{1}".format(self.name, self.age)
인스턴스 변수의 접근 제한 기능
- 클래스 외부에서 필드에 접근하는 것을 제한
- 프라이빗 필드 생성 :
self.__name = name - getter/setter 메서드의 제공 여부 결정 가능
- getter : 멤버를 읽어오는 메서드
- setter : 멤버를 변경하는 메서드
class Person: def __init__(self, name, age): self.__name = name self.__age = age print("{0} 객체가 생성되었습니다.".format(self.__name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.__name)) def to_str(self): return "{0}\t{1}".format(self.__name, self.__age) # __name 필드의 값을 반환하는 getter 메서드 # __name 필드에 대해서는 getter 메서드만 제공 def get_name(self): return self.__name # __age 필드에 대해서는 getter 메서드와 setter 메서드 전부 제공 def get_age(self): return self.__age # __age 필드의 값을 변경하는 setter 메서드 def set_age(self, age): if age < 0: raise TypeError("나이는 0이상의 값만 허용합니다.") self.__age = age members = [Person("홍길동", 20), Person("이순신", 45), Person("강감찬", 35)] members[0].set_age(-20) for member in members: print(member.to_str()) [결과] 홍길동 객체가 생성되었습니다. 이순신 객체가 생성되었습니다. 강감찬 객체가 생성되었습니다. Traceback (most recent call last): File "c:\Intellij\Python\SWEA\2022-01-08-SWEA-Python2#44-50.py", line 31, in <module> members[0].set_age(-20) File "c:\Intellij\Python\SWEA\2022-01-08-SWEA-Python2#44-50.py", line 25, in set_age raise TypeError("나이는 0이상의 값만 허용합니다.") TypeError: 나이는 0이상의 값만 허용합니다. 강감찬 객체가 제거되었습니다. 이순신 객체가 제거되었습니다. 홍길동 객체가 제거되었습니다.
- getter/setter 기능을 대신할 수 있는 기능
- 변수 이름과 같은 메서드를 만들어 사용 가능
- @property
- @property의이름.setter
class Person: def __init__(self, name, age): self.__name = name self.__age = age print("{0} 객체가 생성되었습니다.".format(self.__name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.__name)) def to_str(self): return "{0}\t{1}".format(self.__name, self.__age) @property # 변수처럼 사용 가능 # __name 필드값을 반환하는 getter메서드의 역할 def name(self): return self.__name @property def age(self): return self.__age @age.setter # 변수처럼 사용 가능 # __name 필드값을 반환하는 setter메서드의 역할 def age(self, age): if age < 0: raise TypeError("나이는 0 이상의 값만 허용합니다.") self.__age = age members = [Person("홍길동", 20), Person("이순신", 45), Person("강감찬", 35)] # age @property 데코레이터를 이용해 변수처럼 값 저장 members[0].age = 22 for member in members: print(member.to_str()) [결과] 홍길동 객체가 생성되었습니다. 이순신 객체가 생성되었습니다. 강감찬 객체가 생성되었습니다. 홍길동 22 이순신 45 강감찬 35 강감찬 객체가 제거되었습니다. 이순신 객체가 제거되었습니다. 홍길동 객체가 제거되었습니다.
클래스 내에서 클래스명.변수 형식으로 선언된 변수
- 클래스 변수 정의
class 클래스명: 클래스변수 = 값
- 클래스 변수 접근
클래스명.클래스변수
- 클래스 변수의 count 활용법
class Person: count = 0 def __init__(self, name, age): self.__name = name self.__age = age Person.count += 1 print("{0} 객체가 생성되었습니다.".format(self.__name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.__name)) def to_str(self): return "{0}\t{1}".format(self.__name, self.__age) @property def name(self): return self.__name @property def age(self): return self.__age @age.setter def age(self, age): if age < 0: raise TypeError("나이는 0 이상의 값만 허용합니다.") self.__age = age members = [Person("홍길동", 20), Person("이순신", 45), Person("강감찬", 35)] print("현재 Person 클래스의 인스턴스는 총 {0} 개입니다.".format(Person.count)) [결과] 홍길동 객체가 생성되었습니다. 이순신 객체가 생성되었습니다. 강감찬 객체가 생성되었습니다. 현재 Person 클래스의 인스턴스는 총 3 개입니다. 강감찬 객체가 제거되었습니다. 이순신 객체가 제거되었습니다. 홍길동 객체가 제거되었습니다.
클래스가 소유한 메서드
- 클래스 메서드의 정의
class 클래스명: ... @classmethod def 클래스메서드(cls, 매개변수목록): ...
- 클래스 메서드의 사용
class Person: count = 0 def __init__(self, name, age): self.__name = name self.__age = age Person.count += 1 print("{0} 객체가 생성되었습니다.".format(self.__name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.__name)) def to_str(self): return "{0}\t{1}".format(self.__name, self.__age) @property def name(self): return self.__name @property def age(self): return self.__age @age.setter def age(self, age): if age < 0: raise TypeError("나이는 0 이상의 값만 허용합니다.") self.__age = age @classmethod def get_info(cls): return "현재 Person 클래스의 인스턴스는 총 {0} 개입니다.".format(cls.count) members = [Person("홍길동", 20), Person("이순신", 45), Person("강감찬", 35)] print(Person.get_info()) [결과] 홍길동 객체가 생성되었습니다. 이순신 객체가 생성되었습니다. 강감찬 객체가 생성되었습니다. 현재 Person 클래스의 인스턴스는 총 3 개입니다. 강감찬 객체가 제거되었습니다. 이순신 객체가 제거되었습니다. 홍길동 객체가 제거되었습니다.
- 파이썬에서 연산자는 각 클래스의 메서드와 매핑(mapping)되어 있음
- 사용자 정의 클래스에는 연산자에 대해 매핑될 메서드가 없음 -> 연산자를 사용하기 위해서 연산자를 중복해서 정의
- 비교연산자 오버로딩
class Person: count = 0 def __init__(self, name, age): self.__name = name self.__age = age Person.count += 1 print("{0} 객체가 생성되었습니다.".format(self.__name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.__name)) def to_str(self): return "{0}\t{1}".format(self.__name, self.__age) @property def name(self): return self.__name @property def age(self): return self.__age @age.setter def age(self, age): if age < 0: raise TypeError("나이는 0 이상의 값만 허용합니다.") self.__age = age @classmethod def get_info(cls): return "현재 Person 클래스의 인스턴스는 총 {0} 개입니다.".format(cls.count) # self의 __age 필드가 other 객체의 __age 필드보다 크면 true 반환 def __gt__(self, other): return self.__age > other.__age # self의 __age 필드가 other 객체의 __age 필드보다 크거나 같으면 true 반환 def __ge__(self, other): return self.__age >= other.__age # self의 __age 필드가 other 객체의 __age 필드보다 작으면 true 반환 def __lt__(self, other): return self.__age < other.__age # self의 __age 필드가 other 객체의 __age 필드보다 작거나 같으면 true 반환 def __le__(self, other): return self.__age <= other.__age # self의 __age 필드가 other 객체의 __age 필드와 같으면 true 반환 def __eq__(self, other): return self.__age == other.__age # self의 __age 필드가 other 객체의 __age 필드와 같지 않으면 true 반환 def __ne__(self, other): return self.__age != other.__age members = [Person("홍길동", 20), Person("이순신", 45), Person("강감찬", 35)] cnt = len(members) i = 0 while True: print( "members[{0}] > members[{1}] => {2}".format(i, 0, members[i] > members[i + 1]) ) i += 1 if i == cnt - 1: print( "members[{0}] > members[{1}] => {2}".format(i, 0, members[i] > members[0]) ) break
__str()__ 메서드를 구현하면 str() 함수에 객체를 전달해 문자열로 변환
class Person: count = 0 def __init__(self, name, age): self.__name = name self.__age = age Person.count += 1 print("{0} 객체가 생성되었습니다.".format(self.__name)) def __del__(self): print("{0} 객체가 제거되었습니다.".format(self.__name)) @property def name(self): return self.__name @property def age(self): return self.__age @age.setter def age(self, age): if age < 0: raise TypeError("나이는 0 이상의 값만 허용합니다.") self.__age = age @classmethod def get_info(cls): return "현재 Person 클래스의 인스턴스는 총 {0} 개입니다.".format(cls.count) def __gt__(self, other): return self.__age > other.__age def __ge__(self, other): return self.__age >= other.__age def __lt__(self, other): return self.__age < other.__age def __le__(self, other): return self.__age <= other.__age def __eq__(self, other): return self.__age == other.__age def __ne__(self, other): return self.__age != other.__age def __str__(self): return "{0}\t{1}".format(self.__name, self.__age) members = [Person("홍길동", 20), Person("이순신", 45), Person("강감찬", 35)] for member in members: # Person 클래스의 객체 전달하면 __str__ 메서드 호출 print(str(member)) [결과] 홍길동 객체가 생성되었습니다. 이순신 객체가 생성되었습니다. 강감찬 객체가 생성되었습니다. 홍길동 20 이순신 45 강감찬 35 강감찬 객체가 제거되었습니다. 이순신 객체가 제거되었습니다. 홍길동 객체가 제거되었습니다.
- 부모 클래스의 동작을 자식 클래스에서 재사용하거나 확장, 수정하는 것
- 파이썬에서는 단일 상속만 지원
- 하나의 클래스는 단일 클래스에서만 상속 가능
- 정의
class 클래스명(부모클래스명):
- 예시
class Parent: def __init__(self, family_name): self.__family_name = family_name print("Parent 클래스의 __init__() ...") @property def family_name(self): return self.__family_name class Child(Parent): # Parent 클래스 상속 def __init__(self, first_name, last_name): # 부모 클래스의 __family_name 필드를 매개변수 last_name으로 초기화 Parent.__init__(self, last_name) # super().__init__(last_name) : 클래스 이름 호출이 아니라 super() 써도 됨 self.__first_name = first_name print("Child 클래스의 __init__() ...") @property def first_name(self): return self.__first_name @first_name.setter def first_name(self, first_name): self.__first_name = first_name @property def name(self): return "{0}{1}".format(self.family_name, self.first_name) child = Child("길동", "홍") print(child.family_name) print(child.first_name) print(child.name) print("======>") child.first_name = "길순" print(child.name) [결과] Parent 클래스의 __init__() ... Child 클래스의 __init__() ... 홍 길동 홍길동 ======> 홍길순
부모 클래스에 있는 메서드와 동일한 서명을 가진 메서드를 자식 클래스에서 다시 정의해 사용하는 것
class Parent: def __init__(self, family_name): self.__family_name = family_name print("Parent 클래스의 __init__() ...") @property def family_name(self): return self.__family_name def print_info(self): print("Parent: {0}".format(self.family_name)) class Child(Parent): # Parent 클래스 상속 def __init__(self, first_name, last_name): Parent.__init__(self, last_name) # super().__init__(last_name) : 클래스 이름 호출이 아니라 super() 써도 됨 self.__first_name = first_name print("Child 클래스의 __init__() ...") @property def first_name(self): return self.__first_name @first_name.setter def first_name(self, first_name): self.__first_name = first_name @property def name(self): return "{0}{1}".format(self.family_name, self.first_name) def print_info(self): Parent.print_info(self) # super().print_info() print("Child: {0}".format(self.name)) child = Child("길동", "홍") child.print_info()
오름차순/내림차순으로 정렬하기
- Student 클래스
- 프라이빗 필드를 가지고 있음
- 읽기 전용 name, gender property
- 읽기, 쓰기 모두 가능한 height property
- 특수함수
__repr__에 대한 정의를 가짐
[결과]
name으로 오름차순 정렬 후 ===> Student(name: 강감찬, gender: 남, height: 182.2) Student(name: 유관순, gender: 여, height: 158.4) Student(name: 이순신, gender: 남, height: 188.5) Student(name: 홍길동, gender: 남, height: 176.5) name으로 내림차순 정렬 후 ===> Student(name: 홍길동, gender: 남, height: 176.5) Student(name: 이순신, gender: 남, height: 188.5) Student(name: 유관순, gender: 여, height: 158.4) Student(name: 강감찬, gender: 남, height: 182.2) height으로 오름차순 정렬 후 ===> Student(name: 유관순, gender: 여, height: 158.4) Student(name: 홍길동, gender: 남, height: 176.5) Student(name: 강감찬, gender: 남, height: 182.2) Student(name: 이순신, gender: 남, height: 188.5) height으로 내림차순 정렬 후 ===> Student(name: 이순신, gender: 남, height: 188.5) Student(name: 강감찬, gender: 남, height: 182.2) Student(name: 홍길동, gender: 남, height: 176.5) Student(name: 유관순, gender: 여, height: 158.4)
# -*- coding: utf-8 -*- # student_name_height.py class Student: def __init__(self, name, gender, height): self.__name = name self.__gender = gender self.__height = height @property def name(self): return self.__name @property def gender(self): return self.__gender @property def height(self): return self.__height @height.setter def height(self, height): self.__height = height student_list = [] Student("강감찬", "남", 182.2) Student("유관순", "여", 158.4) Student("이순신", "남", 188.5) Student("홍길동", "남", 176.5)
뭘 어떻게 해야할지 감이 안 온다.. 더 붙잡지 않고 답안을 보기로 했다.
# -*- coding: utf-8 -*- # student_name_height.py class Student: def __init__(self, name, gender, height): self.__name = name self.__gender = gender self.__height = height @property def name(self): return self.__name @property def gender(self): return self.__gender @property def height(self): return self.__height @height.setter def height(self, height): self.__height = height # 객체 출력시 주로 사용하는 함수 def __repr__(self): return "{0}(name: {1}, gender: {2}, height: {3})".format( self.__class__.__name__, self.name, self.gender, self.height ) students = [ Student("홍길동", "남", 176.5), Student("강감찬", "남", 182.2), Student("유관순", "여", 158.4), Student("이순신", "남", 188.5), ] print("name으로 오름차순 정렬 후 ===>") for student in sorted(students, key=lambda x: x.name): print(student) print("name으로 내림차순 정렬 후 ===>") for student in sorted(students, key=lambda x: x.name, reverse=True): print(student) print("height으로 오름차순 정렬 후 ===>") for student in sorted(students, key=lambda x: x.height): print(student) print("height으로 내림차순 정렬 후 ===>") for student in sorted(students, key=lambda x: x.height, reverse=True): print(student) [결과] name으로 오름차순 정렬 후 ===> Student(name: 강감찬, gender: 남, height: 182.2) Student(name: 유관순, gender: 여, height: 158.4) Student(name: 이순신, gender: 남, height: 188.5) Student(name: 홍길동, gender: 남, height: 176.5) name으로 내림차순 정렬 후 ===> Student(name: 홍길동, gender: 남, height: 176.5) Student(name: 이순신, gender: 남, height: 188.5) Student(name: 유관순, gender: 여, height: 158.4) Student(name: 강감찬, gender: 남, height: 182.2) height으로 오름차순 정렬 후 ===> Student(name: 유관순, gender: 여, height: 158.4) Student(name: 홍길동, gender: 남, height: 176.5) Student(name: 강감찬, gender: 남, height: 182.2) Student(name: 이순신, gender: 남, height: 188.5) height으로 내림차순 정렬 후 ===> Student(name: 이순신, gender: 남, height: 188.5) Student(name: 강감찬, gender: 남, height: 182.2) Student(name: 홍길동, gender: 남, height: 176.5) Student(name: 유관순, gender: 여, height: 158.4)
-
sorted() 함수는
reverse=True/False를 이용해 한 번에 오름/내림차순 정렬할 수 있다. -
sorted() 함수의 항목이 리스트나 딕셔너리 등 반복 가능한 시퀀스형일 때,
key=속성을 이용해 항목의 해당 속성을 특정해 정렬할 수 있다. 이때 각각의 항목의 세부 항목에 대해 sorted() 내에서 한 번에 해야하므로lambda x: x.속성을 이용해 세부 항목에 대해 1차로 추려줄 필요가 있다. -
__repr__함수는 보통 출력할 때 많이 사용하는 함수이다. 꼭 그래야하는 건 아니지만 컨벤션이 그렇다.
