이번 시간은 Python과 매우 친해지며 배웠던 객체지향과 에러 처리를 이용해서 학생관리 프로그램을 전면 개정해보도록 하자! 언제나 그랬든 요구사항부터 확인하자

 

* 요구사항 *

① 반별로 학생관리하는 프로그램을 만들어야 한다.

② 학생은 학생 번호, 학생 이름, 학생 성적을 가져야 한다.

③ 학생을 추가 / 제거 / 이름 변경하는 기능도 만들자

④ 학생 번호를 입력받아서 학생정보를 출력하는 기능을 만들자

⑤ 학생 추가 시 학생 번호가 중복되면 받지 번호만 다시 받는 기능을 만들자

⑥ 반을 추가하는 기능을 만들자

⑦ 현재 반의 개수를 출력하는 기능을 만들자

 

이 프로그램 또한 정답은 없으니 배운 내용을 마음껏 사용하여 자신만의 프로그램을 만들고 필자의 구현 방법이 궁금한 사람들은 아래를 확인하자.

 

* 학생관리 프로그램의 ver2의 함수를 옮겨올지는 여러분께 맡기겠다. 일반 함수와 클래스 함수가 크게 다르진 않지만, 작성하면 도움은 될 것이다. 필자는 생략하겠다.

 


일단 어떤 객체를 만들지 파악하기 위해서 우리가 해야 되는 일을 정의해보자

우리는 "학생"이 등록된 "반"정보를 "관리하는 프로그램"을 만들어야 한다.

오호 그러니까 우리가 만들어야 되는 객체는 학생, 반, 관리하는 프로그램이다. 각각의 멤버 변수와 기능은 조건에 요구조건에 따라 다음과 같이 생성해보자

지금 당장은 이렇게 만들고, 필요한 기능을 추가하면 될 거 같다. 다음의 변수와 생성자만 정의된 구조를 코딩하자

class student :
    num = -1
    name = ""

    def __init__(self,num,name) :
        self.num = num
        self.name = name

    def get_info(self) :
        ##

class room :
    num = -1

    def __init__(self,num) :
        self.stu_dict = dict()

    def is_in_stu_num(self,num) :
        ##

    def get_stu_info(self,num) :
        ##
    
    def add_stu(self, stu) :
        ##
   
    def mod_stu(self, stu_num, stu_name) :
        ## 

class manager :

    def __init__(self) :
        self.room_dict = dict()

    def add_room(self) :
        ##

    def add_stu(self, room_num, stu) :
        ##

    def del_stu(self, stu_num) :
        ##

    def mod_stu(self, stu_num, mod_name) :
        ##

    def print_stu(self, stu_num) :
        ##
    
    def show_room(self) :
    	##

파라미터는 필요하다고 생각되는 것을 넣으면 된다. 다음은 student의 구현 부이다

class student :
    num = -1
    name = ""

    def __init__(self,num,name) :
        self.num = num
        self.name = name

    def get_info(self) :
    	return [self.num, self.name]

student의 get_info를 사용하는 곳은 room 클래스에서 이다. 따라서 처리의 편의성을 위해서 get_info는 [학생의 번호, 학생의 이름]을 배열로 반환토록 하였다 다음은 room의 구현 부이다.

class room :
    num = -1

    def __init__(self,num) :
        self.num = num
        self.stu_dict = dict()

    def is_in_stu_num(self,num) :
        if(num in self.stu_dict) :
            return True
        else :
            return False

    def get_stu_info(self,num) :
        try :
            return self.stu_dict[num]
        except KeyError :
            return ""

    def add_stu(self, stu) :
        self.stu_dict[stu.get_info()[0]] = stu.get_info()[1]

    def del_stu(self, num) :
        try :
            del self.stu_dict[num]
        except KeyError :
            pass

    def mod_stu(self, stu_num, stu_name) :
        try :
            self.stu_dict[stu_num] = stu_name
        except KeyError :
            pass

 

학생 번호가 있는지 확인하는 기능이 있어야하니 is_in_stu_num은 학생번호가 있는지 학생 dict에서 찾아서 있다면 True 없으면 False를 반환토록 하였다. 또한 get_stu_info로 학생정보를 가져올 수 있게 하였고, add_stu로 매개변수로 넘어오는 stu의 정보를 등록할 수 있게 하였다. 

* 예외처리 구문의 pass는 코드를 무시한다는 예약어이다. 문법적으로 코드 블록 내부에 필수적으로 무언가를 해야 되는데, 하기 원하지 않는 경우 사용한다.

 

다음은 manager의 구현 부이다.

