Python

[Python] Argument Parser

달나라외계인 2024. 2. 2. 11:19

  argparse 모듈은 실행시 인자로 받은 내용을 처리하기 쉽게 만들어주는 라이브러리입니다. 파이썬을 메인으로 사용하고 있지 않아서 라이브러리로 이런걸 제공하는 것도 몰랐었습니다. 처음에 그냥 직접 만들어 사용하다가 바보짓을 했다는 알게 되었죠. 잊지 않게 예제 위주의 간단한 사용법을 기록해 둡니다.

 

간단하게 다음의 형태의 프로그램을 작성할 때, 어떻게 인자를 받을지 예제를 만들어 보겠습니다.

--c 옵션으로 download, upload 기능을 선택하여 제공하며, source 파일경로가 필수로 들어갑니다.
--c 옵션에서 upload 기능을 사용했을시 target 파일경로를 추가로 반드시 필요로 합니다.

 

먼저 사용하기에 앞서서 모듈을 import 해주고 인자를 받기 위한 객체를 만듭니다.

파이썬을 설치하면 기본적으로 설치되는 모듈이기에 설치 작업이 필요하지는 않습니다.

import argparse
parser = argparse.ArgumentParser()

이제 인자로 받을 옵션들을 parser에게 정의해 줍니다.

parser.add_argument('--command', '-c', type=str, choices=['download','upload'], help='command', required=True)
parser.add_argument('source_path', type=str)
parser.add_argument('target_path', type=str, nargs='?')

  맨처음 들어가는 인자는 명령이름입니다. 추가 인자로 여러개의 이름을 지어줄 수도 있습니다. 보통 약어 명령을 넣어줍니다. 그리고 다음 인자인 'type'은 인자의  자료형을 의미합니다. 또한 인자 'help'를 넣어주면 자동으로 만들어주는 -h 명령 동작 시 해당항목을 설명하는 내용으로 삽입됩니다.

 

  또한 명령 이름에 - 가 붙으면 positional arguments (필수항목), 없으면 optional arguments (옵션항목)가 됩니다.  optional arguments  항목은 명령 이름 후에 값을 넣어 사용하며, positional arguments의 이름은 인자를 쓸 때는 사용되지 않고 값만 넣습니다. positional arguments가 여러개인 경우 정의된 순서로 기입합니다.

 

예) test.py -c upload source.txt /target/

 

'required' 인자는 반드시 설정되어야 하는 인자를 의미합니다. 평상시에는 쓸 일이 없지만 특정 옵션을 필수항목으로 만들어 주고 싶을때 사용됩니다. --c 옵션은 이름 규칙에 의해 optional arguments가 되야 하지만 required 명령에 의하여 positional arguments가 됩니다.

 

  'nargs'는 좀 더 특이한 인자입니다. 보통은 하나의 인자가 하나의 값과 매칭됩니다. nargs는 숫자값을 넣는다면 몇개의 값과 매칭될 지 적어줄 수 있습니다. *, +는 하나 인자이상 사용가능 (+면 하나도 없으면 에러), ?는 없거나 1개인 경우이다.

 

이제 다음의 명령을 통해 인자로 넘어온 값들을 파싱하여 가져오고 처리하면 됩니다.

args = parser.parse_args()

if args.command == 'upload':
    print(args.source_path + "를 " + args.target_path + "로 업로드합니다.")
if args.command == 'download':
    print(args.source_path + "를 다운로드합니다.")

  이때 만약 인자로 설정된 항목중에서 필수로 지정된 항목이 인자로 넘어오지 않거나 형식이 틀렸다면 다음처럼 오류가 발생하며 프로그램은 강제 종료됩니다.

usage: test.py [-h] --command {download,upload} source_path [target_path]
test.py: error: argument --command/-c: expected one argument

  그런데 여기서 좀 더 예외처리해야 할 부분이 있습니다. 사실 저희는 upload시에만 target_path를 필수로 만들고 싶었습니다만 이것을 깔끔하게 자동으로 처리할 방법은 찾지 못했습니다. 일단 직접 다음의 코드를 추가하여 예외처리 해줍니다. (혹시 더 깔끔하게 처리할 방법을 아신다면 알려주시면 감사하겠습니다.)

if args.command == 'upload':
    # upload일때는 target_path가 반드시 필요하다
    if args.target_path is None:
        print('The following options are required.')
        print('option: --c upload {source_path} {target_path}')
        return
    print(args.source_path + "를 " + args.target_path + "로 업로드합니다.")

if args.command == 'download':
    if args.target_path is not None:
        print('target_path is not required.')
        return

    print(args.source_path + "를 다운로드합니다.")

 

 

(실행 결과 1) python.exe test.py -c upload source.txt /target/

source.txt를 /target/로 업로드합니다.

 

(실행 결과 2) python.exe test.py -c download source.txt

source.txt를 다운로드합니다.

 

(실행 결과 3) python.exe test.py -c upload source.txt

The following options are required.
option: --c upload {source_path} {target_path}

 

(실행 결과 4) python.exe test.py -c download source.txt /target/

target_path is not required.

 

  이제야 원래 하려고 했던 간단한 예제가 모두 구현되었습니다. 추후 좀 더 나은 방법을 찾거나 수정될 부분이 있다면 추가로 수정하도록 하겠습니다. 완성된 예제코드는 하단에 '접은글'로 넣어두었습니다. 참고해 주시기 바랍니다.

예제코드

더보기
import argparse

class Main:
    def __init__(self):
        parser = argparse.ArgumentParser()
        parser.add_argument('--command', '-c', type=str, choices=['download','upload'], help='command', required=True)
        parser.add_argument('source_path', type=str)
        parser.add_argument('target_path', type=str, nargs='?')
        args = parser.parse_args()
        if args.command == 'upload':
            # upload일때는 target_path가 반드시 필요하다
            if args.target_path is None:
                print('The following options are required.')
                print('option: --c upload {source_path} {target_path}')
                return
            print(args.source_path + "를 " + args.target_path + "로 업로드합니다.")
        if args.command == 'download':
            if args.target_path is not None:
                print('target_path is not required.')
                return

            print(args.source_path + "를 다운로드합니다.")

if __name__ == '__main__':
    Main()