먼길 오느라 수고들 많으셨습니다. 이번 포스팅은 Python과 매우 친해지기 그 마지막 시간인 Decorator이다. 물론 여기까지로 Python의 100%를 안다고 생각하면 큰 오산이다. 다음 시간부터는 모듈 알아보기 포스팅을 올릴 예정이니, 계속해서 Python에 대한 애정을 멈추지 말아주었으면 한다.


Decorator

여러분이 만들고 있는 함수의 프로그램 동작시간을 측정한다고 가정을 하자. 간단하게 import datetime모듈의 현재시간을 출력하는 datetime.datetime.now() 함수를 이용해보자. 

## 코드 : 
import datetime

def add(val1, val2) :
    print(datetime.datetime.now())
    print(val1+val2)
    print(datetime.datetime.now())
    return val1+val2

def sub(val1, val2) :
    print(datetime.datetime.now())
	print(val1-val2)
    print(datetime.datetime.now())
    return val1-val2

def div(val1, val2) :
    print(datetime.datetime.now())
    print(val1/val2)
    print(datetime.datetime.now())
    return val1/val2

if __name__=="__main__" :
    print(add(3,5))
    print("\n")
    print(sub(3,5))
    print("\n")
    print(div(3,5))
    
## 출력 :

2021-04-10 21:40:10.219000
8
2021-04-10 21:40:10.271000
8


2021-04-10 21:40:10.309000
-2
2021-04-10 21:40:10.337000
-2


2021-04-10 21:40:10.373000
0
2021-04-10 21:40:10.400000
0

으흠; 모든 함수에 대해서 datetime.datetime.now()를 2번씩 처주는 동일한 일을 해줄 필요가 있을까? Decorator는 이러한 문제의 해결책이다. 다음의 예시를 보자

import datetime

def deco(func) :
    def decorated() :
        print(datetime.datetime.now())
        func()
        print(datetime.datetime.now())
    return decorated
    
@deco
def function() :
    print("Function start")

if __name__=="__main__" :
    function()

우선 함수 그 자체를 파라미터로 받아들이는 또다른 함수 deco를 정의하였다. deco의 내부에는 인라인 함수, decorated를 정의하여 func() 실행 앞뒤로 우리가 원하는 행위인 print(datetime.datetime.now())를 실행해주었고, deco함수는 decorated 함수를 리턴하는 것으로 정의부는 마무리된다. 

 

decorated 받을 함수인 functiond은 앞부분에 @deco를 기술하여 이 함수가 deco함수로 decorated 될 것을 명시해 주었다.

 

* Decorator에서 인자를 사용하려면, @deco("some")으로 사용하면된다. 


자 이렇게 Python과 매우 친해지기 포스팅이 마무리되었다. 필자도 수고했고, 여러분도 수고 많았다. 처음에 Python을 그림에 비유했는데, 이는 아직도 유효하다. 여러분들은 그림의 기초적인 테크닉을 배운 것이고, 이제 여러 가지 프로그램을 만들 때 이 정보들이 여러분에게 큰 도움이 될 것이라고 확신한다. 

이번 시간은 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의 학생 번호를 가져와서 있으면 실행하는 것이 빠르다.


 

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

지난 시간 우리는 객체 지향 프로그램이 무엇이고, 클래스가 무엇인지, 그리고 간단하게 클래스 사용의 예시를 살펴보았다. 이번 시간에는 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의 세계의 길잡이를 역할을 하겠다.

사실 입출력(콘솔)의 바로 다음 포스팅을 입출력(파일)으로 하려 했는데, 명절 부모님의 마음으로 "얘 이것도 가져가렴"하다 보니 문자열 기본 다지기와 모듈에 관한 이야기를 하고 드디어 입출력(파일)을 포스팅한다.  사실 개발하는 사람들이나 Python콘솔의 화면을 보고 있지(히히 콘솔 까맣다 이쁘다.) 컴퓨터사용자들이 Python콘솔을 보고 있진 않다. 프로그램에서 입출력 화면을 구현해주거나, 파일을 통한 입출력을 하는 것이 많기에 이번 시간은 우리의 능력을 더욱 끌어올려주는 내용으로 준비했다.


파일 입출력 - 읽기

다음의 글을 복사해서 text.txt를 준비하자

Hello! I'm Python study
THIS IS My FIRST TIME TO PYTHON
PYTHON IS FUN
IM LIAR

Python에서 파일을 처리하기 위해서는 우선 파일을 지정하는 변수가 있어야한다. 관습적으로 f를 변수명으로 사용한다. 다음의 코드를 text.txt가 있는 곳 같은 폴더 안에 생성하자.

 

