WeSeong Log in
← Back to posts
프로그래밍 언어

파이썬 파일 읽기와 쓰기, 클래스

파이썬 파일 읽기와 쓰기, 클래스

파이썬 관련한 글은 계속 될 수 있겠지만 일단, 여기까지 진행하고 네트워크나 리눅스 관련 게시글이 진행될 예정입니다. 파이썬 관련 게시글이 4개밖에 안되는데 사실 이 4개의 글로 파이썬을 전부 이해한다는 건 말도 안됩니다. 제가 올린 게시글 하나의 소제목 하나만으로도 여러 게시글로 충분히 많은 논의가 될 정도이기 때문에, 지금까지 올린 글은 요약집 혹은 개론이라고 봐주시면 될 것 같습니다. 아니면 '이런 순서로 공부하면 되겠구나'라고 생각해주셔도 될 것 같습니다. 그럼 시작하겠습니다!🙃


Ⅰ. 개요

파이썬으로 파일을 불러오거나 생성할 수 있다. 파일을 읽고 쓰는 건 생각보다 중요하며 다양하게 활용될 수 있다. 파일을 읽거나 쓸 때 모두 사용하는 함수는 open() 함수가 있다. 파일을 읽을 때는 readline(), readlines() 함수를 사용한다. 파일을 쓸 때는 write(), writelines() 함수를 사용한다. 객체 지향 프로그래밍의 핵심으로 클래스 개념이 있다. 파이썬도 마찬가지로 객체를 클래스로 만들어 속성행위 기반으로 프로그래밍한다. 클래스는 멤버변수메소드로 이루어지며, 크게 생성, 동작, 소멸의 '라이프 사이클'을 가진다. 클래스의 특별한 메소드로 __del__() 함수와 __add__() 함수가 있다. 클래스는 다른 클래스에 상속될 수 있는데, 여기서 상속은 '자동차 바퀴가 자동차에 속하게 되는 것'처럼 부모 클래스와 자식 클래스의 관계를 가지는 걸 의미한다.


https://docs.python.org/ko/3/library/functions.html#object



Ⅱ. 파이썬 파일 읽기와 쓰기

파일을 읽고 쓰는 걸 왜 파이썬으로 배우는지 의아할 수 있다. 하지만 파일을 읽고 쓰는 건 프로그래밍에서 생각보다 중요하다. 예를 들어, 웹 프로젝트를 진행할 때 수많은 데이터를 가져와 데이터베이스에 넣어 줄 때, 파이썬을 활용한 자동화가 유명한데 자동화 과정에서 입력 받은 걸 파일로 생성하거나 엑셀, 한글, PDF, 텍스트 파일 등을 불러와 활용할 때 사용되는 게 파일 읽기와 쓰기이다. 물론, 이 게시글에서 언급하는 내용으로 위의 예시를 전부 할 수 있다는 건 아니다. 파일 읽기, 쓰기에 사용할 수 있는 많은 라이브러리가 있으며 기본적인 파일 읽기, 쓰기를 이해했다면 추가적으로 다른 라이브러리를 공부해보는 걸 추천한다!😊


※ TIP: 만약 리눅스같이 CLI(Command-Line Interface)을 이용한다면 파일 읽고 쓰기가 더 중요하다. 리눅스는 클라우드 서비스를 활용할 때 사용되는 운영체제인데 CLI는 단순히 텍스트 형태로 데이터를 전달하고 프로그램을 작동시키기 때문에, 텍스트로만 되어 있는 파일을 읽고 쓰며 자동화 프로그램을 만들어 활용할 수도 있다. 윈도우 같은 GUI(Graphical User Interface)는 사용자가 마우스와 키보드로 간단하게 이용할 수 있다. 그래픽 요소 때문인데 CLI는 기본적으로 그래픽 요소를 사용하지 않는다. 그래픽 요소를 사용하게 되면 많은 리소스(Resource)가 필요하게 되며 메모리 관리가 굉장히 중요해진다. 그래서 파일 읽기와 쓰기를 잘 알아두면 좋다.


파일 읽기와 쓰기 모두 3단계 과정을 거친다. 첫 번째, 파일을 연다. 두 번째, 파일을 읽거나 쓴다. 마지막으로 세 번째, 파일을 닫는다.

첫 번째, 파일을 여는 과정은 읽거나 쓸 때 반드시 거치는 과정이다. open() 함수를 이용하며 open() 함수의 return으로 파일 핸들러를 받을 수 있다. 이때 바로 파일을 읽거나 쓸 수 있는 것은 아니며, 정상적으로 작동했다면 파일을 직접 활용하기 전 상태이다.

file = open('경로와 파일이름', '읽기 모드, 쓰기 모드 중 선택', '인코딩 요소')

open() 함수는 더 다양한 인자가 있지만 기본적으로 위의 예시의 인자가 많이 사용된다. 여기서 경로가 중요한데 경로 때문에, 많은 오류를 접할 수 있다. '읽기 모드'는 기본적으로 'r'로 작성할 수 있고 '쓰기 모드''w'로 작성한다. 그 외에도 더 다양한 모드가 있기 때문에, 공부해보면서 더 찾아서 활용해보면 좋을 것 같다. 인코딩(문자셋) 요소는 필수가 아니지만 파일의 인코딩에 따라 한글이 깨지는 일을 겪을 수 있다. 아스키 코드, 유니코드의 개념을 알면 좋은데 이와 관련한 좋은 게시글이 많기 때문에, 찾아보길 권장한다. 보통 우리가 사용하는 인코딩은 'utf-8'이다.


※ TIP: 경로는 상대경로절대경로가 있다. 상대경로는 현재 내가 위치한 디렉토리로부터 이용할 파일을 찾아간다. 절대경로는 루트 디렉토리로부터 이용할 파일을 찾아간다. 상대경로에서 현재 내가 위치한 디렉토리는 '.\'이다. 바로 상위 디렉토리는 '..\'이며 계속 올라갈 수 있다. 절대경로는 윈도우를 예시로 들면, 'C:/User/USER/····' 이런 식으로 구성된다. 경로가 잘못되어 파일을 열지 못하는 경우가 생각보다 많이 발생되니 충분한 연습이 필요하다.


파일을 열었으면 이제 읽거나 써야 한다.

line = file.readline()
lines = file.readlines()
print(line, end="")
결과 출력 ==> '안녕하세요.'
print(lines)
결과 출력 ==> ['안녕하세요.\n', '파일을 읽고 있습니다.\n']

우선 파일을 읽을 때 사용하는 함수는 크게 readline()readlines()를 활용한다. 위의 예시에서 눈치챌 수 있겠지만 readline()은 텍스트 파일을 한줄씩 읽어낸다. readlines()는 모든 내용을 한줄씩 리스트에 넣어준다. 여기서 중요한 점은 파일을 열 때 open() 함수에 newline 속성을 지정할 수 있는데, 따로 지정하지 않으면 기본적으로 '\n' 줄바꿈이 기본값이다. 그래서 lines 변수를 출력했을 때 각각 '\n' 줄바꿈 이스케이프 문자가 들어가 있는 걸 볼 수 있다.


※ TIP: readline(), readlines() 함수 모두 파일을 어떻게 한 줄씩 불러올 수 있는지는 open() 함수의 seek(), tell() 등의 함수와 관련된 커서 개념을 이해하면 좋다. 정리된 좋은 글들이 많이 있으니 참고바란다.


text1 = '안녕하세요.' + "\n"
text2 = '이번엔 파일을 작성해볼거에요.' + "\n"
file.write(text1)
file.write(text2)

text_list = ['안녕하세요.\n', '이번엔 파일을 작성해볼거에요.\n', '우와, 리스트로 작성하네요.\n']
file.writelines(text_list)

파일을 쓸 때 write() 함수와 writelines() 함수를 이용한다. 특이한 점으로 text1, text2 변수 모두 줄바꿈 이스케이프 문자를 추가해줬는데 파일을 쓸 때는 이스케이프 문자로 줄바꿈을 명확히 해줘야 줄바꿈되어 작성된다. write() 함수는 문자열을 작성하지만, writelines()는 위에서 본 readlines()와 마찬가지로 문자열 리스트를 한 번에 작성해줄 수 있다. 상황에 따라 둘 다 활용될 수 있으니 둘 다 잘 알아두는 게 좋다.


※ TIP: 파일을 열 때 이미 존재하지도 않는데 어떻게 여는지 의아할 수 있다. open() 함수에서 파일을 쓸 때 써주는 경로와 파일이름은 내가 파일을 작성해서 저장하고 싶은 위치와 파일명을 기입한다. 만약 그 파일 위치에 이미 같은 파일명이 존재한다면 덮어쓰니 주의가 필요하다!🥲


이제 파일을 읽거나 썼으니 열었던 파일을 다시 닫아줘야 한다. 닫아주지 않으면 open() 함수로 return 받은 파일 핸들러가 메모리에 할당되어 계속 상주하고 있어 메모리 누수를 유발할 수 있다. 속도 저하의 원인이 되며 파일을 닫을 때는 'file.close()'를 해주면 된다.

with open('경로와 파일명', '읽기, 쓰기 모드', '인코딩') as f :
    f.readline(), f.readlines()
    f.write(), f.writelines()

파일을 열거나 닫아주는 건 생각보다 귀찮은 작업일 수 있다. 그리고 코드를 길게 작성하다 보면 닫아주는 걸 잊어먹을 때도 있다. 그래서 이를 간편하게 한 번에 처리해주는 키워드가 있다. 바로 'with'인데 위의 예시처럼 open() 함수 앞에 'with' 키워드를 붙여주고 함수 처럼 ':' 콜론을 붙여준 뒤 그 안에 어떤 동작을 할지 작성해주면 된다. 특이점으로 'as f'가 보일텐데 open() 함수를 따로 썼을 때 file 변수에 파일 핸들러를 return받았던 것처럼 'as'를 뒤에 붙인 뒤 사용할 이름을 작성해주면 그 이름으로 똑같이 읽기, 쓰기 관련 함수를 사용할 수 있다!


미션: 보낼 메시지를 입력받고 파일을 암호화해서 작성한 뒤 작성된 파일로 해독된 파일도 만들어본다.

이 미션은 파일 읽기와 쓰기 둘 다 익히기에 유익한 미션이라고 생각한다. 이번엔 예시 코드를 바로 첨부한다. 천천히 연습해보면 좋을 것 같다.

#암호화 파일 생성
#메시지 입력
def inputText() :
	lines = []
	while True :
		text = input("메시지 입력 : ")
		if text == "" :
			break
		else :
			lines.append(text)

	return lines

#암호화 파일 생성
def fileCode() :
	lines = inputText()
	with open("C:\\Users\\user\\OneDrive\\바탕 화면\\code.txt", "w", encoding="utf-8") as f :
		for txt in lines :
			for tt in txt :
				ascii = ord(tt)
				
				if tt == ' ' :
					f.writelines(chr(ascii))
				else :
					ascii -= 30000
					f.writelines(chr(ascii))
			f.writelines("\n")

fileCode()

#해독한 파일 생성
#파일 불러오기
def callFile() :
	texts = []

	with open("C:\\Users\\user\\OneDrive\\바탕 화면\\code.txt", "r", encoding="utf-8") as f :
		while True :
			line = f.readline()
			if line == '' :
				break
			else :
				line = line.split('\n')[0]
				texts.append(line)

	return texts

#복호화
def descrambling() :
	lines = callFile()

	with open("C:\\Users\\user\\OneDrive\\바탕 화면\\des.txt", "w", encoding="utf-8") as f :
		for txt in lines :
			for tt in txt :
				ascii = ord(tt)
				
				if tt == ' ' :
					f.writelines(" ")
				else :
					ascii += 30000
					f.writelines(chr(ascii))
			f.writelines("\n")

descrambling()

※ TIP: 경로에서 '\\'가 두 번 들어간 이유는 이스케이프 문자로 인식해서 제대로 된 경로지정이 안되기 때문에 한 번 더 써줘서 이스케이프 문자가 아닌 경로에 사용할 문자라는 걸 표현해준 것이다.


이번 수업에서 어려웠던 미션도 같이 공유한다. 나중에 공부할 네트워크, 리눅스에서 사용하게 될 서버 설정 파일을 자동화하는 것인데 아직 그 개념이 명확하지 않아 규칙을 제대로 찾지 못하고 로직을 구성했다. 한 번 해보면 좋을 것 같아 올린다.


아래의 두 설정 파일이 있다.

#다음은 setup.cfg 파일의 설정임

#WEB config
LISTEN : 8001;
SERVER_NAME : www.myserver.com;
proxy_pass : http://1.2.3.4:8080;

#WAS config
DB...

#server.cfg설정 파일
server {
	listen	80;
	server_name	localhost;
	
	location / {
		root /usr/share/nginx/html;
		index index.html index.html;
	}
}

미션: setup.cfg 파일을 불러와 listen, server_name, proxy_pass 등의 속성과 그 설정값을 저장한다. 이어서 server.cfg 파일을 불러와 각각 일치하는 속성에 맞게 값을 바꿔준다. 예를 들어, listen은 값이 80에서 8001로 바뀐다. 만약 setup.cfg 파일에 작성된 속성이 없으면 'lcoation / {}' 설정 내용 안에 넣어준다.

#config파일경로
def configPath() :
	path = input("경로 : ")
	return path

#config 파일 불러오기
def config(path) :
	path = path + "\\setup.cfg"
	cfg = {}
	with open(path, "r", encoding="utf-8") as f :
		while True :
			#데이터 가공 key, value로 구분(ex. 'LISTEN_PORT : 8001;\n'에서 마지막 ';\n'빼고)
			#":"기준으로 split 후 'LISTEN_PORT'는 key, '8001'은 value
			line = f.readline()

			if line.count(":") > 0 :
				index = line.find("\n")
				line = line[:index]
				idx = line.find(":")
				key = line[:idx].lower().strip()
				value = line[idx+1:].lower().strip()

				cfg[key] = value

			if line == "" :
				break
	print("불러오기 성공")
	return cfg

#server.cfg 파일 불러오기
def server(path, cfg) :
	path = path + "\\server.cfg"
	keyList = list(cfg.keys())

	with open(path, "r+", encoding="utf-8") as f :
		lines = f.readlines()

		for i in range(len(lines)) :
			for key in keyList :
				if lines[i].lower().find(key.lower()) >= 0 :
					line = lines[i].split("\t")
					line[-1] = cfg[key] + "\n"
					chgLine = "\t".join(line)
					lines[i] = chgLine
					keyList.remove(key)
				elif lines[i].lower().find(key.lower()) < 0 :
					line = lines[i].split("\t")

					if 'location / {\n' in line :
						newLine = ['\t', key, cfg[key]+"\n"]
						chgLine = "\t".join(newLine)
						lines.insert(i+1, chgLine)

		#파일의 맨 처음으로 커서이동
		f.seek(0, 0)
		#파일의 전체 내용 새로 쓰기
		f.writelines(lines)
		
	print("변환 성공")

#main
def main() :
	path = configPath()
	cfg = config(path)
	server(path, cfg)

main()

좀 더 공부가 필요하다고 생각되는 예시 코드이다. 여러분은 더 좋은 코드를 작성하면 좋겠다.



Ⅲ. 파이썬 클래스

클래스에 대한 글을 작성하기 전에 객체 지향 프로그래밍은 여러 권의 책으로 설명해야 할 정도로 굉장히 중요하고 어렵고 분량이 많다. 이번 게시글로 모두 다룰 수 없다. 그래서 요약집, 개론서로 생각하고 읽어주면 좋겠다.


우선, 객체(Object)는 수많은 사물을 프로그래밍 관점에서 부르는 용어이다. 사물은 말 그대로 사람이 될 수도 있고, 자동차, 동물, 스마트폰, 태블릿 등등 어떤 속성행위를 가지고 있다. 사람에게 손과 발이 있고 걷고, 먹고, 자는 등의 행위가 있는 것처럼, 자동차에게 바퀴, 엔진, 배터리가 있고 앞으로 가고, 뒤로 가고, 음악 재생이 되는 등의 행위가 있는 것처럼, 여기서 사람과 자동차는 객체다. 손과 발, 바퀴, 엔진, 배터리는 속성이 되고 걷고, 먹고, 자고, 앞으로 가고, 뒤로 가고, 음악 재생은 행위가 된다.


파이썬에서는 이미 만들어진 객체를 사용할 수 있게 한다. 파이썬의 문자열은 하나의 객체로 글자라는 속성에 find(), lower(), upper() 등의 행위를 가지고 있으며, 우리가 자주 사용하게 되는 len() 함수도 객체의 메소드이다.


※ TIP: 메소드는 간단하게 함수이자 행위라고 생각하면 이해가 조금 더 쉬울 수 있다.


클래스는 일종의 설계도인데 내가 만들고자 하는 객체의 속성은 멤버변수로 행위는 메소드로 정의하며 프로그램에 활용할 수 있다.

class 클래스명 :
    #멤버 변수
    def 메소드명 :
    def __init__(self) :
        #생성자

이런 식으로 선언하고 정의한다.

class car :
    car_name = '자동차'

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

    def go(self) :
        print(self.car_name + '가 앞으로 간다.')

구체적인 예시로 자동차 클래스가 있다고 가정할 때 자동차 이름은 속성이자 멤버변수이다. 메소드로 go() 함수가 있는데 자동차가 앞으로 가는 행위를 의미한다.


※ TIP: __init__() 메소드는 생성자인데 객체가 생성되자마자 무조건 호출된다. 생성자로 클래스가 어떤 객체인지 명확히 해줄 수 있는데 멤버변수의 값을 매개변수로 받아 초기화를 통해 가능하다. 생성자를 알기 위해 '라이프 사이클'을 이해하면 좋다. 관련된 좋은 자료가 많으니 꼭 반드시 찾아보길 권장한다.


genesis = car('제네시스')
genesis.go()
출력 결과 ==> '제네시스가 앞으로 간다.'

클래스를 만들어 주고 사용할 때 기본적으로 이런 구조를 가진다. 클래스에 맞춰 변수명을 선언해주고 클래스가 매개변수가 있는 생성자를 가지면 매개변수를 넣어준다. 없다면 생략해도 좋다. 이제 그 변수명으로 클래스의 메소드를 호출해 프로그램 로직을 구성할 수 있다.


__init__() 메소드와 반대로 __del__() 메소드가 있는데 클래스의 쓰임새가 다했을 경우 소멸해주는 메소드이다. 클래스가 선언되고 정의되어 활용하게 되면 메모리 공간에 할당되는데 프로그램을 만들다보면 수많은 클래스가 활용된다. 메모리 공간을 효율적으로 관리하기 위해 Java같은 경우 Garbage Collection(가비지 컬렉션)이라고 클래스를 알아서 소멸시켜주는 방식이 있는데 파이썬에서도 있다. 그래서 별도로 __del__() 메소드를 작성해줄 필요없다.


특이한 메소드로 __add__() 메소드가 있다. 예전에 리스트도 덧셈과 곱셈이 가능했는데 객체도 덧셈이 가능하게 해주는 메소드이다.

class car :
    car_name = ''
    def __add__(self, other) :
        print("객체", self.car_name, "과", other.car_name, "가 친구가 되었습니다.")

genesis = car()
genesis.car_name = "제네시스"
k3 = car()
k3.car_name = "K3"

genesis + k3
출력 결과 ==> 객체 제네시스 와 K3 가 친구가 되었습니다.

위의 예시처럼 두 객체(클래스)를 더해줘서 같은 종류의 객체를 같이 활용할 수 있게 된다. '제네시스'와 'K3'를 별도로 정의해준 것처럼, 생성자 외에 클래스의 멤버변수에 직접 접근할 수 있는데 권장하지 않는다. 멤버변수에 직접 접근하면 클래스를 활용할 때 코드가 길어지게 되고 유지보수가 어렵게 된다. 객체를 사용하는 중요한 이유 중 하나가 데이터, 내부 구조(구현) 보호를 위한 '캡슐화'인데 이에 위배되어 객체 내부의 로직을 외부에 노출시킬 수 있다.


사실 이어서 클래스 상속, 객체의 주요 4대 특성(상속, 추상화, 다형성, 캡슐화) 등 이해해야될 개념이 방대하다. 모두 다룰 수는 없고 오랜 시간이 걸릴 수도 있으므로 지속적으로 공부해나가면서 관련 좋은 글을 찾아보고 공부하길 권장한다.



후기

점점 어려워지는 파이썬을 공부했습니다. 특히 지난 차시의 리스트나 튜플, 딕셔너리, 함수도 모두 필수적으로 사용되고 거기까지 알아도 무언가를 만들 수 있지만, 프로그래밍을 고급지게?🫠 만들어 주는 게 클래스입니다. 객체 지향 프로그래밍 개념은 정말 이해가 되지 않아 많이 찾아봤던 기억이 있습니다. 지금도 온전히 이해했다고 할 순 없겠지만, 예전보다 활용할 수 있는 폭이 넓어지는 것 같아 뿌듯한 것 같습니다. 앞으로 네트워크와 리눅스를 공부하게 될텐데 기대가 되며 더 열심히 해야할 것 같습니다.😄