본문 바로가기

파이썬

파일 읽고 쓰기

1. 파일 

■ 파일(file)은 보조 기억장치에서 문서, 소리, 그림, 동영상같은 자료를 모아놓은 것을 말한다.

■ 파일 안에는 바이트들이 순차적으로 저장되어 있고, 맨 끝에는 EOF(end-of-file) 마커가 있다. 

■ 모든 파일은 위의 예시처럼 입출력 동작이 발생하는 위치를 나타내는 파일 포인터를 가지고 있다. 

■ 파일을 열면, 파일 포인터는 파일의 첫 번째 바이트를 가리킨다. 그리고 파일의 내용을 읽거나 쓰면, 파일 포인터는 자동으로 업데이트된다. 



2. 파일 열고 닫기


2.1 open()과 close()

■ 파일을 사용하려면 먼저 open( ) 함수로 파일을 열어야 한다.

■ open( ) 함수는 파일 이름과 파일 모드를 받아서 파일 객체를 생성한 후에 파일 객체를 반환한다.

■ 파일이 열리면 해당 파일에 데이터를 읽거나 쓸 수 있다. 

■ 파일과 관련된 작업이 모두 끝나면 파일을 닫아야 한다. 이는 파일 객체가 가지고 있는 close()를 호출하면 된다. 어떤 경우라도 파일 연결은 파일객체.close() 명령으로 종료할 수 있다.

■ 파일을 열고 닫기 위해 사용하는 open()과 close()의 사용법은 다음과 같다.

파일객체 = open(파일명, 파일 모드)
...,
...,
파일객체.close()

 

f = open('input.txt', 'r')
...,
...,
f.close()

■ open() 함수의 첫 번째 파라미터는 파일의 이름이다. 위의 예시에서 open() 함수는 파일명이 'input'인 텍스트 파일을 열고, 파일과 연관된 객체를 생성한 다음, 파일 객체를 반환한다.

■ 파일에서 데이터를 읽거나 쓰려면 반드시 파일 객체가 필요하다. 만약, open() 함수가 파일을 여는데 실패하면 None 객체가 반환된다.

■ open() 함수의 두 번째 파라미터는 파일을 여는 모드. 파일 모드이다. 

■ 파일 모드는 파일과 관련된 동작 방식을 지정하는 문자열이다. 위의 예시처럼 'r'이면 읽기 모드로, 파일 읽기 작업을 위하여 파일을 여는 것이다.

■ close() 함수는 파일을 닫기 위해 사용한다.


2.2 파일 모드

■ 기본적인 파일 모드로 'r', 'w', 'a'가 있다.

파일 모드 모드 이름 설명
r 읽기 모드(read mode) 파일의 처음부터 읽는다. 파일을 읽기만 할 때 사용한다.
w 쓰기 모드(write mode) 파일에 내용을 쓸 때 사용한다. 파일의 처음부터 쓰며, 파일이 없으면 생성된다.
만약, 파일이 존재하면 기존의 내용은 지워진다.
a 추가 모드(append mode) 파일의 마지막에 내용을 추가할 때 사용한다. 마찬가지로 파일이 없으면 생성된다.

- 파일 모드는 이 외에 'r+'같은 읽기와 쓰기 혼합 모드, 파일을 이진(binary) 형식으로 실행하는 이진 모드 'b' 등이 있다.

■ 현재 작업 디렉토리가 아닌 특정 디렉토리에 존재하는 파일을 열어야 하는 경우가 있다.

■ 예를 들어 C: 드라이브의 어떤 폴더에 input.txt가 있다면, 다음과 같이 경로를 지정할 수 있다

f = open("C:\\...\\...\\input.txt", "r", encoding = "utf-8")
또는
f = open("C:/.../.../input.txt", "r", encoding = "utf-8")

■ \\은 \(백슬래시) 기호 자체를 의미한다. \은 이스케이프 문자 \n처럼 파이썬 의미를 가지는 기호이므로 \만 쓰면 특수 문자로 취급된다.

예를 들어 input.txt가 n으로 시작하는 폴더에 저장되어 있을 때, 경로를 "C:\n..\input.txt"처럼 작성하면, \n 부분이 줄바꿈 기호로 해석된다. 이런 문제를 방지하기 위해 2개의 백슬래시를 붙여 표시하는 것이다.

