실생활 문제 해결

지금까지 파이썬이라는 언어의 여러 가지 구성 요소에 대해 배워 보았습니다. 이제는 지금까지 배운 것들을 토대로, 뭔가 유용한 것을 하는 프로그램을 만들어 보도록 합시다. 이 챕터의 목표는 여러분이 직접 파이썬 스크립트를 만들고 사용하는 법을 배우는 것입니다.

문제

다음과 같은 문제를 해결해 봅시다:

내 중요한 파일들을 백업해두는 프로그램을 만들고 싶어요.

이것은 간단한 문제이지만, 아직 어떻게 접근하면 좋을지 정보가 부족합니다. 따라서 약간 분석을 해 보도록 합시다. 예를 들어, 어떤 파일을 백업해야 할까요? 파일들은 어떻게, 또 어디에 저장되어야 할까요?

문제에 대해 분석한 이후에는, 프로그램을 설계해야 합니다. 이를 위해 우리가 만들 프로그램이 어떻게 동작하면 좋을지 그 목록을 만들어 봅시다. 저는 아래와 같이 저의 방식대로 목록을 만들었습니다. 그러나 모든 사람이 서로 다른 생각을 가지고 있는 것이 당연하므로, 여러분이 목록을 만들어도 저와 같은 항목들로 구성되어 있지 않을 수 있겠죠. 달라도 아무 상관 없습니다.

  • 백업할 파일과 디렉토리들은 리스트의 형태로 지정해 둔다.
  • 백업된 대상들은 백업 디렉토리 안에 저장되어야 한다.
  • 백업된 파일들은 zip 파일로 압축한다.
  • zip 파일의 이름은 현재 날짜와 시간으로 한다.
  • GNU/Linux 환경이나 Unix 환경에서 기본으로 제공되는 zip 명령을 이용한다. (참고: 명령줄 인터페이스에서 사용할 수 있는 어떤 압축 유틸리티든지 사용이 가능합니다)

윈도우 사용사를 위한 주석

윈도우 사용자는 GnuWin32 프로젝트 페이지 에서 zip 명령을 내려받아 설치 할 수 있습니다. 또한 설치한 후 파이썬 명령을 어디서든 실행할 수 있도록 해 주었던 것처럼 C:\Program Files\GnuWin32\bin 폴더를 시스템의 PATH 환경변수에 추가해 주면 어디서든 zip 명령을 사용할 수 있습니다.

첫번째 프로그램

일단은 프로그램을 안정적이게 설계한 것 같으므로, 코드를 입력하여 프로그램으로 구현 해 보도록 합시다.

backup_ver1.py 로 저장하세요:

import os
import time

# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we have to use double quotes inside a string
# for names with spaces in it.  We could have also used
# a raw string by writing [r'C:\My Documents'].

# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using

# 3. The files are backed up into a zip file.
# 4. The name of the zip archive is the current date and time
target = target_dir + os.sep + \
         time.strftime('%Y%m%d%H%M%S') + '.zip'

# Create target directory if it is not present
if not os.path.exists(target_dir):
    os.mkdir(target_dir)  # make directory

# 5. We use the zip command to put the files in a zip archive
zip_command = 'zip -r {0} {1}'.format(target,
                                      ' '.join(source))

# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
    print('Successful backup to', target)
else:
    print('Backup FAILED')

실행 결과:

$ python backup_ver1.py
Zip command is:
zip -r /Users/swa/backup/20140328084844.zip /Users/swa/notes
Running:
  adding: Users/swa/notes/ (stored 0%)
  adding: Users/swa/notes/blah1.txt (stored 0%)
  adding: Users/swa/notes/blah2.txt (stored 0%)
  adding: Users/swa/notes/blah3.txt (stored 0%)
Successful backup to /Users/swa/backup/20140328084844.zip

이제 테스트 단계로 넘어와서 프로그램이 잘 동작하는지 테스트 해보아야 합니다. 만약 예상대로 프로그램이 동작하지 않으면, 프로그램의 버그(bug)를 제거하는 과정, 즉 디버그(debug) 과정을 거쳐야 합니다.

위 예시 프로그램이 잘 동작하지 않는 경우, 실행 결과의 Zip command is 줄 뒤의 내용을 복사한 후 쉘 (GNU/Linux 나 Mac OS X) 혹은 cmd (윈도우 환경) 에 입력해본 뒤 무엇이 문제인지 확인하고 고쳐 보도록 합시다. 또 무엇이 잘못되었는지 확인하기 위해 zip 명령 설명서 또한 확인해 보시기 바랍니다. 만약 위 명령이 올바르게 실행된다면, 문제는 파이썬 프로그램 자체에 있는 것으로 추정되므로 프로그램을 올바르게 정확히 작성했는지 다시 한번 확인해 보시기 바랍니다.