f = open("text.txt","r")

* open함수는 len(), sum()같은 built-in 함수로 첫 번째 파라미터로 파일의 이름(절대 / 상대 경로 다 된다.)과 두 번째 파라미터로 모드를 지정한다.(모드 목록 : r(읽기), w(덮어쓰기), a(이어 쓰기), b(바이너리), t(텍스트), +(업데이트), x(생성 검사))

(두 번째 파라미터는 기본값 읽기로 필수는 아니지만 지정하는 것이 명시적으로 좋다.)

 

이렇게 open함수로 f는 파일 "text.txt"에 대한 읽기 작업을 할 수 있다.(모드가 "r"이다) 주로 사용하는 함수는 다음의 것들이 있다.

>> f = open("text.txt","r")
>> data = f.read()
>> print(data)
Hello! I''m Python study
THIS IS My FIRST TIME TO PYTHON
PYTHON IS FUN
IM LIAR

>> f = open("text.txt","r")
>> data = f.readline()
>> print(data)
Hello! I''m Python study\n

>> f = open("text.txt","r")
>> data = f.readlines()
>> print(data)
["Hello! I''m Python study\n", 'THIS IS My FIRST TIME TO PYTHON\n', 
'PYTHON IS FUN\n', 'IM LIAR']

## f = open("text.txt","r")을 계속 써주는 이유는 f는 자기가 읽은 마지막 부분을 
## 기억하기 때문이다. 만약 f.read()를 한 후 f.readline()할 경우 이미 마지막까지 읽었기에 
## f.readline()은 아무값도 가져오지 않는다.
## 물론 지금은 함수의 사용을 보여주기위해 계속 f를 열었고, 보통 이렇게 안한다.

 

파일 입출력 - 쓰기

쓰기 또한 f을 열고 f에 대한 쓰기 함수를 통해서 작성할 수 있다. 대표적인 쓰기 함수는 write, writeline들이 있지만, wrtie만 다루면 보통 문제 될 건 없다.

>> f = open("text.txt","a")
>> f.write("hello!")
## 지금은 이어쓰기(a)모드로 text.txt를 열었기에 마지막줄에 hello!가 추가되어 있다.
## write함수는 파라미터로 전달된 값을 쓰고(이건 무조건 문자열만 받는다)
## write함수도 반환이 있는데, 쓴 글자수를 정수로 반환한다.

>> f = open("text.txt","w")
>> f.write("hello!")
## 이번에는 덮어쓰기(w)모드로 text.txt를 열었기에 모든입력되 있던 글자는 없어지고
## hello! 만 text.txt에 있다.

 

파일 입출력 - 유의사항

* 파일에 대한 작업이 끝난 다음에는 close() 함수를 호출해서 꼭 파일 핸들(처리하는 부분)을 닫아주어야 한다! [아래 참조]

>> f = open("text.txt","a")
>> f.write("Do something")
>> f.close()
## 그렇지 않으면 python에서 계속 파일의 접근권을 가지고 있어 그 파일에 대한 작업이 
## 프로그램 실행 끝까지 불가할 수도 있다.

이러한 것을 방지하기 위해 with문을 소개한다.

 

with ~ as

with문의 사용방법은 다음과 같다.

with open("text.txt","r") as f:
    data = f.read()

이런 식으로 with는 실행할 함수와 as는 그 결과를 받아줄 변수를 정의한다. 이 변수는 with문 안에서 밖에 사용을 못하기 때문에 data = f.read()까지 with블록이 끝나고 난 후에는  f.close()가 자동으로 실행되어 파일 핸들 관리가 용이해진다.

 

* 이 as에 대해서는 할 말이 조금 더 있는데, 이름이 긴 모듈들에 대해서도 as를 많이 사용한다. 예를 들면 데이터 처리 모듈인 pandas는 import pandas as pd로 사용하고 크롤링 모듈인 BeautifulSoup4도 from bs4 import BeautifulSoup as bs로 사용하기도 한다.

 


이제 우리는 파일 입출력을 자유롭게 할 수 있게 되었다! 사실 입출력은 버퍼를 이용한 더 효율적인 방법이 존재하고, 입출력을 도와주는 FileInput같은 모듈들도 많이 이용하고 있다. 그러나 open함수를 통한 기본적인 파일입출력을 정확하게 이해하는 것이 추가적인 학습 및 프로그래밍에 많은 도움을 줄 것이라고 확신한다. 다음 시간에는 Python과 친해지기 마지막시간으로 저번에 만들었던 학생관리 프로그램ver1을 조금 더 업그레이드하는 시간을 가져보자

+ Recent posts