파일 작업 하기

디렉토리, 파일들을 검색, 변경, 읽기, 쓰기하는 것에 대해서 알아봅니다.

파일이란 문서, 음악, 영상 데이터와 같은 다양한 종류의 데이터를 저장 장치(하드 디스크, 외장 하드, USB 저장 장치 등)에 저장하기 위해 사용되는 컴퓨터 자원입니다.

디렉토리(또는 폴더라고도 불립니다.)란 파일들 또는 또 다른 디렉토리들의 모임을 일컫는 이름입니다.

경로(path)란 파일 또는 디렉토리를 나타내는 유일한 위치를 표시하기 위한 이름입니다. 경로는 디렉토리와 디렉토리 또는 파일들을 경로 구분자로 연결하여 표현 합니다. 경로 구분자는 윈도우즈는 역슬래시 \를 사용하고 유닉스 계열은 슬래시 /를 사용합니다. 윈도우즈의 예를 들면 다음과 같습니다.

'c:\work\subdir\file.txt'

윈도우즈 경로는 맨 앞에 드라이브 이름과 콜론을 포함합니다.

유닉스의 경우는 드라이브 이름이 없으며 최상위 디렉토리는 /로 시작합니다. 유닉스 경로 예는 다음과 같습니다.

/usr/lib/local

파일은 myfile.txt와 같이 표시하고 myfile을 파일이름(filename), txt확장자(extension)라고 부릅니다. 확장자가 없는 경우는 파일과 파일이름이 같습니다. 참고로 파일과 파일 이름에 대한 정의는 저서마다 다를 수 있습니다.

경로를 다룰 때 os, os.path, glob 모듈들은 문자열과 pathlib로부터 만든 객체를 사용할 수 있고, pathlib 모듈은 경로 객체를 사용합니다.

pathlib 모듈

pathlib 모듈은 객체 형식의 경로를 사용하여 운영체제와 독립적으로 파일을 다룰 수 있게 해줍니다. pathlib.Path('path')를 이용하여 객체를 만들어 사용합니다. os.path 함수들의 반환값은 문자열이지만 pathlib 메소드들의 반환값은 대부분 PurePath로부터 상속받은 객체입니다. 다음과 같이 c:\work에 대한 경로를 Path 클래스를 이용해서 만들 수 있습니다.

In [1]: import pathlib
   ...: 
   ...: my_path = pathlib.Path('c:\\work')
   ...: my_path
   ...: 
Out[1]: WindowsPath('c:/work')

my_path는 결과에서 보는 바와 같이 WindowsPath 객체인 것을 알 수 있습니다. WindowsPathPurePath의 하위 클래스입니다. 또한 경로 구분자로 슬래시 /를 사용할 수 있습니다. 윈도우즈 운영체제는 경로 구분자가 백슬래시이지만 슬래시도 혼용해서 사용할 수 있습니다. 하지만 혼용해서 사용하는 것은 오류의 가능성을 높여 주기 때문에 사용하지 않는 것이 좋습니다. pathlib는 경로가 객체이기 때문에 이러한 오류는 발생하지 않습니다.

경로 만들기

경로는 단순한 문자열 그 자체(pathlib의 경우는 문자열로부터 만들어진 경로 객체)이기때문에 주어진 경로가 실제로 존재하는지는 필요할 때 반드시 확인을 해야 합니다. 그렇지 않으면 오류가 발생합니다.

os.path 모듈 이용한 경로

os.path 모듈은 경로를 나타내는 문자열 또는 pathlib의 경로 객체를 사용합니다. 정확히는 유사 경로 객체(path-like object)입니다. 예를 들어 경로 c:\work\subdir\file.txt의 디렉토리를 반환하는 메소드 os.path.dirname(path)에서 인자 path는 다음과 같이 문자열이 들어갈 수 있습니다.

import os

path = "c:\\work\\subdir\\file.txt"
os.path.dirname(path)
'c:\work\subdir'

또는 다음과 같이 pathlib의 객체가 들어갈 수 있습니다.

import os
import pathlib

path = "c:\\work\\subdir\\file.txt"
path_obj = pathlib.Path(path)

os.path.dirname(path_obj) # pathlib 객체 대입
'c:\work\subdir'

pathlib 모듈 이용한 경로

기존의 경로 객체에 추가하고자 하는 경로에 대한 문자열 또는 추가하려는 경로 객체를 합쳐서 새로운 경로를 만들 수 있습니다. joinpath(다른경로) 메소드를 이용해서 만들 수 있습니다. c:\work 폴더와 files 경로를 더해서 c:\work\files 경로를 만들어봅니다. 먼저 문자열 files와 기존 경로 객체를 합쳐서 새로운 경로를 만듭니다.

