본문 바로가기

Python

Chapter 11 : Python - practice(assignment for myself)


안녕하세요, 오늘은 저번 포스팅에서 언급한대로
자체 실습과제를 포스팅 해보도록 하겠습니다.
오늘의 목적은 클래스 복습과 예외처리 숙지하기 입니다.
클래스는 저번 자체실습때도 했었는데요,
자주하면 실력이 더 늘어날테니 중복 복습이 이루어지게 하려고 합니다.
오늘도 실습 과제는 AI에게 만들어달라고 부탁해 보았습니다.

주제

**과제 설명(난이도: 중급)**
최종 결과물 : 학생 정보 관리 시스템을 구현
학생의 이름, 학번, 성적 정보를 저장하고 조회할 수 있는
프로그램을 작성해야 합니다. 이를 위해 클래스와 예외 처리를 활용합니다.
과제 작성 조건은 아래와 같습니다.

  1. 클래스: Student 클래스를 직접 생성합니다.
  2. 상속: Student 클래스는 AdvancedStudent 클래스에 상속됩니다.
  3. 클래스와 메서드 설명:
       1) Student 클래스: 학생 정보를 관리하는 클래스입니다.
         - add_grade: 학생의 성적을 추가하는 메서드입니다. 특정 조건일 때 ValueError 예외를 발생시킵니다.
         - get_grade: 학생의 성적을 조회하는 메서드입니다. 학번이 존재하지 않을 때 예외를 처리합니다.
       2) AdvancedStudent 클래스: Student 클래스를 상속받아 성적 평균을 계산하는 기능을 추가한 고급 학생 클래스입니다.
         - calculate_average: 학생의 성적 평균을 계산하는 메서드입니다. 성적 평균이 특정 기준 이하일 때 LowAverageError 예외를 발생시킵니다.
  4. try-except문: 성적 조회 기능에서는 try-except문을 사용하여 학번이 존재하지 않을 때 예외를 처리합니다.
  5. 예외 발생시키기: Student 클래스에서는 add_grade 메서드에서 특정 조건일 때 ValueError 예외를 발생시킵니다.
  6. 사용자 정의 예외처리: AdvancedStudent 클래스에서는 성적 평균이 특정 기준 이하일 때 LowAverageError라는 사용자 정의 예외를 발생시킵니다.
My Answer

class Student:
    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.checking_list = []  # 학번을 저장해놓고 조회할때 사용
        self.checking_list.append(student_id)  # 리스트에 학번 추가
        self.student_grades = {}  # 과목과 성적을 저장할 딕셔너리 생성

    def add_grade(self, subject, grades):
        try:
            if grades < 0 or grades > 100:  # 성적 예외처리
                raise ValueError("정상적인 성적 입력이 되지 않았습니다. 0~100 사이의 점수를 입력해주세요\
                \n오류학생 : {} / 잘못 입력한 과목 : {}".format(self.name, subject))
            else:
                self.student_grades[subject] = grades  # 딕셔너리 student_grades에 subject와 grades를 키값으로 저장
        except ValueError as e:
            print(e)

    def get_grade(self, student_id):
        try:
            if student_id == self.student_id:  # 조회할 학번이 checking_list에 있으면 본문 실행
                print("이름 : {}".format(self.name))
                for key, value in self.student_grades.items():  # student_grades에 저장된 과목과 성적 출력
                    print(key, ":", value)
            else:
                raise KeyError("존재하지 않는 학번입니다.")
        except KeyError as e:
            print(e)


class LowAverageError(Exception):  # AdvancedStudent클래스에 사용될 사용자 예외클래스
    def __init__(self, low_score):
        self.low_score = low_score

    def errmsg(self):  # 성적이 너무 낮다는 메세지 출력하는 메서드
        return "해당 학생의 평균성적이 너무 낮습니다. \n점수 : {}".format(self.low_score)


