본문 바로가기

개발

파이썬으로 만드는 나만의 커맨드라인 프로그램 #2 - click

반응형

지난 시간에는 argparse모듈을 활용해 커맨드 라인 프로그램을 만드는 법에 대해 알아봤습니다. 이번에는 click모듈을 사용하여 커맨드 라인 프로그램을 만드는 법에 대해 소개하겠습니다.

click 모듈

argparse뿐만이 아니라 click모듈도 많이 사용됩니다. click모듈은 argparse에서 제공하는 기능을 보다 쉽고, decorator를 사용해 보다 깔끔하게 사용할 수 있게 해줍니다. Flask에서 CLI(Command Line Interface)를 구현하기 위해 사용한 모듈이기도 합니다.

click을 사용하여 argparse와 똑같은 코드를 작성해보겠습니다. 이번엔 프로젝트명을 hello 대신 cello를 사용하겠습니다.

# cello/main.py
import click
import sys


def say_hello(name):
    # print와 같지만, python2역시 지원하기 위해 사용하는 click.echo
    click.echo("Hello, {}".format(name))


def say_goodbye(name):
    click.echo('Good bye, {}'.format(name))


# click 데코레이터
@click.option('--bye', is_flag=True, help="say good bye.")
@click.option('--hello', is_flag=True, help="say hello.")
@click.option('-v', '--version', is_flag=True, help="Show version of this program.")
@click.argument('name')
@click.command()
def main(name, version, hello, bye):

    if version:
        print('1.0.0')
        sys.exit()

    # 함수명과 인자명을 구분하기 위해 say_hello / say_goodbye로 함수명을 바꿈
    if bye:
        say_goodbye(name)
    else:
        say_hello(name)


if __name__ == "__main__":
    main()

제 기준으로는 argparse보다 가독성이 좋은 거 같습니다. (개인차가 있으리라 생각됩니다.)

위의 프로그램은 다음과 같은 명령어로 실행할 수 있습니다.

$ python main.py --help

>>
Usage: main.py [OPTIONS] NAME

Options:
  -v, --version  Show version of this program.
  --hello        say hello.
  --bye          say good bye.
  --help         Show this message and exit.

$ python main.py sjquant --hello
>> Hello, sjquant

$ python main.py sjquant --bye
>> Good bye, sjquant

역시 setup.py를 정의해주면 cello라는 명령어로 사용할 수 있습니다.

# setup.py
from setuptools import setup, find_packages

setup(
    name='cello',
    version='1.0.0',
    author='SJQuant',
    author_email='seonujang92@gmail.com',
    description='Greet someone',
    packages=find_packages(),
    entry_points={
        "console_scripts": [
            "cello = cello.main:main"
        ]
    },
    classifiers=[
        'Programming Language :: Python :: 3',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
    ],
)
$ python setup.py install
$ cello sjquant --bye
>> Good bye, sjquant

click.group

click.group을 사용하면 main함수 이외에도 cli 명령어를 정의해서 사용할 수 있습니다. 네이버 영어사전을 크롤링하는 간단한 프로그램을 만들어봤습니다. 코드가 좀 길게 느껴질 수도 있는데, 세부적인 내용은 스킵하고 click 데코레이터를 사용한 부분과 main함수부분만 읽으셔도 무방합니다.

# ndict/main.py

import click
import requests
from bs4 import BeautifulSoup

# -h로도 help를 볼 수 있게 help option 재정의
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])

# cli라는 group 생성


@click.group(context_settings=CONTEXT_SETTINGS)
def cli():
    """
    Simple CLI for crawling Naver Dictionary
    """
    pass


def search_meaining(word, url):
    """
    search meaning from given url

    Params
    ----------
    word: str
    url: str
    """
    res = requests.get(url)
    soup = BeautifulSoup(res.text, 'html.parser')
    try:
        word_box = soup.select('.word_num .list_e2')[0]
    except IndexError:
        import sys
        click.echo('"{}"에 대한 검색결과가 없습니다.'.format(word))
        sys.exit()

    info = word_box.find('dd').find('p')

    if info.find('span')['class'][0] == 'fnt_k05':
        # 한영
        word_class = None
    else:
        # 영한
        word_class = word_box.select('dd p .fnt_k09')[0].text

    meaning = word_box.select('dd p .fnt_k05')[0].text

    try:
        example = word_box.select('dd p .fnt_e07._ttsText')[0].text
    except IndexError:
        example = None

    click.echo('"{}"에 대한 검색결과'.format(word))
    click.echo('='*80)

    if word_class:
        click.echo('품사: {}'.format(word_class))
    click.echo('의미: {}'.format(meaning))

    if example:
        click.echo('예문: {}'.format(example))


def search_example(word, url):
    """
    search example from given url

    Params
    ----------
    word: str
    url: str
    """
    res = requests.get(url)
    soup = BeautifulSoup(res.text, 'html.parser')
    try:
        word_box = soup.select('.word_num .list_a')[0]
    except IndexError:
        import sys
        click.echo('"{}"에 대한 검색결과가 없습니다.'.format(word))
        sys.exit()

    examples_eng = word_box.select('.fnt_e09._ttsText')
    examples_kr = word_box.select('div.fnt_k10')

    click.echo('"{}"에 대한 검색결과'.format(word))
    click.echo('='*80)

    index = 1
    for eng, kr in zip(examples_eng, examples_kr):
        click.echo(index)
        click.echo(eng.text)
        click.echo(kr.text)
        index += 1


