참고도서 : 데이터 과학을 위한 파이썬 프로그래밍, 최상철
객체 지향 프로그래밍을 배우는 이유
- 다른 사람이 작성한 코드를 어떻게 사용하면 좋을 지에 대한 답
- 남이 만든 코드를 재 사용하고 싶을 때 사용하는 대표적인 방법
- 하나의 단일 프로그램을 객체라고 하는 코드로 만들어 재사용할 수 있도록
객체와 클래스
- 객체 : 실생활에 존재하는 실제적인 물건 또는 개념
- 속성과 행동으로 구성
- 클래스 : 객체가 가져야 할 기본 정보를 담은 코드로 일종의 설계도 코드
객체 지향 프로그래밍은 이러한 객체의 개념을 활용하여 프로그램을 표현하는 기법
속성 → 변수, 행동 → 함수로 정의됨
ex) 인공지능 축구 프로그램
어떤 종류의 객체가 필요한지
- 하나의 프로그램에서 여러 개의 객체가 사용될 수 있음 → 객체들을 위한 설계도를 만들어야 함 (클래스 : 객체가 가져야 할 기본 정보를 담은 코드, 설계도 코드)
- 축구 선수라는 객체를 만든다면 선수의 이름, 포지션, 소속팀 등은 기본적 속성
- 이러한 정보를 클래스에 → 객체에 선수 이름을 할당 (인스턴스)
잘 만든 붕어빵틀 → 새로운 종류의 다양한 붕어빵
잘 만든 클래스 코드 → 다양한 종류의 인스턴스 생성
클래스 구현하기
💡 상속 : 기존에 만든 클래스의 특징을 그대로 이어받아 사용하는 것
객체 지향 프로그래밍의 장점 중 하나는 재사용이고, 이를 손쉽게 하는 것이 상속
💡 클래스 이름을 선언할 대는 첫 글자와 중간 글자가 대문자임
속성의 선언
파이썬 클래스의 대표적인 예약 함수는 init(), 이 외에 str**(),** add() 등이 존재
이러한 함수들은 약속된 형태의 작업을 수행
class SoccerPlayer(object):
def__init__(self, name, position, back_number):
self.name = name
self.position = position
self.back_number = back_numbe
- init() : 속성에 대한 정보 선언, 클래스에서 사용할 변수를 정의하는 함수
- init() 함수의 첫 번째 매개변수는 반드시 self 변수를 사용해야 함
- self 변수는 클래스에서 생성된 인스턴스에 접근하는 예약어, 생성된 인스턴스를 지정하는 변수
- self 뒤의 매개변수들은 실제로 클래스가 가진 속성self.name = name → 할당 코드
- 생성된 인스턴스에 있는 name 변수에 매개변수로 입력된 name이라는 값을 할당한다는 뜻
- 이 값들은 실제로 생성되는 인스턴스에 할당됨
- 클래스의 변수는 ‘self.변수이름’으로 init() 함수에서 자유롭게 생성 가능But, init() 내에서만 새로운 속성을 생성해야만 다른 프로그래머가 클래스를 사용할 때 헷갈리지 않을 것
- 파이썬은 인터프리터 언어이고 동적 타이핑 언어이므로, 클래스 내 다른 함수에서도 이와 같은 속성의 생성이 가능
함수의 선언
함수는 클래스가 할 수 있는 다양한 동작을 정의할 수 있음
class SoccerPlayer(object):
def change_back_number(self, new_number):
print("선수의 등번호를 변경한다: From %d to %d" % (self.back_number, new_number))
self.back_number = new_number
기존 함수와 가장 큰 차이점은 self를 매개변수에 반드시 넣어야 한다는 것
self가 있어야만 실제로 인스턴스가 사용할 수 있는 함수로 선언됨
_(underscore)의 쓰임
개수에 따라 쓰임이 나뉨
- 1개
이후로 쓰이지 않을 변수에 특별한 이름을 부여하고 싶지 않을 때
>>> for _ in range(10):
print("Hello, World")
Hello, World
Hello, World
Hello, World
Hello, World
Hello, World
Hello, World
Hello, World
Hello, World
Hello, World
Hello, World
횟수를 세는 _ 변수는 특별한 용도가 없으므로 뒤에서 사용되지 않음
_를 임의의 변수명 대신에 사용
2. 2개
특수한 예약 함수나 변수
- str() : 클래스로 인스턴스를 생성했을 때, 그 인스턴스 자체를 print() 함수로 화면에 출력하면 나오는 값을 뜻함
- init()
인스턴스 사용하기
클래스에서 실제적인 데이터가 입력되어 사용할 수 있는 형태의 객체
SoccerPlayer라는 클래스는 기본 설계도이므로 선수의 이름이나 등 번호가 할당된 것이 아님
- 클래스 이름 호출하고, init() 함수의 매개변수에 맞추어 값을 입력 (초깃값 지정)
- self 변수에는 아무런 값도 할당하지 않음
- jinhyun이라는 이름의 인스턴스가 SoccerPlayer라는 클래스를 기반으로 생 성
- jinhyun이라는 인스턴스 자체가 SoccerPlayer 클래스에서 self에 할당됨
# 전체 SoccerPlayer 코드
>>> class SoccerPlayer(object):
def __init__(self, name, position, back_number):
self.name = name
self.position =position
self.back_number = back_number
def change_back_number(self, new_number):
print("선수의 등번호를 변경한다: From %d to %d" % (self.back_number, new_number))
self.back_number = new_number
def __str__(self):
return "Hello, My name is %s. I play in %s in center." % (self.name, self.position)
# SoccerPlayer를 사용하는 instance 코드
>>> jinhyun = SoccerPlayer("Jinhyun", "MF", 10)
>>>
>>> print("현재 선수의 등번호는:", jinhyun.back_number)
>>> jinhyun.change_back_number(5)
>>> print("현재 선수의 등번호는:", jinhyun.back_number)
>>> print(jinhyun) # __str__() 함수로 선언된 부분이 print() 함수를 사용하면 반환되는 함수
현재 선수의 등번호는: 10
선수의 등번호를 변경한다: From 10 to 5
현재 선수의 등번호는: 5
Hello, My name is Jinhyun. I play in MF in center.
인스턴스가 생성된 후에는 해당 인스턴스의 이름으로 값을 할당하거나 함수를 부르면 되지만, 클래스 내에서는 self로 호출됨
= 생성된 인스턴스인 jinhyun과 클래스 내 self가 같은 역할
클래스를 사용하는 이유
- 자신의 코드를 다른 사람이 손쉽게 사용할 수 있도록 설계하기 위함
- 코드가 데이터 저장 뿐만 아니라 데이터를 변환하거나 데이터베이스에 저장하는 등의 다른 역할을 해야 할 때도 있음 → 하나의 객체로 생성해 다른 사람들에게 배포하는 것이 훨씬 작업이 쉬움
- 단순히 데이터를 저장하기 위한 수단 → 이차원 리스트, Namedtuple 형태를 사용해도 문제 없음
- 코드를 좀 더 손쉽게 선언할 수 있음
- 이차원 리스트로 선언할 수 있는 것을 객체 지향 프로그래밍의 개념을 적용해 좀 더 명확하게 저장된 데이터를 확인할 수 있음
- 다른 사람들이 결과를 사용할 때 데이터가 무엇을 위한 데이터인지 명확히 알 수 있음
# 데이터
>>> names = ["Messi", "Ramos", "Ronaldo", "Park", "Buffon"]
>>> positions = ["MF", "DF", "CF", "WF", "GK"]
>>> numbers = [10, 4, 7, 13, 1]
# 이차원 리스트
>>> players = [[name, position, number] for name, position, number in zip(names, positions, numbers)]
>>> print(players)
[['Messi', 'MF', 10], ['Ramos', 'DF', 4], ['Ronaldo', 'CF', 7], ['Park', 'WF', 13], ['Buffon', 'GK', 1]]
>>> print(players[0])
['Messi', 'MF', 10]
# 전체 SoccerPlayer 코드
>>> class SoccerPlayer(object):
def __init__(self, name, position, back_number):
self.name = name
self.position = position
self.back_number = back_number
def change_back_number(self, new_number):
print("선수의 등번호를 변경한다: From %d to %d" % (self.back_number, new_number))
self.back_number = new_number
def __str__(self):
return "Hello, My name is %s. I play in %s in center." % (self.name,self.position)
# 클래스-인스턴스
>>> player_objects = [SoccerPlayer(name, position, number) for name, position, number in zip(names, positions, numbers)]
>>> print(player_objects[0])
Hello, My name is Messi. I play in MF in center.
‘객체 지향 프로그래밍 만능주의’를 주의하라
모든 코드에 객체 지향 프로그래밍의 개념을 사용해 설계하고자 하는 욕심이 생김
쓰고 버리게 되는 코드를 개발하는 경우도 많음
파이썬은‘어떻게 하면 쉽게 코딩할 수 있을까’에 대한 고민 끝에 나온 언어
새로운 방법으로 코딩하는 것보다 필요한 방법을 적재적소에 적용 시켜 손쉽게 문제를 해결해야 함
객체 지향 프로그래밍의 기본 철학 : 실생활을 모델링
ex) 상속, 다형성, 가시성
상속
- 무엇인가를 내려받는 것
- 부모 클래스에 정의된 속성과 메서드를 자식 클래스가 물려받아 사용하는 것
>>> class Person(object):
pass
object가 Person 클래스의 부모클래스
object : 파이썬에서 사용하는 가장 기본 객체
파이썬 언어가 객체 지향 프로그래밍이므로 모든 변수는 객체임
>>> a = "abc"
>>> type(a)
<class 'str'>
지금까지 문자열형이라고 불렀지만 내부적으로는 객체로 처리되어있음
파이썬의 모든 객체는 object 객체를 상속함
>>> class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
>>>
>>> class Korean(Person): # 간단히 상속받기
pass # 별도의 내용 없이 클래스만 존재
>>>
>>> first_korean = Korean("Sungchul", 35)
>>> print(first_korean.name)
Sungchul
Person 클래스를 먼저 생성
Korean 클래스를 만들면서 Person 클래스를 상속받음
Korean 클래스는 별도의 생성자는 없지만, Person 클래스가 가진 생성자를 그대로 사용하여 인스턴스를 만듦
- 사각형이 클래스, 화살표는 각 클래스의 상속 관계
- 상속을 하게 되면 부모 클래스보다 자식 클래스의 정보가 더 구체화 됨
- 부모 객체에는 일반적 기능을, 자식 객체에는 상세한 기능을 넣어야 함
- 부모 객체보다 자식 객체에 좀 더 많은 정보를 줌 (부모 클래스의 메서드를 재정의)
>>> class Person(object): # 부모 클래스 Person 선언
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def about_me(self): # 메서드 선언
print("저의 이름은", self.name, "이고요, 제 나이는", str(self.age), "살입니다.")
>>>
>>>
>>> class Employee(Person): # 부모 클래스 Person으로부터 상속
def __init__(self, name, age, gender, salary, hire_date):
super().__init__(name, age, gender) # 부모 객체 사용
self.salary = salary
self.hire_date = hire_date # 속성값 추가
def do_sork(self): # 새로운 메서드 추가
print("열심히 일을 한다.")
def about_me(self): # 부모 클래스 함수 재정의
super().about_me() # 부모 클래스 함수 사용
print("제 급여는", self.salary, "원이고, 제 입사일은", self.hire_date, "입니다.")
Person 클래스 → 단순히 사람에 대한 정보 정의
Employee 클래스 → 일하는 시간과 월급에 대한 변수 추가 (init()함수 재정의)
- 오버라이딩 : 상속 시 함수 이름과 필요한 매개변수는 그대로 유지하면서 함수의 수행 코드를 변경하는 것
- super().init(매개변수) : 부모 클래스의 init() 함수를 그대로 사용
- 기존 함수를 그대로 사용하려면 별도의 init() 함수를 만들지 않아도 됨
- But, 기존 함수를 사용하면서 새로운 내용을 추가하기 위해서 사용
자식 클래스에만 필요한 새로운 함수를 생성할 수 있음
다양한 상속 기능 지원 → ex) 다중상속 : 1개 이상의 클래스 특징을 상속
다형성
- 같은 이름의 메서드가 다른 기능을 하는 것
- ex) 부모 클래스와 자식 클래스가 서로 다른 about_me 함수 구현
>>> n_crawler = NaverCrawler()
>>> d_crawler = DaumCrawler()
>>> cralwers = [n_crawler, d_crawler]
>>> news = []
>>> for cralwer in cralwers:
news.append (eralwer.do_crawling())
부모 Crawler 클래스 → do_crawling 함수 생성
Crawler 클래스를 상속시켜 NaverCrawler와 DaumCrawler를 만듦
- 두 자식 클래스는 모두 do_crawling 함수를 갖게 됨. But, 구현 내용은 다름
- 각 클래스에서 구현되는 내부 로직에 차이가 존재하는데 이를 함수의 다형성이라고 함
- 사용자는 함수의 이름만 알면 같은 형태로 사용 가능하지만, 개발자의 입장에서는 내부적인 구현을 각 클래스 별로 다르게 개발할 필요가 있음
- 다형성을 사용하면 프로그램을 작성할 때 사용자가 좀 더 쉽게 클래스를 사용 가능
>>> class Animal:
def __init__(self, naem):
self.name = name
def talk(self):
raise NotImplementedError("Subclass must implement abstract method")
>>> class Cat(Animal):
def talk(self):
return 'Meow!'
>>> class Dog(Animal):
def talk(self):
return 'Woof! Woof!'
>>> animals = [Cat('Missy'), Cat('Mr.Mistoffelees'), Dog('Lassie')]
>>> for animal in animals:
print(animal.name + ': ' +animal.talk())
Missy: Meow!
Mr.Mistoffelees: Meow!
Lassie: Woof! Woof!
부모 클래스는 Animal이며, Cat과 Dog는 Animal 클래스를 상속받음
핵심 함수는 talk으로, 두 동물 클래스의 역할이 다른 것을 확인할 수 있음
- NotImplementedError : 자식 클래스에만 해당 함수를 사용할 수 있도록
가시성
- 객체의 정보를 볼 수 있는 레벨을 조절하여 객체의 정보 접근을 숨기는 것
- 캡슐화 : 객체의 매개변수 인터페이스만 명확히 알면 사용 가능
- 정보 은닉 : 필요한 정보는 숨겨야 함
→ 외부에서 쉽게 사용 가능하고, 코드의 세부적인 내용은 모르게 한다는 측면에서 비슷한 의미로 사용됨
캡슐화를 사용해야 하는 이유는 무엇일까?
- 클래스 간 간섭 및 정보 공유를 최소화하여 개별 클래스가 단독으로도 잘 동작할 수 있도록 해야하기 때문
- 각 클래스가 강하게 연결되어 있다면 독립적으로 사용하기 어려움
- 사용자 입장에서는 상세한 내용을 모르더라도 인터페이스를 이해하면 클래스를 쉽게 사용할 수 있음
파이썬에서는 이러한 개념을 가시성이라는 이름으로 적용시키고. 정보 은닉을 어떻게 할 것인가를 코드 레벨에서 조절함
❓ Product 를 Inventory 객체에 추가
Inventory에는 오직 Product 객체만 들어감
Inventory에 Product기 몇 개인지 확인이 필요함
Inventory에 Product items는 직접 접근이 불가함
>>> class Prodcut(object):
pass
>>>
>>> class Inventory(object):
def __init__(self):
self.__items = []
def add_new_item(self, product):
if type(prodcut) == Product:
self.__items.append(prodct)
print("new item added")
else:
raise ValueError("Invalid Item")
def get_number_of_items(self):
return len(self.__items)
>>> my_inventory = Inventory()
>>> my_inventory.add_new_item(Prodcut())
>>> my_inventroy.add_new_item(Product())
>>>
>>> my_inventory.__items
new item added
new item added
Traceback (most recent call last):
File "visibilityl.py", line 20, in <modul>
my_inventory_iterns
AttributeError: 'Inventory' object has no attribute '__items'
Inventory 객체에 add_new_item() 함수를 사용하여 새롭게 생성된 Product 객체를 넣어줌
자료형을 비교하여 해당 객체가 Product 객체인지 확인하고, 그렇지 않을 경우 ValueError가 발생
items는 Product 객체가 들어가는 공간으로, get_number_of_items()를 사용하여 총 객체의 개수를 반환
- __items 변수 : Inventory를 저장하는 공간으로, add_new_item()을 통해 Product 객체를 넣을 수 있음
- add_new_item()이 아니라 직접 해당 객체에 접근해 새로운 값을 추가하려고 한다면 오류 발생클래스 내부용으로만 변수를 사용 → ‘__변수명’
- 가시성을 클래스 내로 한정하면서 값이 다르게 들어가는 것을 막을 수 있음(정보 은닉)
- _(특수 역할을 하는 예약 문자)가 클래스에서 변수에 두 개가 붙어있어 사용될 클래스 내부에서만 접근할 수 있고, 외부에는 호출하여 사용하지 못하기 때문
이러한 정보를 클래스 외부에서 사용하기 위해서는?
- @property 사용
- 데코레이터 : 클래스의 각 메서드 상단에 삽입하여 해당 메서드의 기능을 추가하는 파이썬 문법
>>> class Inventory(object):
def __init__(self):
self.__items = [] # privae 변수로 선언 (타인이 접근 못 함)
@property # property 데코레이터 (숨겨진 변수 반환)
def items(self):
return self.__items
>>> my_inventory = Inventory()
>>> items = my_inventory.items
>>> items.append(Product())
다른 코드는 그대로 유지하고 마지막에 items라는 이름으로 메서드를 만들면서 @property 를 메서드 상단에 입력
외부에서 사용할 변수인 __items를 반환
** __items 변수의 원래 이름이 아닌 items로 호출
@property를 붙인 함수 이름으로 실제 __items를 사용할 수 있는 것이
이는 기존 private 변수를 누구나 사용할 수 있는 public 변수로 바꾸는 방법 중 하나
'스터디 > 파이썬 스터디 강의자료' 카테고리의 다른 글
[1팀/한규림] 8차시 파이썬 스터디 - 객체 지향 프로그래밍 (0) | 2023.05.18 |
---|---|
[3팀/김규리] 8차시 파이썬 스터디 - 객체 지향 프로그래밍 (0) | 2023.05.18 |
[4팀/이제은] 8차시 파이썬 스터디 - 객체 지향 프로그래밍 (0) | 2023.05.18 |
[1팀/허서원] 7차시 파이썬 스터디 - 자료구조 (0) | 2023.05.11 |
[1팀/한규림] 7차시 파이썬 스터디 - 자료구조 (0) | 2023.05.11 |