■ \\외에 경로 구분자로 /(슬래시)를 사용할 수도 있다. 

■ 만약, 파일이 현재 작업 디렉토리에 있다면 다음과 같이 경로를 붙이지 않아도 된다.

f = open("input.txt", "r", encoding = "utf-8")

2.3 파일에서 읽기 

2.3.1 readline

■ 파일 크기가 매우 클 수 있기 때문에 일반적으로 파일의 모든 내용을 한 번에 읽기보다는 줄 단위로 끊어서 읽는다. 파일 전체를 한 번에 읽으려면 파일 객체가 가지고 있는 read() 메서드를, 한 줄을 읽으려면 readline() 메서드를 호출한다. 

■ 1.의 파일 포인터 예시처럼 텍스트 파일을 열면 1개의 파일 포인터는 해당 파일의 첫 번째 행을 시작으로 맨 첫 부분을 가리킨다.

이때, 매번 변수=파일객체.readline()의 명령이 실행되면, 현재 행은 변수에 설정되고, 파일 포인터는 현재 행의 끝에 도달할 때까지 진행된다. 이 진행 과정에서 텍스트를 읽어 반환하는 것이다.

■ 한 행을 다 반환하면, 파일 포인터는 다음 행으로 이동한다.

readline() 메서드는 파일의 모든 행을 읽은 후에 공백 문자열까지 포함하여 반환한다. 

■ 예를 들어, input.txt 파일에 다음과 같이 2줄이 저장되어 있다고 하자.

애플
사과
f = open('input.txt', 'r')
line = f.readline()
print(line)
line = f.readline()
print(line)
line = f.readline()
print(line)
line = f.readline()
print(line)
```#결과#```
애플

사과


````````````

첫 번째 readline() 호출은 "애플\n" 문자열을 반환한다. 두 번째 readline() 호출은 "사과\n" 문자열을 반환한다. 세 번째 readline() 호출은 더 이상 읽을 행(줄)이 없기 때문에 공백 문자열 ""이 반환된다.

f = open('input.txt', 'r')

line = f.readline() 
while line != "" :
    print(line)
    line = f.readline()
f.close()
```#결과#```
애플

사과

````````````

이 예를 실행하는 동안 파일 포인터의 위치는 다음과 같다.

■ 이 예에서 "애플" 다음에 빈 줄이 출력되는 이유는 line 변수 안에 줄바꿈 문자 "\n"이 저장되어 있기 때문이다.

예시처럼 문자열의 오른쪽에 줄바꿈 문자를 삭제하려면 rstrip() 메서드를 사용하면 된다. strip()을 사용해도 된다.

- strip()은 문자열의 첫 부분과 끝부분에서 줄바꿈 문자같은 공백 문자를 제거한다.

- rstrip()은 끝부분(맨 오른쪽)에 위치한 공백 문자만 제거한다.

f = open('input.txt', 'r')

line = f.readline().strip()
while line != "" :
    print(line)
    line = f.readline().strip()
f.close()
```#결과#```
애플
사과
````````````

f = open('input.txt', 'r')

line = f.readline().rstrip()
while line != "" :
    print(line)
    line = f.readline().rstrip()
f.close()
```#결과#```
애플
사과
````````````

■ 만약, 파일 안에 문자가 아닌 숫자 데이터가 저장되어 있을 때, 문자열을 숫자로 변환하고 싶다면 int() 함수를 사용하여 데이터 타입을 정수로 만들거나, float() 함수를 사용하여 부동소수점으로 만들 수 있다. 

line = f.readline().rstrip()

num1 = int(line)
또는
num2 = float(line)

■ 다음과 같이 while 문으로 무한 루프를 만들어서, 무한 루프 안에서 파일객체.readline()을 호출해 계속 한 행씩 읽어 들여 모든 줄을 출력할 수도 있다. 

■ 더 이상 읽을 행이 없으면 공백 문자열이 반환되기 때문에, break 명령문을 사용하여 무한 루프에서 빠져 나와야 한다. 

f = open('input.txt', 'r')
while True:
    line = f.readline()
    if line == "":
        break
    print(line)