@click.command(help="This searches and returns meaning of a word from Naver Dictionary")
@click.argument('word')
@click.option('-e', '--english-only', is_flag=True, help="Show english version of meaning")
def meaning(word, english_only):
    """
    This searches and returns meaning of a word from Naver Dictionary

    Params
    ----------
    word: str
        word to search
    english_only: bool
        if True, search only english version of meaning
    """

    if english_only:
        url = 'https://endic.naver.com/search.nhn?sLn=kr&query={}&searchOption=all&isOnlyViewEE=Y'.format(
            word)
    else:
        url = "https://endic.naver.com/search.nhn?sLn=kr&searchOption=all&query={}".format(
            word)

    search_meaining(word, url)


@click.command(help="This searches and returns examples of a word from Naver Dictionary")
@click.argument('word')
def example(word):
    """
    This searches and returns examples of a word from Naver Dictionary

    Params
    ----------
    word: str
        word to search
    """
    url = "https://endic.naver.com/search.nhn?sLn=kr&searchOption=all&query={}".format(
        word)

    search_example(word, url)


def main():
    # cli 그룹에 meaning과 example 커맨드 추가
    cli.add_command(meaning)
    cli.add_command(example)
    cli()


if __name__ == "__main__":
    main()

setup.py는 다음과 같이 정의되어 있습니다.

# setup.py
from setuptools import setup, find_packages

setup(
    name='ndict',
    version='1.0.0',
    author='SJQuant',
    author_email='seonujang92@gmail.com',
    description='Simple CLI for crawling Naver Dictionary',
    packages=find_packages(),
    entry_points={
        "console_scripts": [
            "ndict = ndict.main:main"
        ]
    },
    classifiers=[
        'Programming Language :: Python :: 3',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
    ],
)

설치한 프로그램은 다음과 같이 사용할 수 있습니다.

$ ndict --help
>>
Usage: ndict meaning [OPTIONS] WORD

  This searches and returns meaning of a word from Naver Dictionary

Options:
  -e, --english-only  Show english version of meaning
  -h, --help          Show this message and exit.

E:\dev\click_tutorial\ndict>ndict --help
Usage: ndict [OPTIONS] COMMAND [ARGS]...

  Simple CLI for crawling Naver Dictionary

Options:
  -h, --help  Show this message and exit.

Commands:
  example  This searches and returns examples of a word from Naver...
  meaning  This searches and returns meaning of a word from Naver Dictionary
$ ndict meaning --help
>>
Usage: ndict [OPTIONS] COMMAND [ARGS]...

  Simple CLI for crawling Naver Dictionary

Options:
  -h, --help  Show this message and exit.

Commands:
  example  This searches and returns examples of a word from Naver...
  meaning  This searches and returns meaning of a word from Naver Dictionary

E:\dev\click_tutorial\ndict>ndict meaning --help
Usage: ndict meaning [OPTIONS] WORD

  This searches and returns meaning of a word from Naver Dictionary

Options:
  -e, --english-only  Show english version of meaning
  -h, --help          Show this message and exit.
$ ndict meaning program
>>
"program"에 대한 검색결과
================================================================================
품사: [명사]
의미: 프로그램
예문: Load the program into the computer.
$ ndict example program
>>
"program"에 대한 검색결과
================================================================================
1
Displays  program   information ,  version   number ,  and   copyright .

프로그램 정보, 버전 번호, 저작권을 표시합니다.

2
Tom   turned   in   to   documentary   program   for   his   report .

탐은 그의 리포트를 위해서 다큐멘터리 프로그램에 채널을 맞추었다.

3
This   program   is   allowed   for   children   above  8 years  old .

이 프로그램은 8세 이상의 어린이에게만 허용된다.

4
The   news   program   was   bitten   off .

뉴스 프로그램은 잘렸다.

5
This   program   is   sponsored   by   B   publishing   company .

이 프로그램은 B출판사 제공이었습니다

더 공부하기

click 문서를 살펴보시면 이번 튜토리얼에서 다루지 않은 다양한 사용법을 확인할 수 있습니다.

또한, click을 커맨드 라인 툴로 사용하고 있는 가장 유명한 파이썬 웹 프레임워크 중 하나인 Flaskcli.py (클릭)를 살펴보시는 것도 좋습니다.


나만을 위한 프로그램 / 나의 생산성을 위한 프로그램은 GUI(Graphical User Interface)보다 CLI(Command Line Interface)가 정답인 경우가 많습니다. 혹은 jupyter처럼 CLI를 GUI를 실행하기 위한 툴로서 사용하는 것도 좋은 거 같습니다. 이번 튜토리얼이 여러분의 생산성 향상에 도움이 되었으면 좋겠습니다. 감사합니다.

반응형