동작 원리

아래에서 단계별로 어떻게 우리의 설계코드 로 바꾸었는지에 대해 설명할 것입니다.

먼저 ostime 모듈을 불러왔습니다. 그리고, 백업할 파일들과 디렉토리들을 source 라는 리스트에 담아 두었고, 또 백업을 저장해 둘 대상 디렉토리는 target_dir 변수에 지정해 주었습니다. zip 파일의 이름은 현재 날짜와 시간으로 할 것이므로 time.strftime() 함수를 사용하여 현재 날짜와 시간을 얻어온 후 .zip 확장자를 붙여 target_dir 디렉토리에 저장하도록 했습니다.

여기서 os.sep 변수를 주목하시기 바랍니다. 이것은 여러분의 운영 체제에 다른 디렉토리 구분자를 나타내는 것으로 GNU/Linux, Unix, macOS 환경에서는 '/' 일 것이고, 윈도우 환경에서는 '\\' 일 것입니다. 이러한 문자들을 직접 사용하지 않고 os.sep 을 사용함으로써 여러분의 프로그램에 범용성을 제공할 수 있고, 따라서 여러 운영체제에서 수정 없이 사용할 수 있게 할 수 있습니다.

위 프로그램에서 사용한 것처럼 time.strftime() 함수는 특별한 형식을 인수로 넘겨 받습니다. %Y 는 네 자리의 연도로 치환되며, %m 은 두 자리의 달 즉 0112 사이의 숫자를 의미합니다. 날짜 형식에 대해서는 파이썬 레퍼런스 매뉴얼 을 참고하시기 바랍니다.

이제 대상 zip 파일의 이름을 생성하기 위해 문자열 결합 연산자인 더하기 연산자를 사용하여 두 문자열을 합쳐서 하나의 문자열로 만듭니다. 그리고, zip_command 라는 문자열을 만들어 실제로 실행하게 될 문자열을 만듭니다. GNU/Linux 터미널이나 DOS 프롬프트에서 이 문자열을 실행시켜 보고 잘 동작하는지 확인해 볼 수 있을 것입니다.

앞으로 우리가 사용할 zip 명령은 몇 가지 옵션과 매개 변수를 필요로 합니다. -r 옵션은 zip 명령이 주어진 디렉토리에 대해 회귀적(recursive)으로, 즉 해당 디렉토리가 포함하고 있는 모든 하위 디렉토리와 파일들을 포함하도록 하는 명령입니다. 여기서는 두 가지 옵션을 결합하여 하나의 축약 옵션 -qr 을 지정해 주었습니다. zip 명령 뒤에는 차례로 이 옵션이 지정되고, 그 뒤에는 생성될 zip 파일의 이름이, 마지막으로는 압축될 대상 디렉토리들과 파일들이 지정됩니다. 이를 위해 source 리스트를 앞서 설명한 join 메소드를 통해 문자열로 바꿔서 넘겨 주었습니다.

그러면, 최종적으로 os.system 함수를 통해 이 명령을 실제 시스템(system) 의 쉘에서 실행시킵니다. 그러면 프로그램이 성공적으로 실행된 경우 0 이 반환되며 그렇지 않으면 숫자로 된 오류 코드가 반환됩니다.

이제 명령의 실행 결과에 따라, 적절한 메시지를 출력해 주고 백업이 성공했는지 실패했는지를 화면에 출력해 줍니다.

여기까지입니다. 이제 우리는 중요한 파일을 백업하는 스크립트를 성공적으로 만들었습니다!

윈도우 사용자를 위한 주석

이스케이프 문자 백슬래시를 두 번씩 입력하는 것보다 raw 문자열을 이용할 수도 있습니다. 예를 들어, 'C:\\Documents'r’C:\Documents' 로도 쓸 수 있습니다. 그러나, 'C:\Documents' 와 같이 사용할 수는 없습니다. 이것은 \D 라는 알 수 없는 이스케이프 문자를 의미하는 것으로 오류가 발생합니다.