f.close()
```#결과#```
애플

사과
````````````

다른 방법은 다음과 같이 for 문을 사용하는 것이다. 파일은 문자열들이 저장되어 있는 시퀀스 객체로 볼 수 있다. 

f = open('input.txt', 'r')
for line in f:
    line = line.rstrip()
    print(line)
f.close()
```#결과#```
애플
사과
````````````

2.3.2 readlines

■readlines 함수는 파일의 모든 줄을 읽어서 각각의 줄에 있는 것을 요소로 가지는 리스트를 반환한다.

f = open('input.txt', 'r')
lines = f.readlines()
lines
f.close()
```#결과#```
['애플\n', '사과']
````````````
f = open('input.txt', 'r')
lines = f.readlines()
for line in lines:
    print(line)
f.close()
```#결과#```
애플

사과
````````````

f = open('input.txt', 'r')
lines = f.readlines()
for line in lines:
    line = line.strip()
    print(line)
f.close()
```#결과#```
애플
사과
````````````

2.3.3 read

■ read() 함수는 파일의 전체 내용을 문자열로 반환한다.

f = open('input.txt', 'r')
lines = f.read()
print(lines)
f.close()
```#결과#```
애플
사과
````````````

2.4 파일에 쓰기

2.4.1 write

■ 파일을 쓰기 모드인 "w" 모드로 열었다면, 해당 파일이 이미 존재한다면 원래 내용이 모두 사라지고, 해당 파일이 존재하지 않으면 새로운 파일이 생성된다.

"w" 모드로 열었을 때, write() 메서드를 사용하여 파일에 텍스트를 쓸 수 있다. 예를 들어 다음과 같이 문자열 "포테이토"를 파일에 쓸 수 있다.

f = open('new.txt', 'w')
f.write('포테이토\n')

- 파일에 쓸 때, 줄 바꿈을 원한다면 마지막 부분에 줄 바꿈 문자를 붙여줘야 한다. 

■ 다음과 같이 포매팅을 이용하여 변수의 값을 문자열 안에 포함시킬 수 있다.

num = 5
f.write(f'포테이토 개수={num}\n')

f.write('포테이토 개수=%s\n' % num)

■ 유의해야 할 점은, write() 함수는 자동 줄 바꿈이 되지 않기 때문에 줄 바꿈 문자 \n을 직접 추가해야 한다는 점이다.

■ write() 함수를 여러 번 사용하여 각 줄에 입력할 내용을 각각 넣을 수도 있다. 단, 이러한 방식은 입력할 데이터가 많은 경우 비효율적이다.

■ 이런 경우, 다음과 같이 각 줄에 입력할 데이터를 요소로 가지는 리스트를 만든 다음, for 문을 반복해서 write()를 실행하게 할 수 있다.

list1 = ['포테이토', '감자','애플', '사과']

with open('input.txt', 'w') as f:
    for item in list1:
        f.write(f'{item}\n')
!type input.txt
```#결과#```
포테이토
감자
애플
사과
````````````

 

2.4.2 writelines

■ writelines() 함수는 readlines() 함수처럼 여러 줄을 한 번에 작업하는 메서드이다. 그러므로 writelines()는 for문이나 다른 조건문이 별도로 필요하지 않다.

위와 같이 리스트를 이용할 경우, 리스트의 요소인 각 문자열에는 줄바꿈 기호 \n가 포함되어 있어야 한다. 

list2 = ['포테이토\n', '감자\n','애플\n', '사과']

with open('input.txt', 'w') as f:
    f.writelines(list2)
    
!type input.txt
```#결과#```
포테이토
감자
애플
사과
````````````

2.4.3 'r+' 모드와 seek() 함수

■ 'r+' 모드는 읽기와 쓰기가 모두 가능한 모드이다. 

■ 'r+' 모드로 파일을 열 때, 현재 위치를 가리키는 파일 포인터를 제어하기 위해 주로 seek() 함수를 사용한다.

■ 파일을 읽을 때와 쓸 때, 모두 파일 포인터가 변하기 때문에 생각한 것과 다른 위치에서 작업이 실행될 수 있다.

