OOP로 Python 구구단 앱 빌드
본 글에서는 파이썬의 객체 지향 프로그래밍(OOP) 기법을 활용하여 구구단 학습 앱을 구축하는 과정을 상세히 소개합니다.
주요 OOP 개념을 숙지하고, 이를 실제 작동하는 애플리케이션에 적용하는 방법을 익히는 데 초점을 맞춥니다.
파이썬은 다중 패러다임 프로그래밍 언어로서, 개발자가 특정 상황이나 문제에 가장 적합한 접근 방식을 자유롭게 선택할 수 있도록 지원합니다. 객체 지향 프로그래밍은 확장 가능한 애플리케이션을 개발하는 데 수십 년간 널리 사용되어 온 강력한 패러다임입니다.
OOP의 기본 원리
파이썬에서 OOP의 핵심 요소인 "클래스"에 대해 간략히 살펴보겠습니다.
클래스는 객체의 구조와 동작을 정의하는 일종의 설계도입니다. 이 설계도를 바탕으로 개별 객체인 인스턴스를 생성할 수 있습니다. 각 인스턴스는 클래스 정의에 따라 고유한 속성과 메서드를 갖습니다.
예를 들어, 제목과 색상 속성을 가진 간단한 책 클래스는 다음과 같이 정의할 수 있습니다.
class Book:
def __init__(self, title, color):
self.title = title
self.color = color
Book 클래스의 인스턴스를 생성하려면 클래스를 호출하고 필요한 인수를 전달해야 합니다.
# Book 클래스의 인스턴스 생성
blue_book = Book("파란 아이", "파랑")
green_book = Book("개구리 이야기", "초록")
이제 다음 코드를 실행하여 객체의 유형을 확인해 보겠습니다.
blue_book과 green_book 인스턴스의 유형을 확인하면 "Book" 클래스임을 알 수 있습니다.
# 책의 유형 출력 print(type(blue_book)) # <class '__main__.Book'> print(type(green_book)) # <class '__main__.Book'>
이 기본 개념들을 이해했으니, 이제 본격적으로 프로젝트를 시작해 볼까요? 😃
프로젝트 개요
개발자 또는 프로그래머로서, 실제 코드를 작성하는 시간은 전체 업무 시간의 일부에 불과하다고 합니다. 뉴스 스택에 따르면, 개발자는 코드 작성 또는 리팩토링에 전체 시간의 약 1/3 정도만 사용합니다.
나머지 2/3 시간은 다른 개발자의 코드를 읽거나, 문제 분석에 사용됩니다.
따라서 본 프로젝트에서는 문제 설명을 통해 앱을 설계하고 개발하는 전 과정을 자세히 살펴보겠습니다. 이는 문제 정의부터 실제 코드 적용까지의 전체 과정을 이해하는 데 도움이 될 것입니다.
초등학교 교사가 8세에서 10세 사이 학생들의 곱셈 실력을 향상시키기 위한 게임을 원합니다.
이 게임에는 생명력과 점수 시스템이 있어야 하며, 학생은 3개의 생명으로 시작하여 일정 점수에 도달하면 승리합니다. 만약 학생이 모든 생명력을 소진하면 "패배" 메시지가 표시되어야 합니다.
게임은 무작위 곱셈 모드와 구구단 모드, 두 가지 모드를 지원해야 합니다.
무작위 곱셈 모드에서는 1부터 10까지의 숫자 중에서 무작위로 생성된 곱셈 문제를 제시하고, 학생이 정답을 맞히면 점수를 얻습니다. 오답을 제출하면 생명력을 잃고 게임은 계속됩니다. 학생이 5점에 도달하면 게임에서 승리합니다.
구구단 모드에서는 1부터 10까지의 구구단을 순서대로 제시하고, 학생이 곱셈 결과를 입력해야 합니다. 3번의 오답을 제출하면 패배하지만, 2개의 구구단을 성공적으로 완료하면 게임에서 승리합니다.
요구 사항이 다소 복잡해 보일 수 있지만, 이 글을 통해 모든 것을 명확하게 해결해 드리겠습니다. 😁
분할 정복 전략
프로그래밍에서 가장 중요한 능력 중 하나는 문제를 효과적으로 해결하는 능력입니다. 코드를 바로 작성하기 전에 체계적인 계획이 필요합니다.
저는 항상 더 큰 문제를 작고 효율적으로 해결할 수 있는 하위 문제로 분할할 것을 권장합니다.
따라서 게임 개발을 시작할 때, 가장 중요한 요소로 게임을 나누어 분석합니다. 이러한 하위 문제는 보다 쉽게 해결할 수 있을 것입니다.
이를 통해 모든 코드를 작성하고 통합하는 방법에 대한 명확한 이해를 얻을 수 있습니다.
게임의 구조를 간략하게 시각화해 보겠습니다.

