A Byte of Python - Problem Solving Python


Problem Solving


파이썬 언어의 많은 부분을 살펴봤으니 뭔가 유용한 것을 하는 프로그램을 설계하고 작성해 봄으로서 이들이 어떻게 함께 운용되는지 보자. 아이디어는 자체적으로 파이썬 스크립트를 어떻게 배우는가에 대한 것이다.

문제

해결하려는 문제는 
- 내 중요한 파일의 백업을 생성하는 프로그램을 원한다

비록, 이 것은 단순한 문제이지만, 솔루션을 시작하는 충분한 정보가 없다. 더 많은 분석이 필요하다. 예를 들어, 어떻게 어떤 파일을 백업할지를 지정할까? 어떻게 저장되었는가? 어디에 저장되었는가?

문제를 적절히 분석한 후 프로그램을 디자인한다. 어떻게 프로그램이 작동할지에 대한 리스트를 만든다. 이 경우, 어떻게 일할지에 대한 다음 리스트를 작성했다. 설계를 한다면 모든 사람들이 저마다 작업하는 방법이 있으므로 같은 분석이 나오지는 않을 것이다. 
- 백업할 파일과 디렉토리는 리스트에 지정된다.
- 백업은 반드시 주 백업 디렉토리에 저장되어야한다.
- 파일은 zip파일로 백업되어야 한다.
- zip압축파일의 이름은 현재 날짜와 시간이어야 한다.
- 표준 zip명령은 표준 GNU/리눅스 또는 유닉스 배포에서 기본적으로 사용할 수 있다. 커맨드라인 인터페이스만 제공된다면 다른 압축 명령을 사용할 수 있다.

윈도우 사용자
윈도우 사용자는 zip명령을 GnuWin32 프로젝트 페이지에서 설치하고 파이썬 명령자체에서 인식하기위해 비슷하도록 시스템 PATH에 C:\Program Files\GnuWin32\bin 을 등록한다.

솔루션

프로그램 설계는 꽤 안정적이다. 이제 솔루션을 구현하는 코드를 작성할 수 있다.

backup_ver1.py:

import os
import time

source=['/Users/swa/notes']
# 필요에 따라 로스트링을 사용하는 것도 고려한다. [r'C:\My Documents']

target_dir = '/Users/swa/backup'

target=target_dir+os.sep+time.strftime('%Y%m%d%H%M%S')+'.zip'

if not os.path.exists(target_dir):
  os.mkdir(target_dir)

zip_command='zip -r {0} {1}'.format(target,' '.join(source))

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

Output:

$ python backup_ver1.pyZip command is:zip -r /Users/swa/backup/20140328084844.zip /Users/swa/notesRunning:  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
이제 작성한 프로그램이 알맞게 작동하는지 테스트할 단계다. 만약 예상대로 작동하지 않는다면 프로그램을 디버그하고 버그를 제거해야 한다.

만약 프로그램이 작동하지 않는다면 Zip command is 이 후에 프린트되는 라인을 복사해 쉘에서 입력해보고 에러를 확인한다. Zip명령이 틀리지 않았는지 확인한다. 이 명령이 잘 작동했다면 문제는 파이썬 프로그램 자체의 문제이다. 위의 프로그램에 일치하는지 확인한다.

동작분석

어떻게 설계에서 코드로 넘어가는지 단계별로 확인했다.

os와 time 모듈을 처음에 임포트했다. 그러면 파일과 디렉토리를 source 리스트에 지정했다. 백업 파일이 저장될 대상 디렉토리는 target_dir 변수로 지정했다. 생성하는 zip압축의 이름은 현 날짜와 시간으로 time.strftime() 함수를 사용해 만들었다. .zip 확장자를 가지며 target_dir 디렉토리에 저장된다.

os.sep 변수는 운영체제에 따라 디렉토리 구분자를 제공해주는데 리눅스, 유닉스, 맥OS는 /를 윈도우는 \ 이다. os.sep 를 사용해 프로그램이 포터블하고 이들 시스템에서 잘 작동하도록 한다.

time.strftime() 함수는 위의 프로그램에서 사용한 사양 값을 받는다. %Y는 연도이다. 전체리스트는 파이썬 레퍼런스 메뉴얼에서 확인한다.

We create the name of the target zip file using the addition operator which concatenates the strings i.e. it joins the two strings together and returns a new one. Then, we create a string zip_command which contains the command that we are going to execute. You can check if this command works by running it in the shell (GNU/Linux terminal or DOS prompt).

The zip command that we are using has some options available, and one of these options is -r. The -r option specifies that the zip command should work recursively for directories, i.e. it should include all the subdirectories and files. Options are followed by the name of the zip archive to create, followed by the list of files and directories to backup. We convert the source list into a string using the join method of strings which we have already seen how to use.

Then, we finally run the command using the os.system function which runs the command as if it was run from the system i.e. in the shell - it returns 0 if the command was successfully, else it returns an error number.

Depending on the outcome of the command, we print the appropriate message that the backup has failed or succeeded.

That's it, we have created a script to take a backup of our important files!

Note to Windows Users

Instead of double backslash escape sequences, you can also use raw strings. For example, use 'C:\\Documents' or r'C:\Documents'. However, do not use 'C:\Documents' since you end up using an unknown escape sequence \D.