■ 예를 들어, 'r+' 모드로 파일을 열었을 때, 파일 포인터는 맨 첫 부분에 배치된다. 그러므로 이 상태에서 write() 함수로 쓰기 작업을 실행하면, 기존 파일의 내용이 덮어쓰여진다. 

■ 이런 문제를 피하기 위해 seek() 함수로 파일 포인터를 변경한 다음, 작업을 진행하는 것이다. seek() 함수는 파일 내 특정 위치로 파일 포인터를 이동시킨다. 

with open ('input.txt', 'r+') as f:
    lines = f.read() # 파일의 모든 내용 읽기
    print(lines);print('--'*5)
    f.write('\n')
    f.write('dog and cat')
    
    f.seek(0) # 파일 포인터를 파일 맨 첫 부분으로 이동
    f.write('dog\n') # 파일 내용 덮어쓰기 # 파일 포인터 이동
    f.write('cat') # 파일 내용 덮어쓰기 # 파일 포인터 이동
    lines = f.read() # 현재 위치에서 파일 읽기
    print(lines);print('--'*5)

    f.seek(0) # 파일 포인터를 파일 맨 첫 부분으로 이동
    lines = f.read()
    print(lines);print('--'*5)
    
```#결과#```
포테이토
감자
애플
사과
----------

감자
애플
사과
dog and cat
----------
dog
cat
감자
애플
사과
dog and cat
----------
````````````

- 첫 번째 lines = f.read()에서 파일의 모든 내용을 읽어 온다. 즉, 파일 포인터는 맨 처음부터 파일 끝까지 이동하게 되며, 모든 내용을 읽어 온다. 그래서 첫 번째 출력 결과 lines 변수에 "포테이토\n감자\n애플\n사과"라는 문자열이 저장된 것을 볼 수 있다. 

- read() 함수를 사용했기 때문에 현재 파일 포인터의 위치는 파일의 끝에 위치한다. 그러므로 이어서 실행되는 f.write('\n'), f.write('dog and cat') 는 현재 파일 포인터 위치인 파일 끝에 "\n", "dog and cat"이라는 내용이 추가된다.

- 그다음, f.seek(0)을 통해 파일 포인터를 다시 맨 처음(0번 위치)으로 이동시킨다. seek() 함수에 전달된 숫자 0은 절댓값으로 파일의 위치를 의미한다. 

- f.seek(0)을 통해 파일 포인터를 파일의 맨 처음으로 이동시킨 상태에서 f.write('dog\n')를 실행하면, 기존의 "포테이토\n" 부분이 "dog\n"으로 덮여 쓰여지기 시작한다. 

- f.write('cat')을 실행하면, 현재 파일 포인터 위치는 'cat'의 바로 뒤를 가리키고 있게 된다.

- 이 상태에서 lines = f.read()로 현재 파일 포인터의 위치('cat'의 바로 뒤)부터 파일 끝까지의 내용을 읽게 된다. 그 결과가 바로 두 번째 출력 결과이다.

- "감자\n"라는 기존 문자열이 "cat"으로 덮여쓰여졌을 것처럼 보이지만, 두 번째 출력 결과를 보면 실제로는 그렇지 않은 것을 볼 수 있다. 이는 사람이 세는 글자 수와 다르게 컴퓨터는 바이트(byte) 단위로 읽기 때문이다.

- 영어나 공백은 자릿수 하나당 1 byte를 차지하며, 한글은 UTF-8 인코딩을 사용할 경우 글자당 3 byte를 사용한다.

- 즉, 기존 "포테이토\n"은 더 많은 바이트를 차지하고 있었기 때문에 "포테이토\n" 자리에   "dog\n"과 "cat" 문자열이 모두 들어갈 수 있어 "포테이토\n" 뒤의 "감자\n" 문자열은 덮이지 않고 남아 있게 된 것이다.

- 참고로 한글은 UTF-8 인코딩을 사용할 경우 글자당 3 byte, EUC-KR이나 CP949 인코딩을 사용할 경우 2 byte이다.

- 마지막 출력 결과는 f.seek(0)으로 파일 포인터를 맨 앞으로 옮긴 후, f.read()를 통해 전체 파일 내용을 읽었기 때문에, 새로 덮어쓴 "dog"와 "cat"부터 파일의 마지막 내용인 "dog and cat"까지의 문자열이 출력된다.

 