import pathlib

my_path = pathlib.Path('c:/work') # 경로 구분자로 슬래시를 이용해도 됩니다.

path_str = 'files' # 문자열

new_path = my_path.joinpath(path_str)
new_path
WindowsPath('c:/work/files')

다음은 다른 경로 객체(other_path)와 결합하여 새로운 경로 객체를 만든는 예입니다.

other_path = pathlib.Path('files') # 다른 경로 객체

new_path = my_path.joinpath(other_path)
new_path
WindowsPath('c:/work/files')

경로 존재 판단

경로는 어떠한 문자열도 될 수 있으므로 주어진 경로가 존재하는지를 판단해야 합니다.

os.path 모듈 이용한 경로 존재

주어진 경로가 존재하는 지를 판단하는 함수로 os.path.exists(경로)를 사용할 수 있습니다. c:\work 디렉토리가 존재하는지를 알아보기 위해서는 다음과 같이 합니다.

import os

os.path.exists('c:\\work')
True

경로 c:\work\file.txt은 존재하지 않습니다. 즉, c:\work 디렉토리는 존재하지만 그 안에 file.txt는 존재하지 않습니다. 따라서 다음과 같이 exists메소드의 결과가 거짓이 나오는 것을 알 수 있습니다.

import os

os.path.exists('c:\\work\\file.txt')
False

pathlib 모듈 이용한 경로 존재

pathlib 모듈을 이용하려면 pathlib 객체의 exists() 메소드를 사용하면 됩니다.

import pathlib

my_path = pathlib.Path('c:\\work')
my_path.exists()
True

위와 같이 exists() 매소드를 사용하려면 경로(Path) 객체를 먼저 만들어야 합니다.

디렉토리 만들기

다음과 같은 함수들을 이용합니다.

디렉토리 만드는 함수

함수

설명

os.mkdir()

하나의 하위 디렉토리를 만듭니다.

pathlib.Path.mkdir()

하나 또는 여러 개의 하위 디렉토리를 만듭니다.

os.makedirs()

중간 디렉토리를 포함한 여러 개의 하위 디렉토리를 만듭니다.

하나의 디렉토리 만들기

os.mkdir() 함수의 매개변수에 경로를 넘겨줍니다.

In [2]: import os
   ...: 
   ...: os.mkdir('mydir/')
   ...: 

pathlib를 이용하여 만들 수 있습니다.

In [3]: from pathlib import Path
   ...: 
   ...: p = Path('mydir/')
   ...: p.mkdir()
   ...: 

만일 디렉토리가 이미 존재하면 FileExistsError를 발생시킵니다. 에러를 처리하기 위해서는 try…except를 이용합니다.

파일/디렉토리 판단

앞에서 주어진 경로가 실제로 존재하는지를 판단했습니다. 다음으로 존재하는 경로가 디렉토리 경로인지 파일 경로인지를 판단합니다.

os.path 모듈 이용한 파일

주어진 경로가 실제로 존재하는 파일인지 디렉토리인지를 판단하는 방법은 os.path.isfile(경로) 또는 os.path.isdir(경로)를 사용합니다.

import os

os.path.isfile('c:\\work')
False
import os

os.path.isdir('c:\\work')
True

pathlib 모듈 이용한 파일

pathlib 모듈를 사용합니다면 Path.is_file() 또는 Path.is_dir() 메소드를 사용하면 됩니다.

import pathlib
my_path = pathlib.Path('c:/work')
my_path.is_dir()
True
import pathlib

my_path = pathlib.Path('c:/work')
my_path.is_file()
False

경로 순회

다양한 방법으로 주어진 경로 안에 있는 파일 및 디렉토리들을 순회할 수 있습니다. 경로는 모듈 os.path 메소들에서는 문자열을 이용하고 pathlib 모듈은 Path 객체를 이용합니다.

os.walk 이용