위 그림은 앱의 객체 간의 관계를 보여줍니다. 보시다시피, 핵심 객체는 무작위 곱셈과 구구단입니다. 두 객체 모두 점수와 생명력이라는 공통적인 속성을 공유합니다.
이러한 모든 정보를 바탕으로 이제 코드 작성을 시작해 보겠습니다.
부모 클래스 생성
객체 지향 프로그래밍에서는 코드 중복을 최소화하는 것이 매우 중요합니다. 이러한 원칙을 DRY(반복하지 마십시오)라고 합니다.
참고: 코드의 양을 줄이는 것이 목표가 아니라(코드의 품질은 그 양으로 측정해서는 안 됩니다), 가장 자주 사용되는 로직을 추상화하는 것이 목표입니다.
위에서 설명한 것처럼, 애플리케이션의 부모 클래스는 두 자식 클래스의 기본 구조와 동작을 정의해야 합니다.
코드를 통해 확인해 봅시다.
class BaseGame:
# 메시지를 가운데 정렬하기 위한 길이
message_lenght = 60
description = ""
def __init__(self, points_to_win, n_lives=3):
"""부모 게임 클래스
Args:
points_to_win (int): 게임을 완료하기 위한 목표 점수
n_lives (int): 학생이 갖는 생명력. 기본값은 3.
"""
self.points_to_win = points_to_win
self.points = 0
self.lives = n_lives
def get_numeric_input(self, message=""):
while True:
# 사용자 입력 받기
user_input = input(message)
# 입력값이 숫자이면 반환
# 숫자가 아니면 메시지 출력 후 반복
if user_input.isnumeric():
return int(user_input)
else:
print("입력값은 숫자여야 합니다.")
continue
def print_welcome_message(self):
print("파이썬 구구단 게임".center(self.message_lenght))
def print_lose_message(self):
print("생명력을 모두 소진하셨습니다.".center(self.message_lenght))
def print_win_message(self):
print(f"축하합니다! {self.points}점에 도달하셨습니다.".center(self.message_lenght))
def print_current_lives(self):
print(f"현재 생명력: {self.lives}\n")
def print_current_score(self):
print(f"\n현재 점수: {self.points}")
def print_description(self):
print("\n\n" + self.description.center(self.message_lenght) + "\n")
# 기본 실행 메서드
def run(self):
self.print_welcome_message()
self.print_description()
꽤 큰 클래스처럼 보이지만, 자세히 설명하겠습니다.
먼저 클래스 속성과 생성자를 살펴보겠습니다.

기본적으로 클래스 속성은 클래스 내부에서 생성되지만, 생성자나 메서드 외부에서 정의된 변수입니다.
인스턴스 속성은 생성자 내부에서만 생성되는 변수입니다.
이 두 가지의 주요 차이점은 범위입니다. 즉, 클래스 속성은 클래스와 인스턴스 객체 모두에서 액세스할 수 있습니다. 반면에 인스턴스 속성은 인스턴스 객체에서만 액세스할 수 있습니다.
game = BaseGame(5) # 클래스에서 게임 메시지 길이 클래스 속성에 접근 print(game.message_lenght) # 60 # 클래스에서 메시지 길이 클래스 속성에 접근 print(BaseGame.message_lenght) # 60 # 인스턴스에서 점수 인스턴스 속성에 접근 print(game.points) # 0 # 클래스에서 점수 인스턴스 속성에 접근 print(BaseGame.points) # Attribute error
다른 글에서 이 주제를 더 자세히 살펴보겠습니다. 꾸준히 관심을 가져주세요.
get_numeric_input 함수는 사용자가 숫자가 아닌 입력을 제공하는 것을 방지하는 역할을 합니다. 이 메서드는 유효한 숫자 입력을 받을 때까지 사용자에게 입력을 요청하도록 설계되었습니다. 이 메서드는 자식 클래스에서 재사용할 것입니다.