참고) 순차 접근(sequential access)과 임의 접근(random access)

■ 순차 접근 방법에 기반한 파일의 입출력 방법은 파일의 처음부터 순차적으로 접근하여 읽거나 기록한다.

■ 이런 방법의 단점은 한 번 읽은 데이터를 다시 읽으려면 현재 파일을 닫고 다시 열어야 한다는 점과 앞 부분을 건너뛰고 중간이나 마지막 부분으로 바로 접근할 수 없다는 점이다.

■ 반면, 임의 접근 방법은 파일 내 어느 위치에서든 읽기와 쓰기가 가능하다. 

■ 모든 파일에는 파일 포인터(file pointer)가 존재한다. 새 파일을 생성하면 파일 포인터는 값이 0이 된다. 이는 파일의 시작 부분을 가리키는 것이다.

■ 새 파일이 아니라 이미 존재하는 기존 파일을 열 경우, 추가 모드에서는 파일의 끝을 가리키고, 다른 모드에서는 파일의 시작 부분을 가리킨다.

■ 읽기나 쓰기 작업이 수행될 때마다 파일 포인터의 위치는 갱신된다. 

- 예를 들어, 읽기 모드로 파일을 열었을 때 10 바이트를 읽었다면, 파일 포인터의 값은 10

- 추가로 100 바이트를 읽었다면, 파일 포인터의 값은 110이 된다.

■ 순차 접근 방법으로 파일을 읽을 경우, 파일 포인터는 파일 시작 위치부터 시작해 파일의 끝으로 순차적으로 이동한다.

파일 데이터를 모두 읽지 않고 원하는 특정 위치를 골라서 읽고 싶은 경우, seek() 함수를 이용하여 파일 포인터를 임의의 위치로 이동시킬 수 있다.  

cf) 파일 포인터의 현재 위치는 tell() 함수로 알 수 있다.

 

2.4.4 현재 파일에 행(줄) 하나 추가하기

■ 추가 모드인 'a'는 파일의 끝에 새로운 행을 추가한다. write() 함수나 writeliness() 함수로 새로운 행을 추가하는 데 사용할 수 있다.

f = open('input.txt', 'a')
f.write(' and bird')
f.close()

!type input.txt
```#결과#```
dog
cat
감자
애플
사과
dog and cat and bird
````````````

2.5 파일 닫기

■ 파일 작업을 마쳤으면 파일을 닫아야 한다. close()는 열려 있는 파일 객체를 닫아 주는 역할을 한다. 

■ 파이썬은 열려 있는 파일의 객체를 자동으로 닫아 주지만, 다음과 같이 close()를 사용해서 열려 있는 파일을 직접 닫아 주는 것이 좋다.

쓰기 모드로 열었던 파일을 close()로 닫지 않는다면 write()로 쓴 데이터가 정상적으로 저장되지 않거나, 다시 사용하려면 오류가 발생하기 때문이다. 

f = open('input.txt', 'w') # 파일 열기 # 쓰기 모드
f.write('감자\n')
f.close() # 파일 닫기

■ 위의 방법은 완전하지는 않다. open()과 close() 코드 중간에 오류가 발생하면 파일을 제대로 닫지 않고 코드가 종료될 수 있기 때문이다. 이런 문제를 방지하기 위해 다음과 같이 try-finally 블록을 사용한다.

try:
    f = open('input.txt', 'w')
    f.write('감자\n')
finally: # 예외가 발생하더라도 반드시 실행
    f.close()

■ 이렇게 try-finally 구문을 사용한다면, try의 블록에는 예외가 발생할 가능성이 있는 작업들을 두고, finally 블록에는 예외가 발생하더라도 반드시 실행하니 finally에 f.close()를 두면 프로그램을 중지시키는 예외가 발생하더라도 파일을 정상적으로 닫을 수 있다.

■ 파일을 닫는 가장 좋은 방법은 with 명령문을 사용하는 것이다. with 문을 사용하면, with 블록이 종료될 때 파일이 자동으로 닫히기 때문이다. 즉, close()를 명시적으로 호출할 필요가 없다. close() 호출이 내부적으로 이루어지는 것이다. 

