아직 우리는 기초단계에 있기 때문에 지난 글을 읽고 오지 않으셨다면 꼭 읽고 와주시기를 바란다.

2021.08.27 - [Python/파이선과 친해지기] - [Python] - Python과 예쁘게 친해지기-PyQt

 

[Python] - Python과 예쁘게 친해지기-PyQt

오랜만에 Python과 친해지기 강의를 쓰기 위해 키보드를 잡았다. 사실 이 정도면 Python에서 사용되는 대부분의 용어나 개념을 설명했다고 생각하는데, client 프로그램을 만들기 위한 QT Library를 다

tutoreducto.tistory.com

 

## myqt.py

from PyQt5.QtWidgets import QApplication, QWidget, QLabel
import sys

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = QWidget()
    first_window.show()
    app.exec_()

우리가 지난시간 작성한 코드다. 이름대로 기 때문에 다 어떤 동작을 하는지 대충 예상할 수 있는데, 특이한 인스턴스가 하나 보인다.

 

"QApplication"

 

얘는 뭐하는 애일까?


QApplication과 EVENT

 

"QApplication은 Q-Widget 기반의 애플리케이션 기능을 포함한 QGuiApplication의 특수한 형태입니다. QApplication은 위젯의 초기화 / finalization을 처리합니다."

- qt공식 홈페이지 : https://doc.qt.io/qt-5/qapplication.html#details -

 

말이 조금 어려운데, 결국  Q-Widget이 동작하기 위한 기반 위젯이 QApplication이다. 요놈은 다른 QWidget의 생성 / 전시 / 삭제를 처리한다. 우리의 앱에서 최초의 QWidget이 나오기 전에 QApplication이 있어야 하는 이유가 바로 QApplication이 QWidget의 생성을 담당하기 때문이다.

 

한 프로그램에는 QApplication은 한개만 존재한다. 창이 2개 이상이 되더라도 QApplication은 하나만 존재한다.(음.... Python형 싱글턴 객체라고 볼 수 있겠다.) 여담으로 Qt기반의 Widget이 아닌 경우에 저기 정의에 있는 QGuiApplicatio을 사용한다. 홈페이지에 나와있는 기능은 또한 다음의 것들도 처리하는 것으로 적혀있다. 의역하니, 원문을 보고 싶은 사람은 위 qt공식 홈페이지를 방문하자(여기는 Qt가 C++형태이니 함수명이나 클래스명은 그냥 참고만 하자)

  • palette(), font()와 doubleclickInterval()등의 데스크탑 세팅을 초기화한다. 또한 제어판 설정 같은 데스크톱 세팅을 추적하고 반영한다.
  • 이벤트 처리를 담당한다. 관련된 widget에서 이벤트를 송/수신한다. 사용자는sendEvent()와 postEvent()를 위젯에 사용하여 자신만의 이벤트를 전송한다.
  • Command line 인자를 파싱하고 적절하게 초기화한다.-> 요래서 QApplication에 sys.argv가 인자로 들어가는 ㅋㅋ루삥뽕이다.
  • QStyle()으로 애플리케이션의 디자인을 설정한다. setStyle()로 이건 변경할 수 있다.
  • trainslate()기능을 이용해서 지역 언어로 설정할 수 있다.
  • desktop()이나 clipdoard()등을 이용해서 매지컬 오브젝트를 사용한다.
  • QApplication은 widget의 정보를 추적한다. 즉 widgetAt()으로 위치도 가져오고 topLevelWidget()으로 리스트도 가져오고 한다.
  • setOverrideCursur()매서드를 이용해서 애플리케이션의 마우스 움직임을 처리한다.

 

두 번째 기능에 집중해보자. QApplicationd은 이벤트 처리를 담당한다고 한다. 이벤트는 무엇이고 이걸 어떻게 처리한다는 것인가? 


이벤트

 