print 메서드를 사용하여 게임에서 이벤트가 발생할 때마다 반복적인 작업을 줄일 수 있습니다.
마지막으로 run 메서드는 무작위 곱셈 및 구구단 클래스가 사용자와 상호 작용하고 모든 기능을 작동시키는 데 사용하는 래퍼입니다.

자식 클래스 생성
애플리케이션의 기본 구조와 일부 기능을 설정하는 부모 클래스를 만들었으므로, 이제 상속 기능을 사용하여 실제 게임 모드 클래스를 만들 차례입니다.
무작위 곱셈 클래스
이 클래스는 게임의 "첫 번째 모드"를 구현합니다. 사용자가 1부터 10까지의 숫자에서 무작위로 생성된 곱셈 문제를 풀 수 있도록 해주는 random 모듈을 사용합니다. random 모듈과 관련된 유용한 자료는 다음 링크에서 확인하세요 😉.
import random # 무작위 연산을 위한 모듈
class RandomMultiplication(BaseGame):
description = "무작위 곱셈 문제를 정확하게 푸는 게임입니다.\n5점에 도달하면 승리하고, 모든 생명력을 잃으면 패배합니다."
def __init__(self):
# 승리 조건은 5점입니다.
# "points_to_win" 인수에 5를 전달합니다.
super().__init__(5)
def get_random_numbers(self):
first_number = random.randint(1, 10)
second_number = random.randint(1, 10)
return first_number, second_number
def run(self):
# 상위 클래스를 호출하여 환영 메시지 출력
super().run()
while self.lives > 0 and self.points_to_win > self.points:
# 무작위 숫자 두 개를 가져옴
number1, number2 = self.get_random_numbers()
operation = f"{number1} x {number2}: "
# 사용자에게 연산 결과를 입력하도록 요청
# 값 오류를 방지
user_answer = self.get_numeric_input(message=operation)
if user_answer == number1 * number2:
print("\n정답입니다.\n")
# 점수 증가
self.points += 1
else:
print("\n오답입니다.\n")
# 생명력 감소
self.lives -= 1
self.print_current_score()
self.print_current_lives()
# 게임이 끝나면 실행됨
# 모든 조건이 거짓일 때
else:
# 최종 메시지 출력
if self.points >= self.points_to_win:
self.print_win_message()
else:
self.print_lose_message()
또 다른 큰 클래스가 있습니다. 😅 하지만 앞에서 언급했듯이, 중요한 것은 코드의 양이 아니라 얼마나 읽기 쉽고 효율적인 코드인지입니다. 파이썬의 가장 큰 장점은 일반 영어를 사용하는 것처럼 개발자가 깨끗하고 읽기 쉬운 코드를 작성할 수 있다는 점입니다.
이 클래스에서 헷갈릴 수 있는 부분은 하나뿐이지만 최대한 간단하게 설명하겠습니다.
# 부모 클래스
def __init__(self, points_to_win, n_lives=3):
"...
# 자식 클래스
def __init__(self):
# 승리 조건은 5점입니다.
# "points_to_win" 인수에 5를 전달합니다.
super().__init__(5)
자식 클래스의 생성자는 동시에 부모 클래스(BaseGame)를 참조하는 슈퍼 함수를 호출합니다. 기본적으로 파이썬에게 이렇게 지시합니다.
상위 클래스의 "points_to_win" 속성을 5로 채우세요!
생성자 내부에서 super를 호출하기 때문에 super().__init__() 부분 내부에 self를 추가할 필요는 없습니다.
또한 run 메서드에서도 super 함수를 사용하고 있으며, 해당 코드에서 무슨 일이 일어나는지 확인해 보겠습니다.
# 기본 실행 메서드
# 부모 메서드
def run(self):
self.print_welcome_message()
self.print_description()
def run(self):
# 상위 클래스를 호출하여 환영 메시지 출력
super().run()
.....
부모 클래스의 run 메서드는 환영 메시지와 설명 메시지를 출력하는 것을 알 수 있습니다. 그러나 해당 기능을 유지하면서 자식 클래스에 추가 기능을 추가하는 것이 좋은 방법입니다. 따라서, super를 사용하여 자식 메서드의 코드를 실행하기 전에 부모 메서드의 모든 코드를 실행합니다.
run 함수의 나머지 부분은 매우 간단합니다. 사용자에게 답해야 하는 문제 메시지와 함께 숫자를 묻습니다. 그런 다음 결과를 실제 곱셈 값과 비교하여 일치하면 점수를 1점 추가하고 생명력을 빼지 않습니다.
while-else 루프를 사용하고 있다는 점을 언급할 가치가 있습니다. 이는 이 글의 범위를 벗어나지만 며칠 안에 이 주제에 대한 글을 게시하겠습니다.
마지막으로, get_random_numbers는 지정된 범위 내에서 임의의 정수를 반환하는 random.randint 함수를 사용합니다. 그런 다음 두 개의 임의 정수로 구성된 튜플을 반환합니다.
구구단 클래스
"두 번째 모드"는 구구단 형식으로 게임을 표시해야 하며, 사용자가 적어도 2개의 구구단에 대해 정답을 입력해야 합니다.
이를 위해 super 함수를 다시 사용하여 부모 클래스의 points_to_win 속성을 2로 수정합니다.
class TableMultiplication(BaseGame):
description = "구구단을 모두 정확하게 푸는 게임입니다.\n2개의 구구단을 모두 풀면 승리합니다."
def __init__(self):
# 2개의 구구단을 완료해야 승리
super().__init__(2)
def run(self):
# 환영 메시지 출력
super().run()
while self.lives > 0 and self.points_to_win > self.points:
# 1부터 10까지의 숫자 중에서 무작위 숫자를 가져옴
number = random.randint(1, 10)
for i in range(1, 11):
if self.lives <= 0:
# 사용자가 모든 생명력을 소진하면 게임을 종료
self.points = 0
break
operation = f"{number} x {i}: "
user_answer = self.get_numeric_input(message=operation)
if user_answer == number * i:
print("훌륭합니다! 정답입니다.")
else:
print("오답입니다.")
self.lives -= 1
self.points += 1
# 게임이 끝나면 실행됨
# 모든 조건이 거짓일 때
else:
# 최종 메시지 출력
if self.points >= self.points_to_win:
self.print_win_message()
else:
self.print_lose_message()
보시다시피, 이 클래스의 run 메서드만 수정했습니다. 이것이 바로 상속의 마법입니다. 여러 곳에서 사용되는 로직을 한 번만 작성하고 재사용할 수 있습니다. 😅
run 메서드에서 for 루프를 사용하여 1부터 10까지의 숫자를 가져오고, 사용자에게 표시되는 문제를 생성합니다.
생명력이 소진되거나 승리에 필요한 점수에 도달하면 while 루프가 중단되고 승패 메시지가 표시됩니다.
물론 게임의 두 가지 모드를 모두 만들었지만, 지금까지 프로그램을 실행하면 아무 일도 일어나지 않습니다.
이제 모드 선택 기능을 구현하고, 선택에 따라 클래스를 인스턴스화하여 프로그램을 완성해 보겠습니다.
선택 기능 구현
사용자가 플레이할 모드를 선택할 수 있도록 하겠습니다. 이제 구현 방법을 살펴보겠습니다.
if __name__ == "__main__":
print("게임 모드를 선택하세요")
choice = input("[1], [2]: ")
if choice == "1":
game = RandomMultiplication()
elif choice == "2":
game = TableMultiplication()
else:
print("유효한 게임 모드를 선택하세요.")
exit()
game.run()
먼저 사용자에게 1 또는 2 모드를 선택하도록 요청합니다. 입력이 유효하지 않으면 스크립트 실행이 중지됩니다. 사용자가 첫 번째 모드를 선택하면 무작위 곱셈 게임 모드가 실행되고, 두 번째 모드를 선택하면 구구단 모드가 실행됩니다.
다음은 실제 작동 모습입니다.

결론
축하합니다! 객체 지향 프로그래밍을 활용하여 파이썬 앱을 성공적으로 구축했습니다.
전체 코드는 Github 저장소에서 확인할 수 있습니다.
이 글에서는 다음 내용을 배웠습니다.
- 파이썬 클래스 생성자 사용
- OOP를 사용하여 기능적인 앱 만들기
- 파이썬 클래스에서 super 함수 사용
- 상속의 기본 개념 적용
- 클래스 및 인스턴스 속성 구현
즐거운 코딩 되세요 👨💻
다음으로 생산성 향상을 위한 최고의 파이썬 IDE를 살펴보세요.