with open('input.txt.', 'w') as f:
    f.write('포테이토\n')
    f.write('감자\n')

■ 읽기 모드도 쓰기 모드와 동일하게 with 문으로 실행할 수 있다.

with open('input.txt', 'r', encoding = 'utf-8') as f:
    lines = f.read()  
    print(lines)


3. 텍스트 파일의 다양한 입출력 방법

3.1 단어로 분리하기

■ 텍스트 파일에 저장된 문장은 split() 함수를 사용해 공백 문자를 기준으로 단어로 나눠 리스트에 저장할 수 있다.

f = open('new.txt', 'r')
lines = f.read()
print(lines)
f.close()
```#결과#```
The! first word? in, business news.
````````````
with open('new.txt', 'r') as f:
    for line in f:
        line = line.strip()
        word_list = line.split()
        for word in word_list:
            word = word.strip('.,!?')
            print(word)
            
```#결과#```
The
first
word
in
business
news
````````````

- 단어에 붙어 있는 문장 부호들을 제거하고 싶으면 위와 같이 strip()류 함수의 인자에 제거할 문장 부호들을 지정하면 된다.

- 이 예에서 split()를 사용했기 때문에 분리자가 공백이다.

3.2 문자 단위로 읽기

■ read() 함수에 인자를 아무것도 넣지 않으면 파일에 있는 전체 내용을 읽게 된다. read() 함수에 인자로 숫자를 넣으면 원하는 개수만큼의 글자를 읽을 수도 있다. 

■ 예를 들어 한 문자씩 읽고 싶으면 다음과 같이 read(1)을 호출하면 된다.

with open('new.txt', 'r') as f:
    while True:
        ch = f.read(1)
        if ch == "": break
        print(ch)
```#결과#```
T
h
e
!
 
f
i
...
````````````


4. 디렉토리

■ 디렉토리 작업은 os 모듈에서 제공하는 함수들을 사용할 수 있다.

■ 현재 파이썬 프로그램이 실행되는 디렉토리를 작업 디렉토리(CWD: Current Working Directory)라고 한다. 

4.1 os.getcwd()

■ 작업 디렉토리를 얻으려면 os 모듈의 getcwd() 함수를 호출하면 된다.

import os

cwd_dir = os.getcwd()

4.2 os.chdir()

■ 처리해야 할 파일들이 다른 디렉토리에 저장되어 있다면 chdir() 함수를 통해 디렉토리를 바꿔주면 된다.

new_dir = os.chdir("C:/Users/USER/Desktop/")

■ 디렉토리가 변경된 것을 확인할 수 있다.

cwd_dir == new_dir
```#결과#```
False
````````````

4.3 os.listdir()

■ listdir() 함수는 지정한 디렉토리 안에 있는 모든 파일 및 디렉토리 이름이 들어있는 리스트를 반환한다.

os.listdir()

type(os.listdir())
```#결과#```
list
````````````

4.3.1 디렉토리의 파일 목록 조회

■ 파일만 처리하면 다음과 같이 isfile() 함수를 사용하면 된다. 해당 경로에 있는 파일이 정말 파일인지 확인할 때 사용한다. 정말 파일이면 True, 파일이 아니거나 존재하지 않으면 False를 반환한다.

if os.path.isfile('mod1.py'):
    print('mod1.py')
```#결과#```
True
````````````    

os.path.isfile('mod3.py')
```#결과#```
False
````````````

■ endswidth() 함수를 사용하면 파일의 확장자를 검사할 수 있다. 

listdir = os.listdir()
for files in listdir: # files = 모든 파일 및 디렉토리 이름
    if os.path.isfile(files): # 파일인 것만
        if files.endswith('.py'): # 파일 중 확장자가 '.py'인 것만
            print(files) # 해당 파일 이름 출력
```#결과#```
mod1.py
mod2.py
test_mypy.py
````````````

- 위의 코드는 확장자가 '.py'인 파일은 전부 찾아서 파일 이름을 출력하는 코드이다.

4.4 os.mkdir()

■ mkdir() 함수를 사용해 다음과 같이 원하는 경로에 새로운 디렉토리를 생성할 수 있다. 단, 디렉토리 이름이 해당 경로에 이미 존재하면 오류가 발생한다.

os.mkdir('/mkdir')

os.mkdir('/mkdir')
```#결과#``
FileExistsError: [WinError 183] 파일이 이미 있으므로 만들 수 없습니다: '/mkdir'
```````````

4.5 os.makedirs()

■ makedirs() 함수는 하위 디렉토리를 연속으로 생성할 때 사용한다.

■ 예를 들어, 다음 코드는 mkdir 디렉토리 안에  a라는 이름의 디렉토리를 생성하고, a 안에 b, b 안에 c를 생성하는 코드이다.

os.makedirs('mkdir/a/b/c')

4.6 os.rmdir()

 rmdir() 함수는 삭제할 디렉토리를 지정하면, 해당 디렉토리를 삭제하는 함수이다. 단, 해당 디렉토리 안이 비어 있어야 삭제가 가능하다. 디렉토리 내에 다른 디렉토리나 파일이 존재하면 오류가 발생한다.

os.rmdir('mkdir/a/b') # 디렉토리 b 안에 디렉토리 c가 존재하고 있는 상태
```#결과#```
OSError: [WinError 145] 디렉터리가 비어 있지 않습니다: 'mkdir/a/b'
````````````

os.rmdir('mkdir/a/b/c') # 디렉토리 c 삭제

os.chdir('mkdir/a/b/c')
```#결과#```
FileNotFoundError: [WinError 2] 지정된 파일을 찾을 수 없습니다: 'mkdir/a/b/c'
````````````

4.7 os.removedirs()

■ os.makedirs() 함수와 정반대의 기능을 수행하는 함수이다. 지정된 경로의 가장 끝에 있는 디렉토리부터 시작하여, 상위 디렉토리들을 거슬러 올라가며 차례대로 삭제한다.

os.removedirs('mkdir/a/b') # 디렉토리 b부터 mkdir까지 삭제

4.8 os.remove()

■ removedirs() 함수가 디렉토리를 삭제하는 함수였다면, remove() 함수는 경로에 지정한 파일을 삭제하는 함수이다. 단, 파일이 열려 있으면 삭제할 수 없다. 

os.remove('test.py') # 현재 디렉토리에 있는 test.py 파일 삭제

4.9 os.path.exists()

경로에 지정한 파일이 존재하는지 확인하는 함수이다. 

os.path.exists('mod1.py')
```#결과#```
True
````````````

■ os.path.isfile()은 경로에 지정한 파일이 정말 파일인지, os.path.exists()은 경로에 지정한 파일이 존재하는지 확인하여 True/False를 반환한다.

4.10 os.rename()

■ rename() 함수는 파일의 경로와 이름을 바꿔주는 함수이다. 

os.rename('test_mypy.py', 'test.py') # 이름만 변경

listdir = os.listdir()
for files in listdir: 
    if os.path.isfile(files): 
        if files.endswith('.py'): 
            print(files) 
```#결과#```
mod1.py
mod2.py
test.py
````````````

os.rename('test.py', 'C:\\Users\\Desktop\\test2.py') # 경로와 이름을 변경


5. 이진 파일

■ 파일(file)과 이진 파일(binary file)의 차이점은 파일에 기록되는 데이터의 형태가 다르다는 것이다.

■ 텍스트 파일은 모든 정보가 문자열로 파일에 저장된다. 예를 들어 숫자 123456을 저장하더라도 실제 파일에는 "123456"이라는 문자열이 기록된다. 즉, 텍스트 파일은 사람이 읽을 수 있는 문자로 기록된다.

■ 반면, 이진 파일은 사람이 읽을 수 없는 이진수 형태로 데이터가 기록된다. 

■ 또한, 텍스트 파일은 아스키같은 인코딩을 사용하기 때문에 서로 다른 컴퓨터 환경에서도 파일을 열 수 있지만, 이진 파일은 인코딩 차이로 인해 인코딩 문제가 발생할 수 있다. 즉, 텍스트 파일에 비해 이식성이 떨어진다.

■ 이진 파일은 같은 내용을 저장하더라도 텍스트 파일보다 파일 크기가 작기 때문에 저장 공간을 적게 차지한다는 장점이 있다.