os.walk(top_dir) 함수를 이용하여 디렉토리 경로 top_dir를 포함한 모든 하위 디렉토리 및 파일들을 나열할 수 있습니다. os.walk()는 3개의 원소를 갖는 튜플 (dirpath, dirnames, filenames)을 반환합니다. dirpathtop_dir를 포함한 모든 하위 디렉토리들의 이름 문자열입니다. dirpathtop_dir 디렉토리부터 시작해서 하위 디렉토리로 차례로 내려가면서 순회합니다. os.walk()는 생성자(generator)라서 각각의 디렉토리(dirpath) 안의 하위 폴더와 파일 리스트를 반환합니다. dirnames는 주어진 디렉토리 안에 있는 하위 디렉토리 이름들의 리스트입니다. filenames는 주어진 디렉토리 안에 있는 하위 디렉토리가 아닌 파일 이름들입니다. c:\work 폴더와 모든 하위 폴더에 있는 파일 이름을 나열하기 위해서는 다음과 같이 합니다.

import os

top_dir = 'c:\\work'

for dirpath, dirnames, filenames in os.walk(top_dir):
    for filename in filenames:
        print(os.path.join(dirpath, filename))

여기서 주의해야 할 것은 윈도우즈에서 경로를 구분하는 문자 백슬래시 \를 이용할 때는 반드시 백슬래시 2번 \\을 사용하는 것이 안전합니다. 왜냐면 문자열에서 백슬래시는 탈출문자로서 특수한 역할을 감당하기 때문입니다. 예를 들면 \xab에서 \x 뒤의 두 자리문자는 16진수에 해당하는 문자이어야 하고 \xab는 그 숫자에 해당하는 문자를 의미합니다. 16진수 ab는 정수 171이고 이 숫자에 해당하는 문자는 <<가 되어 아래와 같이 출력하게 됩니다.

In [4]: print('c:\xab')
c:«

그러므로 c:\xab 문자 그대로 출력하려면 다음과 같이 탈출문자를 붙여야합니다.

In [5]: print('c:\\xab')
c:\xab

glob.glob() 이용

모듈 glob의 메소드 glob(path)을 이용해서 원하는 파일과 디렉토리들을 찾을 수 있습니다. glob 메소드 인자 path에 특수 문자 *, ?, [] 들을 사용하여 좀 더 자유롭게 파일들을 찾을 수 있습니다. *는 모든 문자를 의미하고 ?는 임의의 문자 하나를 의미합니다. 다음은 현재 작업 디렉토리의 파일 및 디렉토리들의 문자열 리스트를 반환합니다.

import glob

glob.glob('*')

다음은 c:\work 디렉토리 밑의 확장자가 txt인 파일들을 모두 찾아 문자열 리스트 형식으로 반환합니다.

glob.glob('c:\\work\*.txt')

**recursive=True를 이용하면 주어진 경로와 경로 아래의 모든 하위 디렉토리에 대해서 검색을 하게 됩니다. 다음은 c:\work 와 모든 하위 디렉토리에서 확장자가 py인 파일들의 문자열 리스트를 반환합니다.

glob.glob('c:\\work\\**\\*.py')

파일 이름/확장자 분리

주어진 경로가 존재하는 파일이라면 파일과 경로로 분리하고 파일이름과 확장자로 분리할 수 있습니다. 파일과 디렉토리 경로로 분리하는 함수는 os.path.split(경로)를 사용합니다. 다음은 c:\work\subdir\file.txt라는 파일을 파일 file.txt와 나머지 디렉토리 c:\work\subdir로 분리합니다. 여기서 주의해야 할 것은 os.path.split(경로) 메소드는 주어진 경로가 실재로 있는지 없는지를 상관하지 않고 작동합니다.

import os

os.path.split('c:\\work\\subdir\\file.txt')
('c:\work\subdir', 'file.txt')

위 결과에서 보는 바와 같이 os.path.split 메소드는 경로의 맨 끝(file.txt)과 그 나머지(c:\\work\\subdir)로 구성된 문자열 튜플을 반환합니다. 경로의 맨 끝이란 경로 구분자 백슬래시 다음 항목을 의미합니다. 다음 경로 c:\work\는 마지막 백슬래시 뒤에 아무것도 없으므로 다음과 같이 두번째 항목이 빈 문자인 것을 알 수 있습니다.

import os

os.path.split('c:\\work\\')
('c:\work', '')

Caution

따라서 c:\work 경로를 분리하면 아래와 같이 첫번째 항목은 c:\이고 두번째 항목은 work가 되는 것을 알 수 있습니다. 그러므로 split 메소드를 사용할 때 이러한 점을 유의해야 합니다.

In [6]: import os
   ...: os.path.split('c:\\work')
   ...: 
Out[6]: ('c:\\', 'work')

그러므로 다음 예제와 같이 파일이 존재하는지를 먼저 확인하고 파일과 디렉토리로 구분하는 것이 좋을 것입니다.