이벤트는 dispatch 되는 신호이다. 구현은 QEvent()라는 객체를 상속받아 구현되는데, 외부의 자극(유저의 입력 / 프로그램의 움직임 등 겁나 광범위하다.)과 그에 대하 한 결과로 이루어져 있다. 당연하게도 프로그램을 사용하면 엄청나게 많은 이벤트들이 발생한다. 그걸 정확하게 처리하는 기술이 바로 Event Loop이다. QT에는 이벤트 큐(Event Queue)라는 자료구조가 존재한다. 이벤트는 발생된 순서대로 이 Event Queue에 push 한다. QApplication은 이 이벤트 큐를 무한루프 하며 이벤트를 하나하나 처리한다. 큐에 저장된 Event는 Event Handler에 의해 수집되며 이벤트를 accept 할지 ignore 할지를 판단한다. 결정하면 Event를 받는 slot에 정의된 기능에 따라 Event는 처리되고, 다음 Event를 검사한다.


이론적인 이야기가 많았다. 물론 이걸 몰라도 GUI 프로그래밍을 할 수는 있지만 한 단계 높은 성장을 위해서는 필수적으로 알아야 하는 내용이라 적어두었으니, 꼭 관심 가지고 읽어주시기 바란다. 다음 시간에는 드디어 우리의 QWidget을 조금 발전시켜보자.

오랜만에 Python과 친해지기 강의를 쓰기 위해 키보드를 잡았다. 사실 이 정도면 Python에서 사용되는 대부분의 용어나 개념을 설명했다고 생각하는데, client 프로그램을 만들기 위한 QT Library를 다루지 않은 게 항상 마음에 걸렸다. Python

누구나 인정하듯 빠른생산속도를 압도적인 장점으로 다른 언어와 차별점을 두는 특징이 있다. 이번 Python과 예쁘게 친해지기 시간을 통해서 Python으로 GUI Programming에 입문하는 사람이 많았으면 좋겠다. 프로토타입 작성할 때 이거만 한 게 없다.

 

* 본 포스팅은 PyQt5를 지원합니다.


GUI

 

"Graphic User Interface"

 

그렇다. 사용자는 우리 프로그래머들처럼 검정화면만 보고 희열을 느끼는 변태가 아니다(헤헿...콘솔....까맣다...이쁘다...) DOS운영체제가 window로 바뀌며 그래픽 요소가 생기듯, 언제나 사람-지향적인 환경은 글자가 즐비한 환경이 아닌 Graphic, 가시적인 요소로 이루어진 환경이다. 전통적인 프로그램 언어인 C++에서도 WINDOW API로 당연히 GUI Programming을 지원하며, QT라는 고-급 라이브러리를 이용해서 복잡한 C++ WINDOW 프로그램을 이쁘게 만들 수 있다. JAVA에서는 AWT나 SWING 최근 들어서는 JAVAFX를 통해서 JAVA 나름대로의 그래픽 환경을 조성한다. 우리 Python은 실행 속도의 한계도 있고 기타 여러 가지 이유로 C++에 있는 QT LIBRARY를 Pythonic 하게 변환하는(즉 컴파일 시에는 원래 QT로 바뀌는) PyQt를 제공한다.

<PYQT 로고>


PYQT의 설치

새로운 물건을 발견하면 당연히 설레는 기분이 먼저아니겠는가 우선 바로 손으로 찍어먹어 볼 수 있게 PyQt를 설치하겠다.  

c$> pip3 install pyqt5

단순한 명령어다. pyqt5또한 pip를 통해서 다운로드 가능하니 다운로드를 하자. 다음 idle에서 import PyQt5 시  에러가 없으면 설치에 성공한 것이다.

>>> import PyQt5
>>>

HELLO PYQT!

우리의 영광스러운 첫코드는 myqt.py로 이름을 짓도록 하자. 앞으로 이 코드 기준으로 많은 예제를 실습할 예정이니, 떨리는 손을 다른 손으로 잡고 코딩에 들어가자 자 이제 PyQt5를 만나러 가보자

## myqt.py

from PyQt5.QtWidgets import QApplication, QWidget, QLabel
import sys

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = QWidget()
    first_window.show()
    app.exec_()