class AdvancedStudent(Student):
    def __init__(self, name, student_id):
        super().__init__(name, student_id)

    def calculate_average(self):  # 점수의 평균을 계산하는 메서드
        self.result = sum(self.student_grades.values())  # result라는 변수에 성적을 sum한 결과를 저장
        self.result /= len(self.student_grades.keys())  # result를 해당 객체(학생)의 과목수로 나누기
        print("{} 학생의 평균은 {}점 입니다.".format(self.name, self.result))
        self.rank = ""
        try:
            if 95 <= self.result <= 100:
                self.rank = "A+"
            elif 90 <= self.result < 95:
                self.rank = "A"
            elif 85 <= self.result < 90:
                self.rank = "B+"
            elif 80 <= self.result < 85:
                self.rank = "B"
            elif 75 <= self.result < 80:
                self.rank = "C+"
            elif 70 <= self.result < 75:
                self.rank = "C"
            elif 65 <= self.result < 70:
                self.rank = "D+"
            elif 60 <= self.result < 65:
                self.rank = "D"
            else:
                self.rank = "F"
                raise LowAverageError(self.result)
        except LowAverageError as e:
            print(e.errmsg())

        print("평균성적 등급 : {}".format(self.rank))


Student_1 = AdvancedStudent("기면수", 201912341234)  # 학생 정보 입력
Student_2 = AdvancedStudent("sleep_soo", 201912341235)
Student_3 = AdvancedStudent("Dev_soo", 201912341236)


Student_1.add_grade("수학", 100)  # 과목과 성적을 저장
Student_1.add_grade("영어", 100)
Student_1.add_grade("국어", 100)

Student_2.add_grade("수학", 90)
Student_2.add_grade("영어", 85)
Student_2.add_grade("국어", 78.5)

Student_3.add_grade("수학", 78.5)
Student_3.add_grade("영어", 98.2)
Student_3.add_grade("국어", 0)


Student_1.get_grade(201912341234)  # 학번을 통해서 과목별 점수 조회
Student_2.get_grade(201912341235)
Student_3.get_grade(201912341236)

Student_1.calculate_average()
Student_2.calculate_average()
Student_3.calculate_average()


이거 작성하는데 총 5일이 걸렸습니다..ㅎㅎ
부모 클래스 작성하는데 3일 걸렸고,
자식 클래스 작성하는데 2일이 걸려버렸네요,,
이번에 작성한 코드가 지금까지 제가 작성해본 코드중 제일 긴 것 같아요:)
중간에 의도치 않은 결함과 에러와 기타등등
많은 변수가 절 괴롭혔지만 간신히 해결했습니다!
그럼 코드를 한번 살펴보도록 하겠습니다.
코드가 기니까(초보인 제 기준으로는..) 조금 나눠서 살펴봅시다.


class Student

class Student:
    def __init__(self, name, student_id):
        self.name = name
        self.student_id = student_id
        self.checking_list = []  # 학번을 저장해놓고 조회할때 사용
        self.checking_list.append(student_id)  # 리스트에 학번 추가
        self.student_grades = {}  # 과목과 성적을 저장할 딕셔너리 생성

    def add_grade(self, subject, grades):
        try:
            if grades < 0 or grades > 100:  # 성적 예외처리
                raise ValueError("정상적인 성적 입력이 되지 않았습니다. 0~100 사이의 점수를 입력해주세요\
                \n오류학생 : {} / 잘못 입력한 과목 : {}".format(self.name, subject))
            else:
                self.student_grades[subject] = grades  # 딕셔너리 student_grades에 subject와 grades를 키값으로 저장
        except ValueError as e:
            print(e)

    def get_grade(self, student_id):
        try:
            if student_id == self.student_id:  # 조회할 학번이 checking_list에 있으면 본문 실행
                print("이름 : {}".format(self.name))
                for key, value in self.student_grades.items():  # student_grades에 저장된 과목과 성적 출력
                    print(key, ":", value)
            else:
                raise KeyError("존재하지 않는 학번입니다.")
        except KeyError as e:
            print(e)


