이렇게 함수, 모듈과 패키지, 객체 지향에 대한 개념까지 정리하며 살펴보았다. 이제는 위 개념들을 예제 코드를 통해 어떻게 사용하는 것인지를 자세히 알아볼 것이다.
Q1. 다음 리스트에서 홀수만 추출하여 제곱한 결과를 반환하시오.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 방법 1: 반복문과 조건문 사용
result = [i ** 2 for i in numbers if i % 2 != 0] # 리스트 컴프리헨션 사용
print(result)
# 방법 2: lambda와 map, filter 등 사용
result = list(map(lambda x: x ** 2, filter(lambda x: x % 2 != 0, numbers)))
print(result)
→ 해당 문제를 보면 자연스럽게 반복문을 통해 numbers 리스트를 순회하며 2로 나누어지는지를 확인하는 조건문을 사용(방법 1) 해야겠다고 생각할 것이다. 그래서 처음엔 이 방식으로 코드를 작성하였으나, 위에서 배운 함수의 개념을 사용해야겠다는 생각을 하고 lambda 함수와 map, filter 등을 사용(방법 2) 해서 문제를 해결해보았다.
위 문제 해결에 사용한 함수 등을 간단히 설명하면,
1. filter(function, iterable)
- 역할: iterable에서 조건을 만족하는 요소만 걸러냄
- function: 각 요소에 적용할 함수, True를 반환하는 요소만 통과됨
2. lambda 함수
- 역할: 이름 없이 일회용으로 사용하는 간단한 함수 정의 방식
- 문법: lambda 매개변수: 표현식
3. map(function, iterable)
- 역할: iterable의 각 요소에 함수를 적용해서 새로운 값을 생성
- function: 요소 하나를 받아서 처리하는 함수
- 결과: 모든 요소에 함수 적용한 값을 담은 map 객체
4. list(...)
- 역할: map이나 filter가 반환하는 이터레이터 객체를 리스트로 변환
- 필수는 아니지만, 결과를 보기 위해 리스트로 바꿔주는 게 일반적
Q2. 함수 호출 시 매개변수와 반환값을 로그로 출력하는 데코레이터를 작성하시오. 그리고 이 데코레이터를 add 함수에 적용하시오.
# 함수 호출 시 매개변수와 반환값을 로그로 출력하는 데코레이터를 작성하시오. 그리고 이 데코레이터를 add 함수에 적용하시오.
def log_function_call(func): # 데코레이터 함수 생성 / 인자로 func(데코레이터가 적용될 함수)를 받음 => add() 함수가 대상임
def wrapper(*args, **kwargs): # 실제로 add()를 감싸는 함수 / *args: 위치 인자 / **kwargs: 키워드 인자
print(f"함수 '{func.__name__}' 호출됨") # 데코레이터가 감싼 함수의 이름을 가져옴
print(f" - 인자 args: {args}")
print(f" - 인자 kwargs: {kwargs}")
result = func(*args, **kwargs) # 원래의 add() 함수를 실제로 실행 시킴 / 인자는 *args, **kwargs로 전달됨
print(f"반환값: {result}\n")
return result
return wrapper
@log_function_call
def add(a, b):
return a + b
add(3, 5)
→ 데코레이터를 자주 사용해본 적은 없지만 사용해야하는 경우가 있을 수 있으니 알아두긴 해야할 것 같다. 아래에 위 코드의 자세한 설명을 적어보았다.
1. log_function_call(func)
- 이 함수가 데코레이터이다.
- 인자로 func (즉, 데코레이터가 적용될 함수)를 받음.
- 여기서는 add() 함수가 그 대상이다.
2. def wrapper(*args, **kwargs):
- wrapper()는 실제로 add()를 감싸는 함수
- *args는 위치 인자 (예: add(3, 5)에서 3, 5)
- **kwargs는 키워드 인자 (예: add(a=3, b=5)처럼 명시된 인자)
3. print(f"함수 '{func.__name__}' 호출됨")
- func.__name__는 데코레이터가 감싼 함수의 이름 (add)을 가져옴
- 어떤 함수가 호출됐는지 로그 출력
4. result = func(*args, **kwargs)
- 원래의 add() 함수를 실제로 실행시킴
- 인자는 *args, **kwargs로 전달됨
- 반환값은 result에 저장
5. return result
- wrapper() 함수가 실행된 결과를 다시 원래처럼 반환해줌
- 즉, 이 데코레이터를 써도 기존 add()의 동작은 그대로 유지된다.
Q3. 간단한 계산기 모듈을 만들어보시오.
# calculator.py # 모듈로 사용할 파일 이름
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
print("분모에 0을 사용할 수 없습니다.")
return a / b
→ 위와 같이 calculator.py 파일에 계산기 기능을 하는 함수들을 생성해서 저장해놓았다. 이 모듈을 사용하려면 다른 파이썬 파일에서 import를 사용하면 된다.
사용 예시:
# main.py
import calculator
print("덧셈: ", calculator.add(3, 5))
print("뺄셈: ", calculator.subtract(10, 4))
print("곱셈: ", calculator.multiply(6, 7))
print("나눗셈: ", calculator.divide(8, 2))
print("0으로 나누기: ", calculator.divide(10, 0))
Q4. 간단한 도서관 시스템 구현(문제의 자세한 설명은 코드 내 주석에 포함)
# Day3
# 클래스와 객체지향
# 과제: 도서관 시스템 구현
# 다음 클래스를 구현하세요:
# • `Book`: 도서 정보(제목, 저자, ISBN, 출판연도 등)를 관리
# • `Library`: 도서 컬렉션을 관리하고 대출/반납 기능 제공
# • `Member`: 도서관 회원 정보와 대출 목록 관리
# • 다음 기능을 구현하세요:
# • 도서 추가/삭제
# • 도서 검색(제목, 저자, ISBN으로)
# • 도서 대출/반납
# • 회원 등록/관리
# • 회원별 대출 현황 확인
# • 객체 지향 설계 원칙(SOLID)을 최소한 2가지 이상 적용하세요.
# • 적절한 캡슐화를 통해 데이터를 보호하세요.
class Book:
def __init__(self, title, author, isbn, year):
# Book 객체가 생성될 때, 아래 정보를 받아 초기화
self.__title = title
self.__author = author
self.__isbn = isbn
self.__year = year
self.__is_borrowed = False # 도서 대출 상태
def get_title(self):
return self.__title # 제목
def get_author(self):
return self.__author # 저자
def get_isbn(self):
return self.__isbn # ISBN(도서번호)
def get_year(self):
return self.__year # 출판연도
def is_borrowed(self):
return self.__is_borrowed # 대출 여부 (대출되지 않은 상태가 기본값)
def borrow(self): # 도서 대출 확인
if self.__is_borrowed: # True: 대출 중인 상태라면 아래 코드 실행
# 이미 대출 중이라면 예외 처리
raise Exception(f"'{self.__title}'은 대출 중입니다. ")
self.__is_borrowed = True # 대출 상태 처리
def return_book(self): # 반납 처리
self.__is_borrowed = False # False: 대출 상태 해제
class Library:
def __init__(self):
self.__books = [] # 도서 목록을 저장하는 리스트 생성
self.__members = {} # 회원 정보를 저장하는 딕셔너리 (key: 회원 ID, value: Member 객체)
def add_book(self, book): # 도서 추가
self.__books.append(book)
def delete_book(self, isbn): # ISBN을 기준으로 도서 삭제
# 리스트 컴프리헨션 사용 / 해당 ISBN이 아닌 책들을 새 리스트에 저장 (일치하지 않는 도서만 남기는 방식)
self.__books = [book for book in self.__books if book.get_isbn() != isbn]
def search_book(self, keyword, key="title"):
# 제목을 기본 키로 도서를 검색 (기본값: 제목)
if key == "title":
# 제목에 keyword가 포함된 도서 리스트를 반환
return [b for b in self.__books if keyword.lower() in b.get_title().lower()]
elif key == "author":
# 저자명에 keyword가 포함된 책 반환
return [b for b in self.__books if keyword.lower() in b.get_author().lower()]
elif key == "isbn":
# ISBN이 정확히 일치하는 책 반환
return [b for b in self.__books if keyword == b.get_isbn()]
else:
return []
def register_member(self, member): # 새로운 회원 등록
# member 딕셔너리에 member_id를 키로 member 객체를 저장
self.__members[member.get_id()] = member
def get_member(self, member_id):
# member_id로 회원 반환
return self.__members.get(member_id)
def borrow_book(self, member_id, isbn): # 도서 대출
member = self.get_member(member_id) # 회원 받아오기
book = next((b for b in self.__books if b.get_isbn() == isbn), None) # ISBN으로 도서 찾기
if member and book: # 회원과 도서가 모두 존재한다면
member.borrow_book(book) # Member 클래스의 borrow_book이 실행되면서 도서가 대출됨 -> 도서 대출 처리가 위임되었다고 말함
def return_book(self, member_id, isbn): # 도서 반납
member = self.get_member(member_id) # 회원 받아오기
if member: # 만약 회원이 있다면
# 해당 회원이 대출 중인 도서 중 ISBN이 일치하는 도서 찾기
book = next((b for b in member.get_borrowed_books() if b.get_isbn() == isbn), None)
if book: # 만약 도서가 있다면
member.return_book(book) # 도서 반납 처리 위임
def print_member_status(self, member_id): # 회원의 대출 목록 출력
member = self.get_member(member_id) # 회원 받아오기
if member: # 만약 회원이 있다면
print(f"{member.get_name()}님의 대출 목록: ")
for book in member.get_borrowed_books(): # 대출 중인 도서를 순회하며 출력
print(f" - {book.get_title()} by {book.get_author()}")
class Member:
def __init__(self, name, member_id):
self.__name = name # 회원 이름
self.__member_id = member_id # 회원 ID
self.__borrowed_books = [] # 대출한 도서 저장할 리스트 생성
def get_name(self):
return self.__name # 이름 반환
def get_id(self):
return self.__member_id # 회원 ID 반환
def get_borrowed_books(self):
return self.__borrowed_books # 대출 중인 도서 리스트 반환
def borrow_book(self, book): # 도서 대출 함수
book.borrow() # Book 클래스에서 도서 상태 변경 처리
self.__borrowed_books.append(book) # 도서 대출 리스트에 추가
def return_book(self, book): # 도서 반납 함수
if book in self.__borrowed_books: # 만약에 책이 도서 대출 리스트에 있다면
book.return_book() # Book 클래스에서 도서 반납 처리
self.__borrowed_books.remove(book) # 리스트 목록에서 제거
→ 해당 과제는 사용자에게 CLI를 통해 입력받는 경우가 아니라 외부에서 객체를 생성해 추가하거나 하는 그런 경우를 가정한 코드로 작성하였다. 만약 사용자에게 CLI를 통해 정보를 입력받는 경우라고 한다면 위 코드에 메인 함수와 실행 함수 등의 코드를 추가해주면 된다. 다른 코드나 프로그램에서 모듈로써 불러와서 기능을 사용하는 그런 코드에 가깝다고 생각하면 될 것 같다.
위 코드를 작성할 때, 각 클래스에 어떤 기능을 담당하는 함수들을 구현해야하는지를 고민했다. 왼편의 메모에서 볼 수 있듯이 예를 들면, 'Book' 클래스에서 책이 대출 중인지를 확인하는 함수가 필요한가? 라는 생각을 했다. 대출/반납 기능은 'Library' 클래스에 포함되어 있고, 'Member' 클래스에도 대출중인 도서 리스트와 대출/반납 기능을 담당한느 기능이 있기에 그런 생각을 했었던 것 같다.
하지만 조금 더 생각해보니 대출/반납을 처리한 후 'Book' 클래스에서 이를 확인하는 기능과 그에 대한 정보가 없다면 중복의 문제가 발생할 수도 있을 것이라 생각했고, 그렇게 추가하게 되었다.
본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.