class manager :

    def __init__(self) :
        self.room_dict = dict()

    def add_room(self) :
    	self.room_dict[len(self.room_dict)+1] = room(len(self.room_dict)+1)

    def add_stu(self, room_num, stu) :
        try :
            self.room_dict[room_num].add_stu(stu)
        except KeyError :
            print(" 없는 교실입니다.")

    def del_stu(self, stu_num) :
        for i in range(len(self.room_dict)) :
            self.room_dict[i+1].del_stu(stu_num)

    def mod_stu(self, stu_num, mod_name) :
        for i in range(len(self.room_dict)) :
            self.room_dict[i+1].mod_stu(stu_name)

    def print_stu(self, stu_num) :
        for i in range(len(self.room_dict)) :
            value = self.room_dict[i+1].get_stu_info(stu_num)
            if(len(value) != 0 ):
                print(value)
                break

    def show_room(self) :
        print("현재 "+str(self.room_dict)+"의 교실이 있습니다.)

이렇게 매니저의 구현부까지 구현하였다. 여기까지 보고 이해가 되셨다면, 이것보다 훌륭한 코드를 만들 수 있을 것이다.

 

* 코드에 멤버 변수에 접근할 때(예를 들면 student) 직접 접근(student.name)을 하지 않는 것을 볼 수 있다. 코드의 보안성을 위해서라도 이렇게 코딩하는 것이 안전하다.(궁금한 사람들은 접근제어 지시자를 찾아보자 Python에 적용되는 항목은 아니지만, 실력 향상에 도움이 된다.)

* 위 예제 코드는 객체 지향을 억지로 맞춘다고 만든 상당히 비효율적인 코드이다. 사실 student는 클래스로 만들 것이 아니라, 자료구조로 room의 멤버변수처리하는 것이 공간적으로 이득이고, add, del, mod는 room별로 전부 에러처리를 하는것이 아닌 manager에서 room의 학생 번호를 가져와서 있으면 실행하는 것이 빠르다.


 

이렇게 객체지향과 에러 처리가 포함된 새로운 학생관리 프로그램을 만들어 보았다. 다음 시간에는 지금까지 배운 예약어를 포함해서 여러 가지 예약어를 살펴보도록 하자.

프로그램 포스팅을 따라오면서 수많은 ERROR를 확인했을 것이다. 프로그래밍은 에러와 프로그래머의 싸움이다. 물론 코딩을 할 때는 싸워도 된다. 그러나 프로그램을 배포를 할 때나 동작을 확인하고 싶은데 계속되는 에러로 프로그램이 계속 멈추는 것을 보고 싶은 사람은 없을 것이다.


try ~ except ~ finally

따라서 프로그램에 실행되는 도중 에러를 적절한 방법으로 처리해서 안정적인 프로그램을 만드는것은 매우 중요한 일이다. 뭐 간단하게 다음 에러로 에러에 대해서 일단 알아보도록 하자

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다. : "))

        print(100//user_input)
        print("다시 입력해주세요!")
        
출력 : 
100을 다음 수로 나눕니다. : 3
33
다시 입력해주세요!
100을 다음 수로 나눕니다. : 0
Traceback (most recent call last):
  File "C:/Users/82109/Desktop/student.py", line 8, in <module>
    print(100//user_input)
ZeroDivisionError: integer division or modulo by zero

위 코드에서 user_input이 0이되면 ZeroDivisionError로 print("다시 입력해주세요!")는 실행되지 않는다. 이러한 에러 또한 우리가 저번 시간에 배운 클래스이다. 또한 최상위 Exception부터 위에 있는 ZeroDivisionError 클래스까지 다음과 같은 계층구조를 가진다.

<출처 : https://w3.cs.jmu.edu/lam2mo/cs240_2014_08/lab05-exceptions.html>

이런 Exception클래스들은 try ~ except ~ finally 구문으로 적당하게 처리될 수 있다. 다음의 코드를 확인하자

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다."))

        try :
            print(100//user_input)
        except ZeroDivisionError :
            print("0으로는 나눌 수 없습니다.")
        finally :
            print("다시 입력해주세요!")

상기 코드는 보이는 것처럼 에러가 발생할 수 있는 print(100//user_input)을 try 코드블럭에 넣어두고, 이를 except으로 ZeroDivisionError가 발생하면 except 코드 블록이 실행되며, try가 실행되던 except이 실행되던 결국 finally가 실행되도록 동작한다.

 

 

에러의 처리

except 구문으로 핸들될 exception은 as 예약어로 excpetion의 별칭을 지정하고 이를 그대로 출력할 수 있다. 다음의 코드를 확인하자 

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다."))

        try :
            print(100//user_input)
        except ZeroDivisionError :
            print("0으로는 나눌 수 없습니다.")
        finally :
            print("다시 입력해주세요!")
            
출력 : 
100을 다음 수로 나눕니다.0
integer division or modulo by zero
다시 입력해주세요!
100을 다음 수로 나눕니다.

또한 에러처리에 관련해서는 다음과 같은 방법 또한 사용된다.

### except.py ###

if __name__ == "__main__" :

    while(True) :
        user_input = int(input("100을 다음 수로 나눕니다."))

        ## 에러처리1 ##
        try :
            print(100//user_input)
        except (ZeroDivisionError, ValueError) :
            
            print("이상한 값인듯 싶네요.")

        ## 에러처리2 ##
        try :
            print(100//user_input)
        except Exception as e:
            print(e)

        ## 에러처리3 ##
        try :
            print(100//user_input)
        except (ZeroDivisionError, ValueError) :
            print("이상한 값인듯 싶네요.")
        else :
            print("에러가 발생하지 않았습니다.")

1. 에러처리1 처럼 여러 가지 에러를 튜플로 묶어서 핸들 할 수 있다.
2. 최상의 에러인 Exception을 묶어서 에러처리할 수 있다.(사실 추천하지는 않는다. 자세하게 어떠한 에러가 발생했는지 출력하는 것이 좋다.)
3. 마지막 else문을 이용해서 에러가 발생하지 않았을때만 처리할 구문을 쓸 수 있다.(finally와 다르다)

 

raise

에러를 강제로 발생시킬 수도 있다. 이때 에러는 이미 생성된 error class로 생성할 수도 있고, 프로그래머가 적당한 에러를 만들어 그 에러를 사용할 수도 있다. 아래는 그 예시이다.

### except.py ###

class my_error(Exception) :

    def __init__(self,expression) :
        self.expression = expression

if __name__ == "__main__" :

    while(True) :
        try :
            user_input = int(input("100을 다음 수로 나눕니다."))

            if(user_input == 0) :
                raise my_error("0을 입력하셨습니다.","0의 입력")
        except my_error as e:
            print(e)

Exception을 상속받아서 my_error 클래스를 만들었다. my_error의 생성자에는 expression만을 넣었지만, Exception에서는 내용을 의미하는 message들도 있으니, 공식 제공 문서와는 항상 친하게 지내자 또한 중간에

(참고 :  docs.python.org/3/library/exceptions.html

 


이번시간에는 프로그램의 영원한 친구인 에러를 적당하게 처리하는 방법을 알아보았다. 이제 우리 프로그램은 좀 더 안정적인 방향으로 발전하게 되었다. 다음 시간에는 여태까지 배운 예외처리와 객체지향의 특성을 이용해서 학생 관리 프로그램을 발전시켜 보자

아 한 2억만 상속받고 싶다

- 리덕토 - 

 

Python과 매우 친해지기 그 3번째 시간은 클래스의 상속(Inherit)이다. 상속은 객체지향에서 빼놓을 수 없는 중요한 기능이고, 약간의 이해도가 필요하기에 별도의 포스팅을 작성하였다.


상속(Inherit)의 정의와 사용

우리의 친구 위키백과에서 상속이 무엇인지 정의를 가져와 보자

객체 지향 프로그래밍(OOP)에서, 상속(inheritance)은 객체들 간의 관계를 구축하는 방법이다

- 출처 : 위키백과(ko.wikipedia.org/wiki/%EC%83%81%EC%86%8D_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D))

으흠; 조금 모호한 부분이 있는 거 같다. 객체들 간의 관계를 상속만으로 구축되는 것은 아니지만; 위키가 필자보다 몇만 배는 똑똑하기 때문에 쟤가 저러면 그런 거다. 정의는 저렇고 상속은 "다른 객체로부터 새로운 객체를 재생성하는 것"으로 이해해주면 될 거 같다. 

 

이런 상황을 가정하자 우리는 객체 지향 프로그램의 방식으로 프로그램을 만들고 있는 도중인데, 요리사와 소방관의 클래스를 만들어야 된다. 둘은 사람이라는 기본 전제를 가지고 있다. 따라서 사람으로 가질 수 있는 모든 속성이나 공통된 행동이 있을 것이다. 상속은 이러한 아이디어를 정확하게 표현해준다.

여기서 상속을 하는 클래스를 부모(Parent) 클래스라고 하며, 상속을 받는 Fire Fighter이나 Cook을 자식(Sub) 클래스, 같은 레벨에서 상속을 받은 Fire Fighter와 Cook은 서로를 형제(Sibling) 클래스라고 지칭한다. 이렇게 실행한 상속은 다음과 같은 특징을 가진다.

 

1. 자식은 부모의 클래스 변수, 매서드에 접근할 수 있다.
2. 부모는 자식의 클래스변수, 메서드에 접근할 수 없다.
3. 여러 개의 부모를 상속받을 수 있다.(여기서 다이아몬드형 상속이라는 문제가 발생한다. 궁금한 인원은 더보기를 확인)
더보기

위와 같은 형태가 다이아몬드 상속이다. 물론 상속받은 객체를 상속받는 것은 문제가 되지 않으나, Fire Fighter가 human에게 상속받은 some_function()와 Cook이 human에게 상속받은 some_function() 중 GoodMan이 사용해야 하는 매서드가 무엇인지 정할 수 없는 문재가 발생한다.

 

상속은 오로지 조금 더 구체적인 자식으로 단방향으로만 진행된다. 아래의 코드를 확인하면서 Python에서 상속의 사용법을 익혀보자

class human :
    
    name = ""

    def __init__(self,name) :
        self.name = name

    def introduce(self) :
        print("hello im human "+self.name)

    def job(self) :
        print("I have no jobs")


class firefighter(human) :      ## firefighter은 human을 상속받음

    def __init__(self,name) :
        super().__init__(name)  ## 부모의 생성자를 호출

    def job(self) :             ## job은 firefighter가 오버라이딩
        print("my job is firefighter")

    def new_introduce(self) :
        super().introduce()     ## super를 이용, 부모의 매서드 호출
    
    def extinguish(self) :
        print("you off fire!")

        
if __name__ == "__main__" :

    cheolsu = human("cheolsu")
    younghee = firefighter("younghee")

    cheolsu.introduce()
    cheolsu.job()

    younghee.extinguish()
    younghee.introduce()        ## 부모의 매서드인 introduce사용가능
    younghee.job()
    
    

우선 human 클래스는 우리가 저번 시간에서 모두 배운 내용으로 이루어져 있다.(생성자, 매머드의 정의, 클래스 변수 name 등) 제일 먼저 눈에 띄는 것은 상속을 할 때 class [클래스명](상속받을 클래스명) :으로 정의한 점이다.(여러 개를 상속받으려 먼 상속받을 클래스명을 콤마로 나누어 적어주면 된다.)

 

프로그램의 main을 보면 human객체 cheosufirefighter객체 younghee가 있다. 주석에 적어두었듯 younghee 또한 부모의 매서드인 introduce를 사용하고 있다.

 

자식에서 부모를 호출할 때는 앞에 super()를 붙이는데 super() 매더스는 자신의 부모를 반환한다. 따라서 younghee의 super(). introduce()는 human.introduce()로 해석되며, firefighter 생성자의 super().__init__(name)은 부모 생성자를 호출하는 것과 같다.

 

오버 라이딩(Overriding)

부모에서 정의된 메서드를 자식에서 재정의하는 것을 오버 라이딩(Overriding)이라고 한다. 코드에서는 human에서 정의된 job 함수가 firefighter에서 재정의되며 오버 라이딩(Overriding)된 것을 확인할 수 있다.

* 혹시 다른 언어에서 넘어와서 오버 로딩(Overloading)의 개념을 찾으시는 분이 계시다면 아쉽게도 Python은 오버 로딩을 허용하지 않는다는 비보를 전한다. 단, Pythoin은 파라미터를 받을 수 있는 방법을 상당히 다양하게 제공한다.)


이번 시간에는 클래스와 상속에 대해서 알아보았다. 어떤가, 이제 객체 지향과 클래스에 대해서 조금은 익숙해 느낌이 드는가? 다음 시간에는 우리의 프로그램을 조금 더 안정적으로 만들 수 있는 예외처리에 대해서 알아보도록 하자

지난 시간 우리는 객체 지향 프로그램이 무엇이고, 클래스가 무엇인지, 그리고 간단하게 클래스 사용의 예시를 살펴보았다. 이번 시간에는 Python에서 클래스를 사용하는 방법을 자세하게 알아보도록 하자


클래스(Class)의 정의와 사용

우선 다음의 코드를 타이핑하고 이야기를 나누어보자. 주석은 각 코드에 대한 설명이니 한번씩 읽어보길 바란다.

### class_example.py ###

class dog :					## class의 이름을 dog로 지정하였다.
    dog_name = ""				## dog의 멤버변수 dog_name을 정의하고 ""로 초기화

    def __init__(self,name) :			## 생생자, name을 파라미터로 받아 dog_name대입
        self.dog_name = name

    def bark(self) :				## 생성된 객체에서 호출하는 bark
        print("WOW "+self.dog_name)

    def self_bark() :				## class에서 호출하는 bark
        print("BOW")

if __name__ == "__main__" :
    my_dog = dog("puppy")			## my_dog(객체)를 dog(클래스)로 생성
    my_dog.bark()				## 생성된 my_dog의 bark메서드 호출

    dog.self_bark()				## dog(클래스)에서 직접 self_bark메서드 호출
    
    
출력 :
WOW puppy
BOW

self

일단 이 self 예약어는 어떤 의미가 있는 것일까?

Python에서 객체를 생성을 하면 자기자신을 지칭할 때 self라고 한다. 재미있는 점은 class내부에서 메서드를 정의할 때  생성된 객체가 이 메서드를 사용하려면 암묵적으로 self를 첫 번째 파라미터로 넘겨야 한다는 점이다.(실제 사용 시에는 이 self는 없는 것으로 취급한다. 예를 들어 some_function(self)가 정의되어있으면 사용할 때는 some_funtion()으로 사용하면 된다는 것이다.)

 

비교를 위해서 코드에서 2가지 함수를 다음과 같이 정의해보았다.

1. bark(self) : self를 인자로 받는 메서드이다. self를 인자로 받기에, dog에서 객체를 직접 넘겨주거나 dog로 생성이된 객체에서만(위 예시에서는 my_dog)이 bark를 사용할 수 있다.

2. self_bark() : self를 인자로 받지 않는 메서드이다. 따라서 생성이 된 객체는 사용할 수 없으며(생성된 객체는 함수호출시 묵시적으로 첫번째파라미터로 자기 자신을 전달한다. self_bark는 self파라미터를 받지 않는다.) dog클래스에서 직접 호출하면 사용 가능하다.

 

생성자

처음 보는 메서드로 __init__(self, name)이라는 메서드가 눈에 뜨인다. 이 __init__메서드는 이미 정의된 것으로 객체가 생성되는 순간(위 코드에서는 my_dog = dog("puppy")가 처리되는 순간)에서 자동적으로 동작하는 함수를 의미한다. 객체가 생성되는 순간 동작하는 함수이기에 "생성자"라고 부른다. 이 생성자에도 파라미터를 넘길 수 있는데, 위 예제에서는 객체 생성에 사용될 메서드이니 self와 이름인 name을 파라미터로 전달하고 있다. 전달받은 name변수는 dog_name에 저장한다. Python에 생성자처럼 사전 정의된 다음과 같은 것들도 있다.

1. __del__(self) : 소멸자, 객체가 사라질 때 행동을 정의한다.

2. __repr__(self) : 프린팅 지시자, print로 객체 자신이 호출될 때 return 값으로 무엇을 출력할지 지시한다.

3. __add__(self,other) : 연산자 "+"가 객체에 취해질 때 행동을 정의한다.

4. __cmp__(self,other) : 비교 연산자를 객체에 사용할 수 있게 행동을 정의한다.

 

클래스 변수

아래 코드에서 dog클래스 바로 아래 dog_name, travel처럼 정의된 변수를 클래스 변수라고 한다.

객체는 자신의 변수를 접근할 때 self. [변수명]을 붙인다. 코드에서 new_puppy = dog("happy")를 했다면 my_dog.dog_name은 "puppy", new_puppy.dog_name은 "happy"가 된다.(dog.dog_name은 ""로 출력이 된다)

 

이 클래스 변수는 재미있는 특징이 있다. 다음의 코드 main을 보자

class dog :
    dog_name = ""
    travel = list()

    def __init__(self,name) :
        self.dog_name = name

    def bark(self) :
        print("WOW "+self.dog_name)

    def self_bark() :
        print("BOW")

    def add_travel(self, value) :
        self.travel.append(value)

if __name__ == "__main__" :
    my_dog = dog("puppy")
    new_dog = dog("happy")

    my_dog.add_travel("home")
    

가장 마지막의 my_dog.add_travel("home")을 통해서 my_dog의 travel list에 "home"을 추가해 주었다. 그런데 웬걸?

>> new_dog.travel
["home"]
>> dog.travel
["home"]

이상하게 다른 객체인 new_dog와 객체의 틀인 dog클래스의 travel list에도 "home"이 추가된 걸 확인할 수 있다.

 

이는 클래스가 언제 객체로 바뀌느냐(이를 인스턴스화라고 한다.)의 문제이다.

 

Python에서 리터럴을 제외한 자료구조와 변수들은 class 바로 아래 정의를 했다면 객체끼리 공유된다. 이는 생성자에 자료구조를 정의하는 것으로 해결된다. 위에 코드로 해결책을 예를 들면 생성자에 self.travel = list()로 정의한다.(이러면 객체가 생성(인스턴스화)이 되고 self.travel이라는 list가 생성이 된 것이니 객체끼리 공유되지 않고, 생성된 객체 고유의 자료구조로 본다.)


이번 시간에는 객체 지향 프로그래밍을 하기 위한 가장 기본적인 방법인 클래스 생성과 사용에 대해서 알아보았다. 다음 시간에는 객체 지향 프로그래밍에서 빼놓은 수 없는 개념인 상속과 python에서의 상속 사용을 알아보도록 하자

Python과 친해지는 것을 넘어서서 매우 친해지려는 여러분들을 환영한다. 아직 Python의 흥미를 잃지 않았기를 바라며, 더욱 재미있고 심화적인 내용을 준비해 보았다. 그 첫 번째 시간은 객체지향과 클래스이다.

 


객체 지향 프로그래밍(Object Object Programming) 

객체 지향 프로그램이은 프로그래밍은 프로그램을 만들 때 사용된는 하나의 이론이다. 프로그램 그 자체를 명령의 집합으로 딱딱하게 보는 것이 아니라, 객체(Object)의 단위로서 이해를 하며, 객체와 객체의 상호작용으로 프로그램은 동작한다는 느낌이다. 현재는 하나의 Dictionary students를 가지고 있는 우리의 학생관리 프로그램을 객체 지행 프로그래밍으로 설계를 했으면 아래처럼 설계를 했을 것이다.

즉 학생이 등록된 학생DB를 하나의 객체로 보고, 여러 개의 학생 개체들이 상호작용을 하며 프로그램이 동작했을 것이다. 

 

 

객체 지향 프로그래밍의 장점

굳이 객체 지향으로 프로그램을 만들어야하는가 하는 의문을 가진 사람들도 있을 것이다. 물론 합당한 이유고, 필자도 객체 지향으로 몇 번 프로그래밍하기 전까지는 그 장점을 알 수 없었다. 일단 다음과 같은 장점이 객체 지향 프로그래밍을 의미 있게 한다.

 

① 프로그래밍 코드의 재사용성 향상

여러번 코드 안쳐도 된다는 거다. 학생 객체를 생성해두면 이걸 그대로 교수 객체, 노동자 객체로 만들 수 있다. 상속을 통해서 더욱 이 장점이 부각되는데, 상속이 어떤 것인지는 추후에 배우게 된다.

 

② 코드의 높은 응집력(Strong Cohesion과 낮은 결속력(Weak coupling)구현

응집력이란 한 모듈의 내부의 처리 요소들이 얼마나 기능적으로 연결되었는지를 의미하고, 결속력은 한 모듈과 다른 모듈 간의 상호 의존성을 의미한다. 따라서 "높은 응집력과 낮은 결속력"이 의미하는 것은 객체들이 얼마나 본인의 역할을 충실히 하면서 다른 모듈간의 의존을 하지 않는가를 의미한다.(유지보수 측면에서 이게 또 기가 막힌다. 장애 발생 시 고장이 난 모듈만 고치면 된다.)

 

③ 일상생활의 높은 수준으로 추상화를 통한 구현 용이성

자연어로 기술 시 나오는 컴포넌트들이 그대로 객체지향을 구현될 수 있다는 특징이다.  예를 들면 위의 설계도를 짜기 위해서 필자가 처음 생각한 것은 "학생 DB에 학생을 넣어 관리할 수 있는 프로그램"이었다. 그래서 학생 DB와 학생을 객체로 만들에 다이어그램을 만든 것이고, ver3 학생관리 프로그램은 실제로 저렇게 구현을 할 것이다.

 

 

클래스(Class)

클래스(clss)는 객체 지향 프로그래밍(OOP)에서 특정 객체를 생성하기 위해 변수와 메서드를 정의하는 일종의 틀.
객체를 정의하기 위한 상태(멤버 변수)와 메서드(함수)로 구성된다

- 출처 : 위키백과(ko.wikipedia.org/wiki/%ED%81%B4%EB%9E%98%EC%8A%A4_(%EC%BB%B4%ED%93%A8%ED%84%B0_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D)) - 

위는 위키백과에서 정의한 클래스이다. 무엇이든 뒤에 붙은 말은 핵심인데(붕어빵!), 첫 문장의 끝인 "일종의 틀"이라는 것이 눈에 들어온다. 

우선 아래와 같은 클래스 사용의 간단한 예시문을 통해서 이해를 높여보자 (* 문법은 지금 몰라도 무관하다.)

코드 : 
class student :
    name = ""

    def print_info(self) :
        print("im student "+self.name)

if __name__ == "__main__" :
    cheolsu = student()
    cheolsu.name = "cheolsu"
    cheolsu.print_info()

    younghee = student()
    younghee.name = "younghee"
    younghee.print_info()


출력 :
im studentcheolsu
im studentyounghee

우리는 class인 student를 만들었다. 그리고 student에는 name이라는 상태(멤버 변수)와, 자신의 정보를 출력하는 메서드(함수)를 정의했다. 그 후 학생 2명 철수와 영희를 student 클래스를 통해서 만들었다. 이제 클래스가 왜 "일종의 틀"인지 알 거 같다. 즉 정의된 class를 통해서 객체(Object)를 찍어내는(생성하는)것이다.

 

* if __name__ == "__main__"의 의미

더보기

* if __name__=="__main__" : 을 처음 보는 사람이 분명 있을 것이다.

위 조건문은 딱 읽으면 __name__이라는 변수가 __main__일 때 실행하는 조건문인데, 이 __name__이라는 변수는 프로그램의 내장 변수로 현재의 모듈의 이름을 담고 있는 내장 변수이다.

 

이때 직접 실행된 모듈은 __name__변수가 __main__이다. 따라서 저 조건문을 작성한 행위는 직접 실행 코드에 대해서 실행할 코드를 지정하는 일이다. 만약 다른 모듈에 if __name__=="__main__" 이 있더라도 직접 실행하지 않고, import 하면 그 모듈의 if __name__=="__main__" 코드 블록은 실행되지 않는다.

 

내장 변수에 대해서는 추후에 자세한 글을 포스팅할 예정이다.

 


맨 처음 advanced 한 tutorial시간이었는데, 잘 따라와 줘서 감사할 따름이다. 다음 시간에는 이 class가 어떻게 Python에서 동작하는지 깊게 이해하는 시간을 가져보도록 하자.

우리는 저번 학생 관리 프로그램을 만들 때 보다 문자열을 보다 세부적으로 다룰 수 있게 되었고, 정의된 모듈을 가지고 올 수 있게 되었으며, 사용자와 상호작용할 수 있는 방법을 알았으고 이를 파일로 입력 / 출력할 수 있는 방법을 알았다. (WOW 이렇게나 발전하다니, 오늘 저녁은 치킨이닭)

 

이제 이러한 새로 배운 지식으로 우리의 학생관리 프로그램을 다음의 조건들에 맞게 한 단계 진화시켜보자

 

추가된 개발조건

① 같은 폴더에 new_student.txt를 생성하고 그곳에서 학생정보를 가져와서 입력받는 기능을 추가할 것
② 통과한 학생에 대해서만 콘솔로 성적 정보를 입력받는 기능을 추가할 것

③ 모든 학생정보를 출력할 수 있는 기능을 students로 출력할 수 있게 기존 기능을 개선할 것
④ 파라미터는 students만 받아 랜덤 한 학생의 정보를 출력하는 기능을 추가할것(random 모듈의 randrange함수를 사용할 것)

 

* new_student.txt는 [학생 번호] [학생 이름]이 여러 줄로 구성된 파일이다.
* 이제 입출력을 배웠으니, students는 초기화 하자

 

우선 자신이 코딩할 수 있는 부분을 코딩해보고 아래의 필자의 코드를 참고하도록 하자(아직도 우리는 최상의 코드를 만들 수 없다 ㅠㅠ)


학생관리 프로그램 ver1보다는 훨씬 쉬워 보인다. 처음 조건부터 개발 착수해보자

① 같은 폴더에 new_student.txt를 생성하고 그곳에서 학생정보를 가져와서 입력받는 기능을 추가할 것

예시로 다음과 같은 new_students.txt를 생성하자

P_01 CHEELL
R_03 GLAADOS
T_04 SOMEONE

①은 다음과 같이 구현하면 될 것 같다.

students = dict()
## 새마음 새출발 새Dictionary

def add_students_file(target_dict) :
    with open("new_students.txt","r") as f:
        data = f.readlines()
        for i in range(len(data)) :
            temp_data = data[i].strip()
            student_num = temp_data.split()[0]
            student_name = temp_data.split()[1]
            target_dict[(student_num,student_name)] = list()
    with open("new_students.txt","w") as f :
        f.write("")

코드를 해석해보자. 저번과 비슷한 느낌으로 target_dict은 함수 내부에서 수정이 가능하니 파라미터로 받아왔고, with open("new_students.txt","r") as f를 통해서 파일 핸들을 읽기 모드로 열었다. 그 후 data에 f.readlines()로 나온 리스트를 저장했고, (현재 data = ["P_01 CHEELL", "R_03 GLAADOS"]) for문을 통해 data의 공백을 strip() 함수로 제거해 준후, 공백을 기준으로 나누어 이를 튜플로 target_dict;의 key로 삼아 list로 value를 초기화했다. 추가로 받아온 내용은 지우기 위해 다시 한번 f를 덮어쓰기 모드("w")로 열어 빈 값을 넣어주었다.

 

② 통과한 학생에 대해서만 콘솔로 성적 정보를 입력받는 기능을 추가할 것

②는 다음과 같이 구현할 수 있다.

def add_scores(target_dict) :
    keys = list(target_dict.keys())
    values = list(target_dict.values())
    stop = [False for i in range(len(keys))]

    for i in range(len(stop)) :
        if(len(value[i]) == 0 ):
            continue
        else :
            if(value[i][-1] <= 70) :
                stop[i] = True

    for i in range(len(keys)) :
        if(stop[i] == False) :
            score = int(input(keys[i][1]+"의 성적은? : "))
            value[i].append(score)

우선 target_dict의 key와 value를 list화 해서 각각 keys와 values에 저장하였다. 또한 저번처럼 stop을 만들어 응시한 마지막 시험이 70 이하인 학생들에 대해서는 입력을 받지 않도록 하였다. 중간 for문의 if(len(value[i]) ==0)는 맨 처음 응시하는 경우 value [해당 학생의 인덱스][-1]을 하면 배열의 길이가 0이라 마지막을 찾을 수 없어 에러가 발생하기 때문에 continue를 지시하였다. 그 후 stop[i]값이 False인 학생들을 int(input(score = int(input(keys[i][1]+"의 성적은? : "))을 하여 응시결과를 입력받았다.

* stop을 생성한 stop = [False for i in range(len(keys))] 은 Python의 고급 기능인 내포(Comprehension)인데, 조만간 볼 것이다.(아니 미 X놈아 그러면 지금 어떻게 하라고!)

 

③ 모든 학생정보를 출력할 수 있는 기능을 students.txt로 출력할 수 있게 기존 기능을 개선할 것

③은 다음과 같이 구현할 수 있다.

def show_students(target_dict,show_type) :
    keys = list(target_dict.keys())
    values = list(target_dict.values())
    if(show_type == 1) :
        for i in range(len(target_dict)) :
            print("학생번호 "+keys[i][0]+": "+keys[i][1])
    elif(show_type == 2) :
        with open("student.txt","w") as f:
            for i in range(len(target_dict)) :
                f.write("학생번호 "+keys[i][0]+": "+keys[i][1]+"\n")

ver1에서 만들었던 show_students함수를 개량했다. 콘솔로 출력할지(show_type : 1), 파일로 출력할지(show_type : 2)를 지정하였고, show_type에 따라 출력을 달리하였다. 파일로 출력할 땐 마지막에 개행을 함으로 가독성을 높였다.(write() 함수는 end="\n"뭐 이런 파라미터가 없다. print와 다르게 개행을 명시적으로 해주어야 된다.)

 

④ 파라미터는 students만 받아 랜덤한 학생의 정보를 출력하는 기능을 추가할 것(random 모듈의 randrange함수를 사용할 것)

마지막 조건인 ④는 일단 random이라는 모듈을 불러오고 randrange()의 사용을 보아야 한다. randrange()는 2개의 정수 파라미터를 받아 그 사이의 값(인덱스처럼 계산된다. randrange(0,10)이면 0~9중 하나 반환)을 랜덤으로 반환하는 함수이다. 다음과 같이 구현할 수 있다.

import random

def rshow_students(target_dict) :
    limit = len(target_dict)
    keys = list(target_dict.keys())
    o_index = random.randrange(0,limit)
    print("학생번호 "+keys[o_index][0]+": "+keys[o_index][1])

우선 random 모듈을 import 했다. 함수 내부에서는 randrange의 사용을 위해서 범위를 설정할 limit정수를 len(target_dict)으로 초기화했고, 출력했다.

 

이제 우리 학생관리 프로그램의 전체 코드는 다음과 같다.

import random

students = dict()

def add_students(target_dict, stu_num, stu_name) :
    target_dict[(stu_num, stu_name)] = list()

    
def show_students(target_dict,show_type) :
    keys = list(target_dict.keys())
    values = list(target_dict.values())
    if(show_type == 1) :
        for i in range(len(target_dict)) :
            print("학생번호 "+keys[i][0]+": "+keys[i][1])
    elif(show_type == 2) :
        with open("student.txt","w") as f:
            for i in range(len(target_dict)) :
                f.write("학생번호 "+keys[i][0]+": "+keys[i][1]+"\n")

def rshow_students(target_dict) :
    limit = len(target_dict)
    keys = list(target_dict.keys())
    o_index = random.randrange(0,limit)
    print("학생번호 "+keys[o_index][0]+": "+keys[o_index][1])
    
                

def add_score(target_dict, score_list) :
    keys = list(target_dict.keys())
    values = list(target_dict.values())
    stop = [False,False,False,False,False]

    for i in range(4) :
        add = True
        for j in range(len(values)) :
            if(stop[j] == True) :
                continue
            else :
                values[j].append(score_list[i][j])
                if(values[j][-1] <= 70) :
                    stop[j] = True
                    
def add_students_file(target_dict) :
    with open("new_students.txt","r") as f:
        data = f.readlines()
        for i in range(len(data)) :
            temp_data = data[i].strip()
            student_num = temp_data.split()[0]
            student_name = temp_data.split()[1]
            target_dict[(student_num,student_name)] = list()
    with open("new_students.txt","w") as f :
        f.write("")

def add_scores(target_dict) :
    keys = list(target_dict.keys())
    values = list(target_dict.values())
    stop = [False for i in range(len(keys))]
    print(keys,stop)

    for i in range(len(stop)) :
        if(len(values[i]) == 0 ):
            continue
        else :
            if(values[i][-1] <= 70) :
                stop[i] = True

    for i in range(len(keys)) :
        if(stop[i] == False) :
            score = int(input(keys[i][1]+"의 성적은? : "))
            values[i].append(score)
    

뿌듯한 마음이 들지 않는가? 이번 시간이 완벽하게 이해가 되었다면 어디 가서 이제 제가 Python좀 씁니다라고 자랑해도 된다. 이번 시간까지 해서 길다면 길고 짧다면 짧았던 Python과 친해지기가 끝이 났다. 아직 얼굴만 좀 아는 사이이고, 조금 더 깊은 관계를 맺기 위해서는 가야 할 길이 태산처럼 많이 남아있다. 학습을 할 때 조바심을 내는 것은 정말 좋지 않다. 빠르게 흥미가 떨어지고, 두 번 다시 안 보게 되는 것만큼 학습을 좀먹는 것도 드물다고 생각한다. 이제 필자는 Python과 "매우"친해지기 포스팅의 클래스와 객채를 준비하여 다시 한번 여러분들의 Python의 세계의 길잡이를 역할을 하겠다.

+ Recent posts