먼저, 부모클래스인 Student 클래스부터 보겠습니다.
생성자에서는 학생의 이름과 학번을 전달받아 학생 정보를 저장하기 위해
namestudent_id를 인자로 받도록 하였습니다.
student_grades라는 이름의 비어있는 딕셔너리를 선언해주었는데요,
이건 {과목(key) : 성적(value)} 형태로 저장해서
나중에 출력할 수 있도록 하겠습니다.

add_grade : 이 메서드는 과목과 그 과목의 성적을 저장합니다.
특정 과목과 성적을 전달받으면  
아까 생성해 놓은 빈 딕셔너리 student_grades에 저장하는데
여기서 try-except으로 예외처리를 해놓습니다.
성적이 음수이거나 100점이 넘으면 말이 안되기 때문에
조건문을 활용해서 성적이 0보다 작거나 100보다 크면 ValueError를 일으키고,
그렇지 않고 정사적인 숫자라면 비어있던 딕셔너리 student_grades
해당 메서드로 전달받은 과목과 성적을 키-값 쌍으로 저장합니다.

get_grade: 이 메서드는 학번을 인자로 전달받아
그 학번을 통해 해당 학생의 과목별 성적을 조회해주는 기능을 합니다.
여기서 매개변수로 전달된 학번(student_id)
인스턴스에 저장된 학번(self.student_id)과 같다면
student_grades에 저장된 키와 값을 모두 출력합니다.
이는 과목과 그 과목의 성적이 되는 것이겠죠.
하지만, 만약 인자값으로 전달받은 학번과 인스턴스에 저장된 학번이 다를경우
예외처리로 KeyError를 일으키도록 하였습니다.

*여기서 새로 알게된 문법
1. 딕셔너리이름[key가 될 변수] = value가 될 변수
: 딕셔너리에 내용 추가
2. for key, value in 딕셔너리이름.items()
: 딕셔너리의 키와 값 출력할때 사용(변수는 꼭 key, value로 안해도 됨)

Class LowAverageError(Exception)

class LowAverageError(Exception):  # AdvancedStudent클래스에 사용될 사용자 예외클래스
    def __init__(self, low_score):
        self.low_score = low_score

    def errmsg(self):  # 성적이 너무 낮다는 메세지 출력하는 메서드
        return "해당 학생의 평균성적이 너무 낮습니다. \n점수 : {}".format(self.low_score)


이 클래스는 아래에 등장하는 AdvancedStudent클래스의 본문에서
성적의 평균을 구할때 성적이 너무 낮으면 발생시킬
사용자 예외처리 클래스입니다.

class AdvancedStudent(Student)

class AdvancedStudent(Student):
    def __init__(self, name, student_id):
        super().__init__(name, student_id)

    def calculate_average(self):  # 점수의 평균을 계산하는 메서드
        self.result = sum(self.student_grades.values())  # result라는 변수에 성적을 sum한 결과를 저장
        self.result /= len(self.student_grades.keys())  # result를 해당 객체(학생)의 과목수로 나누기
        print("{} 학생의 평균은 {}점 입니다.".format(self.name, self.result))
        self.rank = ""
        try:
            if 95 <= self.result <= 100:
                self.rank = "A+"
            elif 90 <= self.result < 95:
                self.rank = "A"
            elif 85 <= self.result < 90:
                self.rank = "B+"
            elif 80 <= self.result < 85:
                self.rank = "B"
            elif 75 <= self.result < 80:
                self.rank = "C+"
            elif 70 <= self.result < 75:
                self.rank = "C"
            elif 65 <= self.result < 70:
                self.rank = "D+"
            elif 60 <= self.result < 65:
                self.rank = "D"
            else:
                self.rank = "F"
                raise LowAverageError(self.result)
        except LowAverageError as e:
            print(e.errmsg())

        print("평균성적 등급 : {}".format(self.rank))