Now that we have a working backup script, we can use it whenever we want to take a backup of the files. This is called the operation phase or the deployment phase of the software.

The above program works properly, but (usually) first programs do not work exactly as you expect. For example, there might be problems if you have not designed the program properly or if you have made a mistake when typing the code, etc. Appropriately, you will have to go back to the design phase or you will have to debug your program.

두번째 버전

첫 번째 버전도 작동한다. 그런나 날짜 기반으로 만들면 더 좋을 것이다. 이를 소프트웨어의 유지보수 단계라고 한다.

수정할 것은 더 나은 파일명 매커니즘이다. 파일명에 시간을 사용할 때 현 날짜는 디렉토리로 묶는 것이다. 첫 장점은 계층적인 구조로서 관리하기 편해진다. 두번째 장점은 파일명이 훨씬 짧아진다. 세번째 장점은 구별된 디렉토리가 매일 백업을 만들었다면 체크하는데 도움을 줄 것인데 디렉토리는 해당 날짜에 백업이 있을때만 작성되기 때문이다.

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')

Output:

$ python backup_ver2.pySuccessfully created directory /Users/swa/backup/20140329Zip command is:zip -r /Users/swa/backup/20140329/073201.zip /Users/swa/notesRunning:  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

동작 방식

Most of the program remains the same. The changes are that we check if there is a directory with the current day as its name inside the main backup directory using the os.path.exists function. If it doesn't exist, we create it using the os.mkdir function.

세번째 버전

두번째 버전도 잘 작동하지만, 백업이 너무 많다면 백업간의 차이가 뭔지 알기가 어렵다. 예를 들어, 프로그램 또는 프레젠테이션 사이의 주요 차이를 만들었고 이 변화를 zip압축의 이름에 연관되게 하고 싶다. 이는 zip이름에 사용자 제공 코멘트를 붙임으로서 쉽게 만들 수 있다.

WARNING: The following program does not work, so do not be alarmed, please follow along because there's a lesson in here.

Save as 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')

Output:

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


How This (does not) Work

This program does not work! Python says there is a syntax error which means that the script does not satisfy the structure that Python expects to see. When we observe the error given by Python, it also tells us the place where it detected the error as well. So we start debugging our program from that line.

자세히 살펴보면 단일 논리 라인이 두 물리 라인으로 쪼개졌는데 두 라인이 함께임을 지정하지 않았기 때문이다. 기본적으로, 파이썬은 로지컬 라인에서 어떤 오퍼랜드도 없이 +만 발견하고 이 것이 계속되는지 알지못한다. 다음라인으로 연속됨은 \로 지정할 수 있음을 기억하자. 이 프로그램을 올바르게 작동하게 만드는 작업을 버그 픽싱이라고 한다.

네번째 버전


Save as 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')

Output:

$ python backup_ver4.pyEnter a comment --> added new examplesZip command is:zip -r /Users/swa/backup/20140329/074122_added_new_examples.zip /Users/swa/notesRunning:  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

How It Works

This program now works! Let us go through the actual enhancements that we had made in version 3. We take in the user's comments using the input function and then check if the user actually entered something by finding out the length of the input using the len function. If the user has just pressed enter without entering anything (maybe it was just a routine backup or no special changes were made), then we proceed as we have done before.

However, if a comment was supplied, then this is attached to the name of the zip archive just before the .zip extension. Notice that we are replacing spaces in the comment with underscores - this is because managing filenames without spaces is much easier.

추가 개선

네 번째 버전은 충분히 만족스럽지만 추가적인 개선점도 있다. 예를 들어, zip 명령에 -v를 추가해 verbosity 레벨을 지정하거나 -q를 통해 아무것도 출력되지 않게 할 수 있다.

다른 가능한 개선은 추가 파일과 디렉토리가 스크립트에 전달되게 하는 것이다. 이런 이름은 sys.argv 리스트에서 얻고 소스리스트에는 extend메소드를 사용해 이들을 추가한다.

The most important refinement would be to not use the os.system way of creating archives and instead using the zipfile or tarfile built-in modules to create these archives. They are part of the standard library and available already for you to use without external dependencies on the zip program to be available on your computer.

However, I have been using the os.system way of creating a backup in the above examples purely for pedagogical purposes, so that the example is simple enough to be understood by everybody but real enough to be useful.

Can you try writing the fifth version that uses the zipfile module instead of the os.system call?

소프트웨어 개발 단계

We have now gone through the various phases in the process of writing a software. These phases can be summarised as follows:

What (Analysis)
How (Design)
Do It (Implementation)
Test (Testing and Debugging)
Use (Operation or Deployment)
Maintain (Refinement)
A recommended way of writing programs is the procedure we have followed in creating the backup script: Do the analysis and design. Start implementing with a simple version. Test and debug it. Use it to ensure that it works as expected. Now, add any features that you want and continue to repeat the Do It-Test-Use cycle as many times as required.

Remember:

Software is grown, not built. -- Bill de hÓra

Summary

We have seen how to create our own Python programs/scripts and the various stages involved in writing such programs. You may find it useful to create your own program just like we did in this chapter so that you become comfortable with Python as well as problem-solving.

Next, we will discuss object-oriented programming.

덧글

댓글 입력 영역