■ 이진 파일에서 데이터를 읽는 것도 open() 함수를 사용하여 파일을 열면 된다.

f = open('파일명', 'rb')

■ with 문을 이용하는 것도 가능하다.

with open('파일명', 'rb') as f:
    ...,
    ...,
    ...

■ 마찬가지로 파일 객체에 read() 함수를 사용하면 전체 내용을 읽어온다. 단, 이진 파일이라 단위가 바이트(byte)이므로, 예를 들어 read(1)이면 1 byte를 읽고, read(8)이면 8 byte를 읽는다. 

byte1 = f.read(1)
byte8 = f.read(8)

■ 이진 파일에 바이트들을 저장하려면 다음과 같이 bytes() 함수를 이용하면 된다.

f = open('ff.bin', 'wb')
bytesArray = bytes([255, 128, 0, 1])
f.write(bytesArray)

bytesArray
```#결과#```
b'\xff\x80\x00\x01'
````````````
text_data = 'Let sleeping dog lie.'
f = open('aa.bin', 'wb')
text_encode = text_data.encode()
f.write(text_encode)
f.close()

text_encode
```#결과#```
b'Let sleeping dog lie.'
````````````

f = open('aa.bin', 'rb')
bdata = f.read()
bdata_decode = bdata.decode()
f.close()

text_encode
```#결과#```
'Let sleeping dog lie.'
````````````


6. 객체 출력

■ 2.에서 3.의 내용은 문자열 데이터를 텍스트 파일에 쓰고 읽는 방법에 대한 설명이다.

■ 파이썬에서 문자열이 아닌 딕셔너리, 리스트, 클래스와 같은 객체도 형식 그대로 유지하면서 파일로 저장하고 불러올 수 있다. 이때 사용하는 모듈이 pickle이다. 

■ 딕셔너리, 리스트 등의 요소가 많을 경우, 데이터를 그대로 저장하면 용량이 매우 커질 수 있다. pickle을 사용하면 바이너리(binary) 형태로 저장되기 때문에 용량을 크게 줄일 수 있다. 


6.1 pickle

■ 예를 들어, 다음과 같은 딕셔너리가 있다고 하자.

data = {}
data[1] = {'no':10, 'money':1000, 'quality':'high', 'shopping_list':['apple','바나나','grape']}
data['discount_rate'] = 0.5
print(data)
```#결과#```
{1: {'no': 10, 'money': 1000, 'quality': 'high', 'shopping_list': ['apple', '바나나', 'grape']}, 'discount_rate': 0.5}
````````````

■ pickle 모듈의 dump()와 load() 메서드를 사용하여 객체를 쓰고 읽을 수 있다.

■ 생성한 딕셔너리를 확장자가 '.p'인 피클 파일로 저장하는 방법은 다음과 같다.

# 딕셔너리 객체를 pickle 모듈로 압축
with open('data.p', 'wb') as f:
    pickle.dump(data, f)
    
또는

f = open('data.p', 'wb')
pickle.dump(data, f)
f.close()

■ pickle.dump(data, f)는 data.p라는 파일에 딕셔너리 data를 저장한다는 의미이다. 이때 저장하는 데이터는 바이너리이므로 'wb' 형태의 쓰기 모드를 지정해야 한다. 

■ 바이너리(이진) 파일인 피클 파일 'data.p'는 바이너리 데이터 형식으로 내용이 저장되어 있으므로, 파일 내용은 아래와 같이 사람이 읽기는 어렵다.

!type data.p
```#결과#```
�븏}�(K}�(�no봌
�money봎��quality뵆high뵆
shopping_list�]�(�apple뵆	諛붾굹�굹뵆grape봢u�
discount_rate봆?�u.
````````````

■ 피클 파일에 저장된 내용을 (복원해서) 불러오려면 load() 메서드를 사용하면 된다.

with open('data.p', 'rb') as f:
    load_data = pickle.load(f)
    
print(load_data)
```#결과#```
{1: {'no': 10, 'money': 1000, 'quality': 'high', 'shopping_list': ['apple', '바나나', 'grape']}, 'discount_rate': 0.5}
````````````

■ 마찬가지로 불러올 피클 파일은 바이너리이므로 'rb' 형태의 읽기 모드를 지정해야 한다.