다음은 Student 클래스를 상속받은 자식클래스 AdvancedStudent입니다.
calcualte _average : 과목별 점수들을 계산해서 평균을 구하는 메서드입니다.
student_gradesvalue(과목의 점수)를 모두 합한뒤,
key(과목)의 개수로 나누어서 평균을 구하도록 했습니다.
이때 평균값의 범위에 따라 등급을 산출하는 기능까지 넣어봤는데요,
이부분은 점수가 낮을 경우 어떻게 할지 예외처리를 해 놓았습니다.
(위의 사용자예외처리 클래스 LowAverageError로 넘어갑니다.)
여기서 중요한건 rank라는 변수에 공백을 저장해놓고 시작해야한다는 점입니다.
그렇지 않으면 rank라는 변수가 선언이 되어있지 않은 상태라서
if문이 작동하지 않습니다.

객체선언 및 활용(학생 정보 입력 및 조회)

Student_1 = AdvancedStudent("기면수", 201912341234)  # 학생 정보 입력
Student_2 = AdvancedStudent("sleep_soo", 201912341235)
Student_3 = AdvancedStudent("Dev_soo", 201912341236)


Student_1.add_grade("수학", 100)  # 과목과 성적을 저장
Student_1.add_grade("영어", 100)
Student_1.add_grade("국어", 100)

Student_2.add_grade("수학", 90)
Student_2.add_grade("영어", 85)
Student_2.add_grade("국어", 78.5)

Student_3.add_grade("수학", 78.5)
Student_3.add_grade("영어", 98.2)
Student_3.add_grade("국어", 0)


Student_1.get_grade(201912341234)  # 학번을 통해서 과목별 점수 조회
Student_2.get_grade(201912341235)
Student_3.get_grade(201912341236)

Student_1.calculate_average()
Student_2.calculate_average()
Student_3.calculate_average()


총 3명의 학생 정보를 저장해보려고 Student_숫자 형식으로
AdvancedStudent 클래스의 인스턴스를 선언해 놓았습니다.
인자값으로 이름과 학번을 전달해서 학생정보를 저장했죠.
(학번은 제가 19학번이라 2019로 시작..ㅎ)

그 다음은 객체를 통해 메서드를 호출하도록 하였습니다.
add_grade 메서드에 접근해서는 과목과 점수를 전달함으로써
해당 학생의 과목별 점수를 저장하였고,
과목은 간단하게 수학 영어 국어 3과목만 저장해보았습니다.

get_grade 메서드를 호출할때는 학번을 인자값으로 하여
해당 학번에 일치하는 학생 정보를 불러왔습니다.
(여기서부터 print가 찍히게 됩니다.)

마지막에는 정보가 저장되어있는 학생 인스턴스에서
calculate_average를 호출해서 평균 점수과 등급을 불러왔습니다.
이것으로 프로그램의 구현이 마무리되며
작성 코드의 출력 결과는 아래 사진과 같습니다.

작성코드 실행결과



자! 이렇게 길고긴 사투 끝에 자체실습과제도 완성하였고
코드리뷰 포스팅까지 완료했습니다!
정말 중간중간에 에러가 계속 나고,, 수정하고,,
의도대로 실행되지 않아서 다시 뜯어고치고를 반복하느라
생각보다 훨씬 오래 걸렸네요,,ㅎ
그래도 제가 직접 코드를 작성하면서 자체실습도 하고
직접 작성 코드를 리뷰하니까 중간에 실수했던 부분도 잘 찾아내서
코드 리팩토링까지 잘 이루어진것 같아서 뿌듯합니다:)

앞으로도 자체실습 많이 하면서 발전해야겠습니다.
오늘은 여기까지로 하도록 하죠.
이제 곧 명절인데 시골가서도 한번 복습해겠습니다 ㅎ
모두 새해 복 많이 받으세요~!

출처 : https://youtu.be/kWiCuklohdY
필자는 해당 링크 영상을 통해 학습하였으나,
포스팅 내용 및 설명은 영상과 다를 수 있음을 알립니다.