아직은 저렴해 보이는 이 도화지가 우리 여행의 출발점이다. 주석을 조금 달아 코드를 설명하자면 다음과 같다.

  • from PyQt5.QtWidgets import QApplication, QWidget, QLabel : PyQt5.QtWidgets에는 화면에 표현할 요소인 Widget이 사전에 정의되어 있다. 이번 코드에서는 QApplication과 QWidget만 사용한다. 어 나 QLabel은 안 썼는데?
  • app = QApplication(sys.argv) : 시스템 인자를 매개변수로 QApplication을 구동한다. QApplication은 이벤트 루프를 처리하는 widget으로 생각해주면 된다. 
  • first_window.show() : 모든 widget은 parent의 속성에 종속적이지만, 단독이라면 보이지 않는(invisible) 상태이다. show() 함수를 호출하여 눈에 보이게 하였다.
  • app.exec_() : QApplication을 시작한다. 이는 기존 QT의 exec와 같은 함수이나, Python에서 exec은 예약어이기에 언더스코어(_)가 붙는다. 이는 print도 동일하다. 포스팅을 따라오다 보면 print_()를 만날 수 있다.

새로운 내용을 시작하려니 필자도 벌써 설레는 기분이다. 이번 시간에 우리는 pip를 이용해서 PyQt5를 설치하였고, 우리의 처음 Application을 띄워보았다. 다음 시간에는 우리 도화지를 조금 더 이쁘게 꾸며줄 템플릿인 MainWindow을 알아보고 QAppliaction에 대해서 조금 자세하게 알아보자

Python과 다른 언어와의 큰 차이점을 두자면(요즈음 들어서는 많은 언어가 지원하기에 퇴색된 기능이지만) Type으로부터의 자유로움을 손에 꼽을 수 있다.

>>> a=1 >>> b="heyhey" >>> type(a) <class 'int'> >>> type(b) <class 'str'>

위와 같이 다른 언어에서의 변수 선언 시 type을 같이 지정해 주어야 하는 재래식 언어와는 많은 차별점을 둔다.

다만 이런 Python의 장점은 코드의 규모가 커지고 방대해질 수록 많은 문제를 야기하며, 프로그램의 동작상에서 동작하는 Runtime level의 문제라기보다는, 프로그래머 사이 작성되는 코드 혹은 과거의 나와 싸우고 있는 프로그래머들에게 Type의 힌트를 줄 필요성이 생겼다.

<최솟값을 찾는 함수 : JAVA의 경우>

// 최소값을 찾는 JAVA함수, int를 반환하고 ArrayList<int>를 받는다는 것을 함수에서 알 수 있다. public int findMinimumNumber(ArrayList<int> arr){ int result = arr[0]; for(int a : arr) if(result > a) result=a; return result; }

<최솟값을 찾는 함수 : Python의 경우>

### 내장함수 min에 의해 처리될 수 있는 value를 입력으로 받고 그의 원소값을 반환 ### 어떤형의 parameter를 받고 반환하는지 알 수 없다. def find_minimum(value) : return min(value)

위와 같은 문제들을 해결하기 위해 함수에 주석을 달거나 하는 프로그래머 개개인의 노력을 이용했다.

### param : list ret : int def find_minimum(value) : return min(value)

Python도 이 문제를 인식한 듯 Type Annotation을 제공한다. 다음과 같다.

def find_minimum(value : list) -> int : return min(value)

사용법은 간단한다.

  • 변수명 뒤 콜론(:)을 붙이고 type명을 기술 type명에는 클래스도 들어갈 수 있다.
  • 함수의 반환은 화살표(->)을 붙이고 type명을 기술 당연하게도 여기도 클래스가 들어갈 수 있다.

단! 이렇게 적는 type annotation은 주석의 확장으로 밖에 여겨지지 않으며, 인터프리터는 해석 시 type annotation을 염두하지 않는다.

오랜만에 Python과 매우 친해지기 글을 포스팅한다. 사실 지난 Decorator로 어지간한 Python사용법은 거의 포스팅했다고 생각했는데, 몇 가지 빼먹은 게 있더라, 차츰차츰 올리는 걸로 하고, 이번에는 다시 올리는 Python과 매우 친해지기 파라미터 편이다.


파라미터의 사용

 

파라미터는 어디에 사용될까? 아래의 예시를 보자

##코드
class my_class :

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

    def print_name(self) :
        print(self.my_name)
    
if __name__=="__main__" :
    chulsu = my_class("철수")		## my_class에 파라미터로 "철수" str 전달
    chulsu.print_name()
    
    
    
## 출력결과
철수

 

 

위와같이 클래스(def__init__에) 나 함수 블록에 전달하는 "인자"를 우리는 파라미터라고 부른다.

이런 파라미터들은 함수나 클래스에 정의된 대로 넘겨줄 수 있다. 다음의 예시를 보자

## 코드
def salute(name,job) :
    print("my name is "+name+" and my job is "+job)


if __name__=="__main__":
    salute("chulsu","java programmer")
   
   
   
## 출력결과
my name is chulsu and my job is java programmer

파라미터에 여러가지값을 넘길 수 있는 방안이 있을까?? 가령 학생이 10명 있는 반의 학생관리 프로그램에서 학생정보를 출력하는 함수를 정의한다고 생각하자. 기본값으로 ""를 주어 여러 학생을 받는다는 형식으로 본다면 다음과 같이 길고 더럽고 추잡한 코드가 나올 것이다.

## 코드
def print_student(stu1="", stu2="", stu3="", stu4="", stu5="", stu6="",
                      stu7="",stu8="",stu9="",stu10="") :
    print("stu1 : "+stu1)
    print("stu2 : "+stu2)
    print("stu3 : "+stu3)
    print("stu4 : "+stu4)
    print("stu5 : "+stu5)
    print("stu6 : "+stu6)
    print("stu7 : "+stu7)
    print("stu8 : "+stu8)
    print("stu9 : "+stu9)
    print("stu10 : "+stu10)

if __name__=="__main__":
    print_student("chulsu","yonghee","minsu","jinsu","binsu","osu",stu9="pengsu")
    
    
    
## 출력결과
stu1 : chulsu
stu2 : yonghee
stu3 : minsu
stu4 : jinsu
stu5 : binsu
stu6 : osu
stu7 : 
stu8 : 
stu9 : pengsu
stu10 :

 

이렇게 된다. 10명이라면 다행이지만, 100명 200명이면 어떻할까?(리스트로 받으면 된다고? 엄만 갈 거야 리스트는 여기 있어 그럼) 이와 같은 문제를 멋지게 해결할 수 있는 방법이 가변 파라미터이다.


args, kwargs

args의 약어는 arguments, kwargs의 약어는 Keyword Arguments이다. 각각 튜플과 딕셔너리형태로 다수의 파라미터를 제한 없이 받아들일 수 있는 특징이 있다. 마치 java의...이나 Kotlin의 vararg 같은 특성을 가진다. args의 앞에는 *, kwargs앞에는 **로 이 파라미터들이 가변이라는 것을 표현하는데, 다음의 예시를 보자 

### args의 예시, 코드
def print_student(*stu) :
    
    print(type(stu))        ## 가변인자 stu의 자료구조는 tuple
    
    index = 0
    for s in stu :
        print("stu"+str(index)+" : "+s)
        index += 1
    

if __name__=="__main__":
    print_student("chulsu","yonghee","minsu","jinsu","binsu","osu")
    
    
### args의 예시, 출력결과
<class 'tuple'>
stu0 : chulsu
stu1 : yonghee
stu2 : minsu
stu3 : jinsu
stu4 : binsu
stu5 : osu

출력 결과

다음은 kwargs의 예시이다.

### kargs의 예시, 코드
def print_student(**stu) :
    
    print(type(stu))        ## 가변인자 stu의 자료구조는 dictionary
    
    for k,v in stu.items() :
        print(k + " : " + v)
    

if __name__=="__main__":
    print_student(stu1 = "chulsu",stu2 = "yonghee",stu3 = "minsu")
    
    
## kargs의 예시, 출력결과
<class 'dict'>
stu1 : chulsu
stu2 : yonghee
stu3 : minsu

 

 

흔히 사용하는 우리의 python내장함수 print도 가변인자로 구성되어 있다. print는 다음과 같이 정의되어있다.

 

  ★ print(*objects, sep=' ', end='\n,  file=sys.stdout, flush=False)

 

맨앞 *objests가 가변인자 *args로 지정되어 있는 것을 알 수 있다.

 

우리는 저번 학생 관리 프로그램을 만들 때 보다 문자열을 보다 세부적으로 다룰 수 있게 되었고, 정의된 모듈을 가지고 올 수 있게 되었으며, 사용자와 상호작용할 수 있는 방법을 알았으고 이를 파일로 입력 / 출력할 수 있는 방법을 알았다. (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