import os

path = 'c:\\work\\files\\123.txt'

if os.path.isfile(path):
    print("디렉토리 = '{}, 파일 = '{}'".format(*os.path.split(path)))
elif os.path.isdir(path):
    print('디렉토리 = ', path)
else:
    print('경로가 존재하지 않습니다.')
디렉토리 = 'c:workfiles, 파일 = '123.txt'

파일을 파일 이름과 확장자로 분리하는 것은 os.path.splitext(파일)을 사용합니다.

import os

os.path.splitext('123.txt')
('123', '.txt')

위 결과에서 주의해야 할 것은 튜플의 두번째 항목에 (.)이 포함된다는 것입니다. os.path.splitext() 메소드는 인자로 파일이 아니라 경로가 들어갈 수 있습니다. 이 때는 결과로 반환되는 두번째 항목은 점과 뒤의 확장자가 되고 첫번째 항목은 그 나머지가 됩니다. 즉 첫번째 항목에 디렉토리와 파일 이름이 포함됩니다.

import os

os.path.splitext('c:\\work\\1234.txt')
('c:\work\1234', '.txt')

파일 입출력

파일 입출력을 위해 open 함수를 이용해 파일 객체를 생성한 후 readline, readlines, read, write 메소드들을 이용하면 됩니다. 파일 객체를 만들 때 읽는 모드와 쓰는 모드로 지정해 줄 수 있습니다. 파일을 읽거나 쓰는 일을 모두 마친 후에는, close 메소드를 호출하여 파일 자원에 대한 사용을 종료해야 합니다.

파일에 쓰기

 = open('새로운파일.txt', 'w')
.close()

파일을 쓰기 모드('w')로 열게 되면 해당 파일이 이미 존재할 경우 기존 파일 내용이 모두 사라지고, 해당 파일이 존재하지 않으면 새로운 파일이 생성됩니다. 위의 예에서는 디렉토리에 파일이 없는 상태에서 새로운파일.txt를 쓰기 모드인 'w'로 열었기 때문에 새로운파일.txt라는 이름의 새로운 파일이 현재 디렉토리에 생성되는 것입니다.

만약 새로운파일.txt라는 파일을 C:\work라는 디렉토리에 만들고 싶다면 다음과 같이 작성해야 합니다.

 = open("C:\\work\\새로운파일.txt", 'w')
.close()

파.close()는 열려 있는 파일 객체()를 닫아 주는 역할을 합니다. 프로그램을 종료할 때 파이썬이 열려 있는 파일의 객체를 모두 닫아주기 때문에 close() 문장을 생략할 수 있지만, 프로그램 종료 전에 열려있는 파일을 다른 어딘가에서 사용하려고 하면 오류가 발생합니다. 그러므로 반드시 열었던 파일은 닫아주어야 합니다.

경로를 이용해서 파일에 쓰는 방법에 대해서 알아봅니다. os.makedirs(경로, exist_ok=False) 함수를 이용해서 주어진 경로의 디렉토리가 없으면 재귀적으로 만듭니다.

my_dirpath = 'c:\\work\\files\\subdir1\\subsubdir1'

os.makedirs(my_dirpath)
  • 새로운 파일 만들기

새로운 파일의 경로를 설정합니다.

file_path = 'c:\\work\\files\\subdir1\\subsubdir2\\test.txt'

os.path.dirname(경로)를 이용해서 디렉토리만 추출한 후, 디렉토리가 없으면 만듭니다.

os.makedirs(os.path.dirname(file_path), exist_ok=True)

내장 함수 open()과 쓰기 모드 w를 이용해 파일을 생성합니다. 그리고 생성된 파일 객체 fwrite() 메소드를 이용해 파일에 씁니다.

with open(file_path, 'w') as f:
    f.write('lalala')

파일 객체의 write 메소드를 이용해 파일에 쓸 수 있습니다.

 = open("새로운파일.txt", 'w')
.write("안녕하세요.")
.close()

 = open("새로운파일.txt", 'r')
 = .read()
print()
.close()
안녕하세요.

기존 파일에 추가하기

파일을 열 때 모드를 'a'로 하면 추가모드가 되어 기존 파일 내용의 맨 끝에 새로운 내용을 추가합니다. 아래 코드를 실행해보고 새로운파일.txt 파일을 열어 확인해 봅니다.

 = open("새로운파일.txt", 'a')
.write("새로운 내용을 추가했습니다.")
.close()

