강의 소개 : 객체 지향 프로그래밍에 대해 알려주는 곳은 많지만, 이론부터 실전까지 제대로 알려주는 곳은 많지 않습니다. 이번 토픽을 통해 직접 프로그램을 설계하고 파이썬 코드로 구현해 보면서 객체 지향 프로그래밍을 마스터하세요!
속성(변수)
과 행동(메소드)
으로 이루어짐class ClassName :
pass
# 클래스 이름은 항상 앞 글자를 대문자로 쓴다.
instance1 = ClassName()
instance2 = ClassName()
# 같은 클래스를 호출했어도 서로 다른 인스턴스가 된다.
객체
와인스턴스
는 굳이 따르자면 다르긴 하다!
인스턴스명.속성이름 = "속성(인스턴스 변수)에 넣을 값"
인스턴스명.속성이름
인스턴스명.메소드이름(인스턴스)
= 인스턴스.메소드이름()
인스턴스
를 자동으로 (첫 번째 파라미터)
로 할당함self
라고 써야 한다.__init__
- 인스턴스가 생성될 때 자동으로 호출되는 메소드__str__
- print() 함수가 사용될 때 자동으로 호출되는 메소드클래스명.클래스변수명
클래스명.클래스변수명
or 인스턴스명.클래스변수명
cls
를 쓰며, 클래스가 자동으로 전달된다.@classmethod
데코레이터를 표시@staticmethod
데코레이터를 표시# 인스턴스 메소드
def __str__(self):
return "사용자: {}, 이메일: {}, 비밀번호: ******".format(self.name, self.email)
# 클래스 메소드
@classmethod
def number_of_users(cls):
print("총 유저 수는: {}입니다".format(cls.count))
# 정적 메소드
@staticmethod
def is_valid_email(email_address):
return "@" in email_address
파이썬
가변 타입 객체
- 한 번 생성한 인스턴스의 속성 변경 가능 (ex. 리스트, 딕셔너리, 직접 작성하는 클래스)
불변 타입 객체
- 한 번 생성한 인스턴스의 속성 변경 불가능 (ex. 튜플, 불린, 문자열, 정수 등등)
# 반복적으로 사용하는 코드를 함수로 정의한다
def print_person_info(person_name, person_age, person_gender):
# 사람의 이름, 나이, 성별을 파라미터로 받으면 받은 정보를 이해할 수 있는 문자열로 출력해주는 함수
print("사람 한 명을 소개합니다")
print("{}님은 {}살이고 {}입니다".format(person_name, person_age, person_gender))
def is_underage(person_age):
# 사람의 나이를 파라미터로 받아서 미성년자인지를 리턴해주는 함수
return person_age < 20
# 영훈이의 정보
young_name = "영훈"
young_age = 10
young_gender = "남자"
# 윤수의 정보
yoonsoo_name = "윤수"
yoonsoo_age = 20
yoonsoo_gender = "남자"
# 영훈/윤수 정보 출력
print_person_info(young_name, young_age, young_gender)
print_person_info(yoonsoo_name, yoonsoo_age, yoonsoo_gender)
# 영훈/윤수가 미성년자인지 출력
print(is_underage(young_age))
print(is_underage(yoonsoo_age))
절차 지향 프로그래밍 | 객체 지향 프로그래밍 |
---|---|
프로그램 안에서 서로 관련된 동작들만 묶어서 관리한다. | 관련된 동작들을 관련된 데이터와도 함께 묶어서 관리한다. |
프로그램을 만들 때 데이터와 함수를 합칠 수 없다. | 프로그램을 만들 때 데이터와 함수를 합칠 수 있다. |
프로그램을 명령어들을 순서대로 실행하는 것으로 본다. | 프로그램을 객체들이 순서대로 소통하는 과정으로 본다. |
불린 값에 따라 다른 값을 리턴하는 구문
condition = True
condition_string = "nice" if condition else "not nice"
print(condition_string) # => nice
int_list = [1, 2, 3, 4, 5, 6]
squares = [x**2 for x in int_list]
print(squares) # [1, 4, 9, 16, 25, 36]
“text”.zfill(k)
0
을 채워준다.print("1".zfill(6)) # 000001
print("333".zfill(2)) # 333
다른 문자열로 채우는 방법으로는 rjust()
, ljust()
가 있다.
text1 = “123”.rjust(5, “-“)
text2 = “123”.ljust(5, “-“)
print(text1)
print(text2)
—123
123–
변수, 함수, 클래스 등을 모아놓은 파일
from 모듈명 import 불러올 변수명/함수명/클래스명
from random import randint, uniform
x = randint(1, 20)
y = uniform(0, 1)
# 나의 풀이
class GameCharacter:
# 게임 캐릭터 클래스
def __init__(self, name, hp, power):
# 게임 캐릭터는 속성으로 이름, hp, 공격력을 갖는다
self.name = name
self.hp = hp
self.power = power
def is_alive(self):
# 게임 캐릭터가 살아있는지(체력이 0이 넘는지) 확인하는 메소드
return True if self.hp > 0 else False
def get_attacked(self, damage):
"""
게임 캐릭터가 살아있으면 공격한 캐릭터의 공격력만큼 체력을 깎는 메소드
조건:
1. 이미 캐릭터가 죽었으면 죽었다는 메시지를 출력한다
2. 남은 체력보다 공격력이 더 크면 체력은 0이 된다.
"""
self.damage = damage
if self.hp > 0 :
if self.hp > self.damage :
self.hp = self.hp - self.damage
else :
self.hp = 0
else :
print(f"{self.name}님은 이미 죽었습니다.")
def attack(self, other_character):
# 게임 캐릭터가 살아있으면 파라미터로 받은 다른 캐릭터의 체력을 자신의 공격력만큼 깎는다
other_character.get_attacked(self.power)
def __str__(self):
# 게임 캐릭터의 의미있는 정보를 포함한 문자열을 리턴한다
return f"{self.name}님의 hp는 {self.hp}만큼 남았습니다."
# 정답 풀이
class GameCharacter:
# 게임 캐릭터 클래스
def __init__(self, name, hp, power):
self.name = name
self.hp = hp
self.power = power
def is_alive(self):
return True if self.hp > 0 else False
def get_attacked(self, damage):
if self.is_alive() :
self.hp = self.hp - damage if self.hp >= damage else 0
else :
print(f"{self.name}님은 이미 죽었습니다.")
def attack(self, other_character):
other_character.get_attacked(self.power)
# 공격한 캐릭터의 공격력을 파라미터로 넘기면, 다른 캐릭터에 대미지가 그 공격력만큼 입혀진다.
def __str__(self):
# 게임 캐릭터의 의미있는 정보를 포함한 문자열을 리턴한다
return f"{self.name}님의 hp는 {self.hp}만큼 남았습니다."
return True if self.hp > 0 else False
>> return self.hp > 0
self.damage = damage
를 굳이 선언할 필요가 없다. 이 메소드에서 한 번만 사용하는 메소드이기 때문에.self.is_alive()
이다.# 게임 캐릭터 인스턴스 생성
character_1 = GameCharacter("Ww영훈전사wW", 200, 30)
character_2 = GameCharacter("Xx지웅최고xX", 100, 50)
# 게임 캐릭터 인스턴스들 서로 공격
character_1.attack(character_2)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)
character_2.attack(character_1)
# 게임 캐릭터 인스턴스 출력
print(character_1)
print(character_2)
# 실행 결과
Ww영훈전사wW님은 이미 죽었습니다.
Ww영훈전사wW님의 hp는 0만큼 남았습니다.
Xx지웅최고xX님의 hp는 70만큼 남았습니다.
class Post:
# 게시글 클래스
def __init__(self, date, content):
# 게시글은 속성으로 작성 날짜와 내용을 갖는다
self.date = date
self.content = content
def __str__(self):
# 게시글의 정보를 문자열로 리턴하는 메소드
return "작성 날짜: {}\n내용: {}".format(self.date, self.content)
class BlogUser:
# 블로그 유저 클래스
def __init__(self, name):
"""
블로그 유저는 속성으로 이름, 게시글들을 갖는다
posts는 빈 배열로 초기화한다
"""
self.name = name
self.posts = [] # 블로그 유저의 게시글을 담는다.
def add_post(self, date, content):
# 새로운 게시글 추가 - posts에 추가
new_post = Post(date, content) # Post class에 파라미터 넣어 생성
self.posts.append(new_post) # posts list에 새로운 게시글 추가
def show_all_posts(self):
# 블로그 유저의 모든 게시글 출력
for post in self.posts:
print(post) # print() 에 의해 아래 __str__ 내용 리턴받는다.
def __str__(self):
# 간단한 인사와 이름을 문자열로 리턴
return f"안녕하세요 {self.name}입니다."
# 블로그 유저 인스턴스 생성
blog_user_1 = BlogUser("성태호")
# 블로그 유저 인스턴스 출력(인사, 이름)
print(blog_user_1)
# 블로그 유저 게시글 2개 추가
blog_user_1.add_post("2019년 8월 30일", """
오늘은 내 생일이었다.
많은 사람들이 축하해줬다.
행복했다.
""")
blog_user_1.add_post("2019년 8월 31일", """
재밌는 코딩 교육 사이트를 찾았다.
코드잇이란 곳인데 최고다.
같이 공부하실 분들은 www.codeit.kr로 오세요!
""")
# 블로그 유저의 모든 게시글 출력
blog_user_1.show_all_posts()
# 나의 코드
class Clock :
def __init__(self, hour, min, sec) :
self.hour = hour
self.min = min
self.sec = sec
def set(self, hour, min, sec) :
self.hour = hour
self.min = min
self.sec = sec
def tick(self) :
self.sec += 1
if self.sec >= 60 :
self.sec -= 60
self.min += 1
else :
pass
if self.min >= 60 :
self.min -= 60
self.hour += 1
else :
pass
if self.hour >= 24 :
self.hour -= 24
else :
pass
def __str__(self) :
return str(self.hour).zfill(2) + ":" + str(self.min).zfill(2) + ":" + str(self.sec).zfill(2)
# 1시 30분 48초인 시계 인스턴스 생성
clock = Clock(1, 30, 48)
# 13초를 늘린다
for i in range(13):
clock.tick()
# 시계의 현재 시간 출력
print(clock)
# 모범 답안
class Counter:
"""
시계 클래스의 시,분,초를 각각 나타내는데 사용될 카운터 클래스
"""
def __init__(self, limit):
"""
인스턴스 변수 limit(최댓값), value(현재까지 카운트한 값)을 설정한다.
인스턴스를 생성할 때 인스턴스 변수 limit만 파라미터로 받고, value는 초깃값 0으로 설정한다.
"""
self.limit = limit
self.value = 0
def set(self, new_value):
"""
파라미터가 0 이상, 최댓값 미만이면 value에 설정한다.
아닐 경우 value에 0을 설정한다.
"""
if 0 <= new_value < self.limit:
self.value = new_value
else:
self.value = 0
def tick(self):
"""
value를 1 증가시킨다.
카운터의 값 value가 limit에 도달하면 value를 0으로 바꾼 뒤 True를 리턴한다.
value가 limit보다 작은 경우 False를 리턴한다.
"""
self.value += 1
if self.value == self.limit:
self.value = 0
return True
return False
def __str__(self):
"""
value를 최소 두 자릿수 이상의 문자열로 리턴한다.
일단 str 함수로 숫자형 변수인 value를 문자열로 변환하고 zfill을 호출한다.
"""
return str(self.value).zfill(2)
class Clock:
"""
시계 클래스
"""
HOURS = 24 # 시 최댓값
MINUTES = 60 # 분 최댓값
SECONDS = 60 # 초 최댓값
def __init__(self, hour, minute, second):
"""
각각 시, 분, 초를 나타내는 카운터 인스턴스 3개(hour, minute, second)를 정의한다.
현재 시간을 파라미터 hour시, minute분, second초로 지정한다.
"""
self.hour = Counter(Clock.HOURS)
self.minute = Counter(Clock.MINUTES)
self.second = Counter(Clock.SECONDS)
self.set(hour, minute, second)
def set(self, hour, minute, second):
"""현재 시간을 파라미터 hour시, minute분, second초로 설정한다."""
self.hour.set(hour)
self.minute.set(minute)
self.second.set(second)
def tick(self):
"""
초 카운터의 값을 1만큼 증가시킨다.
초 카운터를 증가시킬 때, 분 또는 시가 바뀌어야하는 경우도 처리한다.
"""
if self.second.tick() :
if self.minute.tick() :
self.hour.tick()
def __str__(self):
"""
현재 시간을 시:분:초 형식으로 리턴한다. 시, 분, 초는 두 자리 형식이다.
예시: "03:11:02"
"""
return f”{self.hour}:{self.minute}:{self.second}“
clock = Clock(1, 30, 48)
clock.set(2, 59, 58)
for i in range(5) :
clock.tick()
print(clock)
if문
에 반드시 else
가 있을 필요는 없다.클래스명()
을 입력하면 된다.()
안에 쓰면 된다.self.시분초
는 최대 시분초를 지정하고, 현재 시간은 set()
을 이용한다.self.set(hour, minute, second)
self.시분초.set(시분초)
self.시분초.tick()
은 다른 클래스 Counter
의 tick()
을 불러온다.강의 소개 : 추상화, 캡슐화, 상속, 다형성… 네 가지 핵심 요소만 제대로 이해하면, 누구나 객체 지향 프로그래밍 코드를 작성할 수 있습니다. 파이썬으로 직접 실습을 하면서 하나씩 공부하다 보면, 객체 지향 프로그래밍을 금방 마스터할 수 있을 거예요.
프로그래머들이 특정 코드를 사용할 때 필수적인 정보를 제외한 세부사항을 가리는 것 (꼭 필요한 부분만 보이도록 드러낸 것)
ex) 변수나 함수, 클래스를 사용하는 것
문서화(docstring, documentation string)
하기 : 주석처리로 정보를 입력하기help(ClassName)
이용하면 정보가 출력된다.Type hinting
이용 가능:type
을 작성하면 된다.-> type
을 작성하면 된다.def find_suggestion_videos(self, number_of_suggestions=5):
# Google docstring
"""유저에게 추천할 영상을 찾아준다
Parameters:
number_of_suggestions (int): 추천하고 싶은 영상 수
(기본값은 5)
Returns:
list: 추천할 영상 주소가 담긴 리스트
"""
# reStructuredText (파이썬 공식 문서화 기준)
"""유저에게 추천할 영상을 찾아준다
:param number_of_suggestions: 추천하고 싶은 영상 수
(기본값은 5)
:type number_of_suggestions: int
:returns: 추천할 영상 주소가 담긴 리스트
:rtype: list
"""
# NumPy/SciPy (통계, 과학 분야에서 쓰이는 Python 라이브러리)
"""유저에게 추천할 영상을 찾아준다
Parameters
----------
number_of_suggestions: int
추천하고 싶은 영상 수 (기본값은 5)
Returns
-------
list
추천할 영상 주소가 담긴 리스트
"""
__변수명
처럼 변수명 앞에 __
를 붙이면, 이 변수는 해당 클래스 내에서만 사용할 수 있게 된다. 클래스 밖에서 접근하면 에러가 발생한다.__
를 붙인 변수나 메소드는 네임 맹글링
되어 _클래스명__변수명
형태의 새로운 이름을 갖게 된다. 따라서 _클래스명__변수명
을 통해서는 접근이 가능하다. (사실 파이썬에서는 언어 자체에서 캡슐화를 지원하지 않는다)getter 메소드
- 변수의 값을 읽는 메소드@property
입력하면 getter 메소드로 사용할 수 있다. 변수/메소드 호출할 때 이 메소드 호출된다.setter 메소드
- 변수의 값을 설정하는 메소드메소드명.setter
입력하면 setter 메소드로 사용할 수 있다. 변수/메소드 값을 설정할 때 이 메소드 호출된다.두 클래스 사이에 부모-자식 관계를 설정하는 것
mro()
- 메소드 검색 순서.isinstance(검사할 인스턴스, 기준 클래스)
- 어떤 인스턴스가 주어진 클래스의 인스턴스인지 알려준다. 불린 값으로 리턴한다.issubclass(검사할 클래스, 기준이 되는 부모 클래스)
- 한 클래스가 다른 클래스의 자식 클래스인지 알려준다. 불린 값으로 리턴한다.부모 클래스의 내용을 자식 클래스에서 덮어쓰는 것
super()
- 부모 클래스의 메소드 호출 가능self
파라미터를 받지 않는다.super().__init__(name, wage)
부모 클래스의 name과 wage 정의 내용을 가져온다.하나의 클래스가 여러 개의 클래스를 상속 받는 것
super()
이용 불가능 - 어떤 부모 클래스를 말하는 것인지 모르니까다중 상속의 단점을 보완하기 위해..
부모클래스끼리 같은 이름의 메소드를 갖지 않도록 하고, 같은 이름의 메소드는 자식클래스에서 오버라이딩을 하여야 한다.
부모클래스를 참조할 때의 애매모호함 때문에 되도록 다중 상속을 하지 않는 것이 바람직하다.
하나의 변수가 서로 다른 클래스의 인스턴스를 가리키는 성질 (인스턴스가 그 메소드를 갖고 있어야만 다형성이 성립된다.)
isinstance()
를 한 번만 써서 사용할 수 있다.추상 클래스
(여러 클래스들의 공통점을 추상화해서 모아놓은 클래스)로 만들어서 사용할 수 있다.from abc import ABC, abstractmethod
class ClassName(ABC)
ABC 상속받기@abstractmethod
추상 메소드(자식 클래스가 반드시 오버라이딩 해야 하는 메소드) 만들기추상 클래스 다중 상속은 일반적으로 많이 사용한다.
다중 상속받는 부모 추상 클래스들이 추상 메소드로만 이뤄져 있으면 아무 문제 없이 다중 상속받을 수 있다. 다중 상속받는 부모 추상 클래스들 간에 이름이 겹치는 일반 메소드가 있으면 일반 클래스를 다중 상속받을 때와 동일한 문제가 생길 수 있다.
LBYL
Look Before You Leap (뛰기 전에 살펴보라)
EAFP
Easier to Ask for Forgiveness than Permission (허락보다 용서가 쉽다) - 일단 먼저 빨리 실행하고, 문제가 생기면 처리한다.
isinstance()
대신 try except
구문 사용강의 소개 : 객체 지향 프로그래밍을 어떻게 하면 더 잘할 수 있을까요? 이번 토픽에서는 객체를 설계하는 다섯 가지 원칙에 대해 배웁니다. 이걸 각 원칙의 앞 글자만 따서 SOLID라고 부르죠. 한 가지씩 살펴보면서, 복잡한 코드도 깔끔하고 수정하기 쉬운 ‘견고한' 코드로 만드는 방법을 알아봅시다.
모든 클래스는 단 한가지의 책임만을 갖고, 클래스 안에 정의되어 있는 모든 기능은 이 하나의 책임을 수행하는데 집중되어 있어야 한다.
즉, 하나의 클래스로 너무 많은 일을 하지 말고, 딱 한 가지 책임만 수행해야 한다. <-> God Object
같이 수정해야될 것들은 묶고, 따로 수정해야될 것들은 분리하면 된다.
클래스는 확장에 열려있어야 하며, 수정에는 닫혀있어야 한다. 즉, 기존 클래스의 코드를 수정하지 않고도 기능을 확장할 수 있어야 한다.
이는 추상 클래스 활용으로 해결된다.
부모 클래스의 인스턴스를 사용하는 위치에 자식 클래스의 인스턴스를 대신 사용했을 때 코드가 원래 의도대로 작동해야 한다. 즉, 부모 클래스의 행동규약을 자식 클래스가 위반하지 말아야 한다는 것이다.
자식 클래스가 오버라이딩을 잘못 하는 경우,
1. (형식적인 측면) 자식 클래스가 부모 클래스의 변수의 타입을 바꾸거나 메소드의 파라미터 또는 리턴값의 타입이나 갯수를 바꾸는 경우
2. (내용적인 측면) 자식 클래스가 부모 클래스의 의도와 다르게 메소드를 오버라이딩 하는 경우
자식 클래스가 부모 클래스의 행동규약을 위반하게 된다.
추상 클래스 중에서 추상 메소드만 있고 일반 메소드는 없는 것을 인터페이스라고 한다.
클래스가 사용하지 않을 메소드에 의존(메소드를 갖는다)할 것을 강요하면 안 된다. 즉, 클래스가 나중에 사용하지도 않을 메소드를 가지도록 강제하면 안 된다.
상속받는 자식 클래스가 자신에게 필요하지도 않은 메소드를 굳이 오버라이딩 하게 되면 안 된다.
이를 해결하려면 기능, 역할에 따라 더 작은 추상클래스 역할 인터페이스(role interface)
로 분리해야 한다.
상위 모듈은 하위 모듈의 구현 내용에 의존하면 안 된다. 상위 모듈과 하위 모듈 모두 추상화된 내용에 의존해야 한다.
즉, 상위 모듈이 하위 모듈을 사용할 때 직접 인스턴스를 가져다가 쓰지 말라는 것이다. 의존하게 되면 하위 모듈에 변화가 있을 때마다 상위 모듈의 코드를 자주 바꿔줘야 하기 때문이다.
이에 대한 해결책은 추상 클래스로 상위 모듈과 하위 모듈 사이에 추상화 레이어를 만드는 것이다.