이제 잘 동작하는 백업 스크립트를 만들었으므로, 이 스크립트를 언제든지 사용하여 중요한 파일들을 백업할 수 있습니다. 이 단계를 소프트웨어의 운영(Operation) 단계 혹은 배포(Deployment) 단계라고 합니다.

위 프로그램은 잘 동작하지만, 첫번째로 만든 프로그램은 (종종) 예상한 대로만은 실행되지 않습니다. 예를 들어 프로그램 설계를 잘못했다든지 코드를 입력할 때 실수를 했다던지 할 수 있습니다. 이런 경우 상황에 맞춰서 설계 단계로 돌아가거나 프로그램을 디버깅해야 합니다.

두 번째 프로그램

첫 번째로 만든 프로그램은 일단 잘 동작합니다. 그러나, 프로그램을 매일매일 잘 쓰기 위해 좀 더 개선의 여지가 있습니다. 이 단계를 소프트웨어의 유지보수(maintenance) 단계라고 합니다.

제가 이 프로그램을 쓰다가 느낀 한가지 개선점은 파일에 이름을 짓는 부분에 대한 것인데, 주 백업 디렉토리에 날짜로 된 하위 디렉토리를 만들고 시간으로 된 압축 파일들을 그 안에 넣는 것입니다. 이렇게 하면 백업된 파일들이 계층적으로 저장되므로 좀 더 쉽게 관리할 수 있을 것입니다. 또한, 파일명도 좀 더 짧아집니다. 마지막으로 각각 디렉토리에 백업 파일이 나뉘어 저장되므로 어떤 날에 백업을 했는지 여부를 그 날짜에 해당하는 디렉토리가 있는지 여부만으로 쉽게 확인할 수 있을 것입니다.

backup_ver2.py 로 저장하세요:

import os
import time

# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"', 'C:\\Code']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we had to use double quotes inside the string
# for names with spaces in it.

# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using

# Create target directory if it is not present
if not os.path.exists(target_dir):
    os.mkdir(target_dir)  # make directory

# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory
# in the main directory.
today = target_dir + os.sep + time.strftime('%Y%m%d')
# The current time is the name of the zip archive.
now = time.strftime('%H%M%S')

# The name of the zip file
target = today + os.sep + now + '.zip'

# Create the subdirectory if it isn't already there
if not os.path.exists(today):
    os.mkdir(today)
    print('Successfully created directory', today)

# 5. We use the zip command to put the files in a zip archive
zip_command = 'zip -r {0} {1}'.format(target,
                                      ' '.join(source))

# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
    print('Successful backup to', target)
else:
    print('Backup FAILED')

실행 결과:

$ python backup_ver2.py
Successfully created directory /Users/swa/backup/20140329
Zip command is:
zip -r /Users/swa/backup/20140329/073201.zip /Users/swa/notes
Running:
  adding: Users/swa/notes/ (stored 0%)
  adding: Users/swa/notes/blah1.txt (stored 0%)
  adding: Users/swa/notes/blah2.txt (stored 0%)
  adding: Users/swa/notes/blah3.txt (stored 0%)
Successful backup to /Users/swa/backup/20140329/073201.zip

동작 원리

많은 부분은 이전과 그대로입니다. 변경된 부분은 주 백업 디렉토리 안에 그 날짜에 해당하는 디렉토리가 있는지 여부를 os.path.exists 함수로 확인하는 부분입니다. 해당 디렉토리가 없으면, os.mkdir 함수를 통해 디렉토리를 새로 만듭니다.

세 번째 프로그램

두 번째 프로그램도 잘 동작했지만, 백업을 많이 하고 싶을 때, 많은 백업 파일이 생성되므로 어떤 파일이 어떤 것의 백업인지 구분하기가 너무 어려웠습니다! 예를 들어, 어떤 문서나 프로그램에 큰 변화를 주었을 때 그 내용을 zip 파일의 이름에 추가로 달아 주면 좋을 것 같습니다. 이 문제는 zip 파일을 생성할 때 뒤에 사용자 정의 꼬리말을 달아 주는 기능을 추가하면 쉽게 해결될 것입니다.

WARNING: 아래 프로그램은 동작하지 않으니 놀라지 마시고 쭉 따라오시기 바랍니다. 이를 통해 뭔가를 배울 것입니다.

backup_ver3.py 로 저장하세요:

import os
import time

# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"', 'C:\\Code']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we had to use double quotes inside the string
# for names with spaces in it.

# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using

# Create target directory if it is not present
if not os.path.exists(target_dir):
    os.mkdir(target_dir)  # make directory

# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory
# in the main directory.
today = target_dir + os.sep + time.strftime('%Y%m%d')
# The current time is the name of the zip archive.
now = time.strftime('%H%M%S')

# Take a comment from the user to
# create the name of the zip file
comment = input('Enter a comment --> ')
# Check if a comment was entered
if len(comment) == 0:
    target = today + os.sep + now + '.zip'
else:
    target = today + os.sep + now + '_' + 
        comment.replace(' ', '_') + '.zip'

# Create the subdirectory if it isn't already there
if not os.path.exists(today):
    os.mkdir(today)
    print('Successfully created directory', today)

# 5. We use the zip command to put the files in a zip archive
zip_command = "zip -r {0} {1}".format(target,
                                      ' '.join(source))

# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
    print('Successful backup to', target)
else:
    print('Backup FAILED')

실행 결과:

$ python backup_ver3.py
  File "backup_ver3.py", line 39
    target = today + os.sep + now + '_' +
                                        ^
SyntaxError: invalid syntax

동작(하지 않는) 원리

이 프로그램은 동작하지 않습니다! 실행시켜보면 구문 오류가 있다는 메시지가 출력되며, 이것은 위 스크립트가 파이썬의 문법 규칙을 만족하지 않는다는 것을 의미합니다. 파이썬이 출력해준 오류 메시지를 확인해 보면 어디에서 이 오류가 발생했는지를 알려 줍니다. 따라서 그 줄부터 디버깅 을 시작해 봅시다.

자세히 살펴보면, 한 개의 논리적 명령줄이 두개의 물리적 명령줄로 나뉘어 있지만 두 개의 물리적 명령줄이 사실 하나의 명령줄이라는 것을 파이썬에게 알려줄만한 뭔가가 누락되어 있습니다. 여기서 파이썬은 더하기 연산자 (+)를 발견했으나 그 논리적 명령줄에 피연산자가 없음을 발견하고는 어떻게 해야 할지 모르는 상황에 처하게 된 것입니다. 이 경우, 두 물리적 명령줄을 하나로 연결해 주기 위해서는 맨 뒤에 백슬래시를 추가해 주어야 한다고 배웠으므로 누락된 백슬래시를 추가해 줍니다. 프로그램에서 문제를 찾고 수정 하는 이러한 과정을 버그 수정 이라고 합니다.

네 번째 프로그램

backup_ver4.py 로 저장하세요:

import os
import time

# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"', 'C:\\Code']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we had to use double quotes inside the string
# for names with spaces in it.

# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using

# Create target directory if it is not present
if not os.path.exists(target_dir):
    os.mkdir(target_dir)  # make directory

# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory
# in the main directory.
today = target_dir + os.sep + time.strftime('%Y%m%d')
# The current time is the name of the zip archive.
now = time.strftime('%H%M%S')

# Take a comment from the user to
# create the name of the zip file
comment = input('Enter a comment --> ')
# Check if a comment was entered
if len(comment) == 0:
    target = today + os.sep + now + '.zip'
else:
    target = today + os.sep + now + '_' + \
        comment.replace(' ', '_') + '.zip'

# Create the subdirectory if it isn't already there
if not os.path.exists(today):
    os.mkdir(today)
    print('Successfully created directory', today)

# 5. We use the zip command to put the files in a zip archive
zip_command = 'zip -r {0} {1}'.format(target,
                                      ' '.join(source))

# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
    print('Successful backup to', target)
else:
    print('Backup FAILED')

실행 결과:

$ python backup_ver4.py
Enter a comment --> added new examples
Zip command is:
zip -r /Users/swa/backup/20140329/074122_added_new_examples.zip /Users/swa/notes
Running:
  adding: Users/swa/notes/ (stored 0%)
  adding: Users/swa/notes/blah1.txt (stored 0%)
  adding: Users/swa/notes/blah2.txt (stored 0%)
  adding: Users/swa/notes/blah3.txt (stored 0%)
Successful backup to /Users/swa/backup/20140329/074122_added_new_examples.zip

동작 원리

이제 프로그램이 잘 동작합니다! 이제 세 번째 프로그램을 작성할 때 추가했던 사항들에 대해 살펴보도록 합시다. 먼저 input 함수를 통해 사용자의 꼬릿말을 입력받은 후, 사용자가 뭔가를 입력했는지 여부를 len 함수를 통해 확인합니다. 만약 사용자가 아무것도 입력하지 않고 enter 키를 입력한 경우 (아마도 특별한 꼬릿말이 필요 없는 일상적인 백업을 할 경우에 해당될 것입니다), 이전과 동일하게 처리합니다.

그러나 사용자가 꼬릿말을 입력한 경우에는, zip 파일명을 생성할 때 뒤에 이 꼬릿말을 붙여 주고 .zip 확장자를 붙여 줍니다. 여기서 사용자가 입력한 꼬릿말에 포함된 공백 문자를 모두 밑줄로 치환하였는데, 이것은 나중에 파일들을 관리할 때 공백 문자가 없는 편이 관리가 더 쉽기 때문입니다.

더 많은 개선점

아마 네 번째 프로그램은 많은 경우에 만족스럽게 사용될 수 있겠지만, 언제나 개선할 사항은 넘쳐납니다. 예를 들면, 사용자가 -v 옵션을 지정하면 zip 명령의 상세함(verbosity) 단계를 지정하게 하여 프로그램이 실행될 때 처리되는 사항을 더 상세히 화면에 출력해주도록 할 수도 있고, -q 옵션을 통해 아무 출력 없이 조용히(quiet) 프로그램이 실행되도록 할 수도 있습니다.

또 다른 가능한 개선사항은 추가로 백업할 파일들이나 디렉토리들을 명령줄로부터 넘겨받아 함께 백업하게 하는 것을 생각해 볼 수 있습니다. 이러한 추가 파일들의 이름은 sys.argv 리스트를 이용하면 넘겨받을 수 있을 것이고, 이것을 list 클래스의 extend 메소드를 이용하여 source 리스트 뒤에 추가해 줄 수 있을 것입니다.

생각해볼 수 있는 가장 중요한 개선사항은 os.system 을 사용하지 않고 파이썬에서 제공되는 내장 모듈인 zipfile 이나 tarfile 을 이용하여 압축 파일을 생성하는 것입니다. 이들은 표준 라이브러리에 포함되어 있으며 zip 프로그램과 같은 추가 프로그램 등을 설치하지 않고서도 프로그램이 동작할 수 있도록 해 줄 수 있을 것입니다.

이 예제에서는 순전히 교육적인 목적에서 os.system 을 이용하여 백업 파일을 생성하였는데, 이것은 누구나 알아볼 수 있을 만큼 간단한 프로그램을 구현할 수 있으면서도, 또 실제로 사용할만 한 프로그램을 제작할 수 있기도 하기에 사용한 것입니다.

자, 이제 os.system 을 호출하지 않고 zipfile 모듈을 사용하여 다섯번째 프로그램을 만들어보지 않겠습니까?

소프트웨어 개발 단계

지금까지 소프트웨어를 개발하면서 여러 단계 들을 거쳐 왔습니다. 이 단계들은 다음과 같이 축약하여 설명할 수 있습니다:

  1. 무엇을 만들 것인가? (분석 단계)
  2. 어떻게 만들 것인가? (설계 단계)
  3. 만들기 (구현 단계)
  4. 테스트 하기 (테스트와 디버깅 단계)
  5. 실제로 사용하기 (운영 또는 배포 단계)
  6. 유지 및 보수하기 (개선 단계)

앞으로 프로그램을 작성할 때 지금까지 여러분이 백업 스크립트를 만들면서 거쳐 왔던 과정을 그대로 따라 하기를 추천합니다. 문제를 분석하고 프로그램을 설계하세요. 구현은 가장 단순한 프로그램으로 시작하세요. 테스트하고 디버그하세요. 한번 사용해보고 제대로 동작하는지 확인해보세요. 이제, 원하는 기능을 추가하고, 만들어보고 테스트해보고 사용해보는 일련의 과정들을 반복하며 프로그램을 개선해 나가세요.

기억하세요:

소프트웨어는 성장하는 것이며, 만들어지는 것이 아니다. -- Bill de hÓra

요약

지금까지 직접 파이썬 프로그램/스크립트를 만드는 법과 이러한 프로그램을 만들기 위해 거쳐야 하는 여러가지 단계들에 대해 배워 보았습니다. 이 챕터에서 배운 것들을 통해 프로그램을 작성하는 방법을 익혀두면 유용할 것이며, 또 실제 문제를 해결하는 데 파이썬을 사용하는 것에 좀 더 익숙해질 수 있을 것입니다.

다음으로는, 객체 지향 프로그래밍에 대해 다루어 보겠습니다.

results matching ""

    No results matching ""