다음은 이육사 시인의 청포도입니다. 청포도.txt 파일에 저장해 봅니다.

청포도 = """내 고장 칠월은
청포도가 익어 가는 시절.

이 마을 전설이 주저리주저리 열리고
먼 데 하늘이 꿈꾸며 알알이 들어와 박혀,

하늘 밑 푸른 바다가 가슴을 열고
흰 돛 단 배가 곱게 밀려서 오면,

내가 바라는 손님은 고달픈 몸으로
청포(靑袍)를 입고 찾아온다고 했으니,

내 그를 맞아 이 포도를 따 먹으면
두 손은 함뿍 적셔도 좋으련,

아이야, 우리 식탁엔 은쟁반에
하이얀 모시 수건을 마련해 두렴."""

# 쓰기위해 파일 열기
 = open('청포도.txt', 'w', encoding='utf-8')
# 파일에 쓰기
.write(청포도)
# 파일 닫기
.close()

파일 읽기

내장 함수 open(파일경로, 모드)파일경로라는 파일을 모드라는 형태로 만들어 파일 객체를 반환합니다.

파일 모드

모드

설명

r

읽기 전용 모드

w

쓰기 전용 모드. 새로운 파일을 만듭니다.(같은 이름의 파일이 있으면 지운다)

a

기존 파일에 추가합니다.(파일이 없으면 새로 만듭니다.)

r+

읽기와 쓰기 둘 다 가능합니다.

b

바이너리 파일 모드로 읽고 씁니다. rb 또는 wb

t

텍스트 형식으로 읽고 씁니다. rt 또는 wt

파일 객체의 readline() 메소드를 이용하여 파일 내용을 읽는다. 앞에서 저장한 청포도.txt 파일을 읽어봅니다.

#  모드가 설정되어 있지 않으면 기본은 읽기('r')모드
 = open('청포도.txt', encoding='utf-8')
 = .readline()
while :
    print(, end='')
     = .readline()
.close()
 고장 칠월은
청포도가 익어 가는 시절.

 마을 전설이 주저리주저리 열리고
  하늘이 꿈꾸며 알알이 들어와 박혀,

하늘  푸른 바다가 가슴을 열고
   
... 생략 ...

직접하기

  • 청포도.txt 파일에 제목 “청포도”와 저자 “이육사”를 함께 저장하는 프로그램을 작성하시오.

파일 객체 입출력 메소드들은 다음과 같습니다.

파일 입출력 메소드

메소드

설명

read([크기])

파일을 열 때 모드에 따라 크기가 주어지면 크기 만큼의 바이트 또는 글자수를 읽어온다. 크기가 없으면 파일 전체를 읽어 온다.

readlines([크기])

크기 줄 만큼을 읽어온다. 그렇지 않으면 파일 전부를 줄 단위로 읽어 온다.

write(문자열)

문자열을 파일에 씁니다.

writelines(문자열리스트)

줄 단위의 문자열 리스트를 파일에 씁니다.

close()

파일 자원을 닫는다.

flush()

버퍼에 있는 것을 디스크에 씁니다.

seek(위치)

주어진 위치로 스트림을 이동합니다.

closed

파일 자원이 닫혔으면 참(True) 반환

with

파일 입출력을 위해서는 openclose를 반복해야 합니다.

 = open("file.txt", 'w')
.write("파일을 open() 하면 반드시 close()를 해야 합니다.")
.close()

이러한 과정을 자동으로 처리할 수 있게 한 것이 with문입니다.

with문맥 관리자(context manager)와 관련되어 더 많은 작업을 할 수 있습니다.

with open("청포도.txt", encoding='utf-8') as :
    for  in :
        print(, end='')
 고장 칠월은
청포도가 익어 가는 시절.

 마을 전설이 주저리주저리 열리고
  하늘이 꿈꾸며 알알이 들어와 박혀,

하늘  푸른 바다가 가슴을 열고
   
... 생략 ...

압축 파일

zipfile 모듈을 이용해서 압축 파일을 다룬다.

파일 이름이 한글일 때 깨져서 나올 때가 있습니다. 이럴 때는 다음과 같이 인코딩 처리를 해줍니다.

In [7]: import os
   ...: import zipfile
   ...: zipref = zipfile.ZipFile('./data/관서별_5대범죄_발생_및_검거_현황_2000_2016_.zip')
   ...: for fname in zipref.namelist():
   ...:   zipref.extract(fname)
   ...:   os.rename(fname, './data/' + fname.encode('cp437').decode('cp949'))
   ...: