직전 IBM developerworks 기사에서, 파이썬으로 명령행 도구를 만드는 즐거움에 대해 기사를 썼다. 이 기사에서는 다음 단계로 플러그인을 만들어 명령행 도구를 확장하는 방법을 다룬다. 플러그인과 명령행 도구는 현존하는 코드 기능을 확장하는 편리한 방법을 제공한다. 플러그인을 명령행과 함께 사용하면 정말 강력한 도구가 될 수 있다.
플러그인 작성 방법을 시작하기 앞서, pathtool라는 이름의 오픈 소스 파이썬 패키지를 사용해야 한다. 이 패키지는 파일 시스템을 돌아다니며 파일 객체를 만들어내는 생성기를 사용한다. 이 라이브러리는 파일 객체에 뭔가를 적용하는 독자적인 필터를 작성해 결과를 반환하는 방법으로 개발자 쪽에서 확장이 가능하도록 만들어졌다.
실제 파이썬 모듈 코드 크기는 이 기사에서 보는 코드 크기보다 좀 더 크기 때문에, 실제로 사용하는 API 일부만 보여줄 계획이다.
Listing 1. pathtool API
def path(fullpath, pattern="*", action=(lambda rec: print_rec(rec))):
"""이 함수는 경로(fullpath), 셸 패턴(pattern), action 콜백을 받는다.
이 함수는 체크섬을 계산하기 위해 좀 더 늦은 pathattr 함수를 사용한다.
"""
for rec in pathattr(fullpath):
for new_record in match(pattern, rec): # 필터를 적용한다.
action(new_record) # 객체 생성을 위해 람다 콜백을 적용한다.
|
이 예제를 보면, path 함수가 옵션으로 pattern 키워드 인수, 람다 콜백이라는 action 인수와 함께 경로 위치 인수를 필수적으로 받는다는 사실을 확인할 수 있다. path를 위한 기본 콜백은 예제로 파일 이름만 출력한다. 개발자는 easy_install pathtool을 사용하기만 하면 된다. easy_install 명령을 사용하는 방법은 참고자료 절에 나와 있다. 모듈 import와 함수 호출은 다음과 같다.
from pathtool import path
path("/tmp", pattern="*.mp3", action=(lambda rec: print_rec(rec)))
|
참고: 편의상 pathtool을 위한 원시 코드를 이 기사에 포함시켰다. 이 예제를 소개하는 이유는 람다 활용 때문이다. 참고자료 절에서 파이썬 튜토리얼 항목에 나온 링크를 따라가면 람다에 대한 설명이 나온다. 간략하게 요약하자면, 람다는 함수에게 다른 함수를 "호출"하라고 알려주는 편리한 방법이다.
이제 콜백을 포함한 경로 탐색 라이브러리 활용법에 대한 일반적인 아이디어를 얻었으므로, 플러그인을 사용해 확장 가능한 명령행 도구를 실제로 작성할 시간이 다가왔다. 첫 번째 버전을 살펴본 다음에 좀 더 작은 조각으로 나눠 설명할 것이다.
Listing 2. 플러그인 방식으로 동작하는 명령행 도구
#!/usr/bin/env python
# encoding: utf-8
"""
pathtool-cli.py 0.1
파일 시스템을 돌아다니는 명령행 도구
plugin 디렉터리에서 Action plugins 콜백을 얻는다.
action=(lambda rec: print_rec(rec))
"""
from pathtool import path
import optparse
import re
import os
import sys
try:
plugin_available = True
from plugin import *
from plugin import __all__ # 등록된 플러그인 전체 목록을 얻기 위해
except ImportError:
plugin_available = False
def path_controller():
descriptionMessage = """
A command line tool for walking a filesystem.\
Takes callback 'Action' functions as plugins.\
example: pathtool_cli /tmp print_path_ext
"""
p = optparse.OptionParser(description=descriptionMessage,
prog='pathtool',
version='pathtool 0.1.1',
usage= '%prog [starting directory][action]')
p.add_option('--pattern', '-p',
help='Pattern Match Examples: *.txt, *.iso, music[0-5].mp3\
plain number defaults to * or match all. \
Uses UNIX standard wildcard syntax.',
default='*')
p.add_option('--list', '-l',
action="store_true",
help='lists available action plugins',
default=False)
options, arguments = p.parse_args()
if options.list:
try:
print "Action Plugins Available:"
if plugin_available:
for p in __all__:
print p
finally:
sys.exit(0)
if len(arguments) == 2:
fullpath = arguments[0]
try:
action_plugin = eval(arguments[1])
# 플러그인 작성자는 다음과 같은 이름 관례에 따라 메서드를 작성해야 한다.
#path(fullpath,options.pattern,action=(lambda rec: move_to_tmp.plugin(rec)))
path(fullpath, options.pattern,action=(lambda rec: action_plugin.plugin(rec)))
except NameError:
sys.stderr.write("Plugin Not Found")
sys.exit(1)
else:
print p.print_help()
def main():
path_controller()
if __name__ == '__main__':
main()
|
이 예제를 돌리면 다음과 같은 결과를 얻는다.
# python pathtool_cli.py
Usage: pathtool [starting directory][action]
A command line tool for walking a filesystem. Takes callback 'Action'
functions as plugins. example: pathtool_cli /tmp print_path_ext
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-p PATTERN, --pattern=PATTERN
Pattern Match Examples: *.txt, *.iso, music[0-5].mp3
plain number defaults to * or match all.
Uses UNIX standard wildcard syntax.
-l, --list lists available action plugins
|
명령어 결과를 보면 이 도구는 fullpath와 "action"을 요구한다. action은 개발자가 만든 플러그인이다. 명령행에 옵션 목록을 추가해 어떤 플러그인이 사용 가능한지를 보여주도록 만들었다. 출력 결과는 다음과 같다.
# python pathtool_cli.py -l Action Plugins Available: move_to_tmp print_file_path_ext |
도구가 동작하는 원리에 대해 잘 모르더라도, 이름만 봐서 어떤 동작을 할지 눈치챌 수 있다. 직전에 작성한 print_file_path_ext action는 file, path, ext를 출력한다. 실제 동작 결과는 다음과 같다.
# python pathtool_cli.py /tmp print_file_path_ext /tmp/foo0.txt | foo0.txt | .txt /tmp/foo1.txt | foo1.txt | .txt /tmp/foo10.txt | foo10.txt | .txt /tmp/foo2.txt | foo2.txt | .txt /tmp/foo3.txt | foo3.txt | .txt /tmp/foo4.txt | foo4.txt | .txt /tmp/foo5.txt | foo5.txt | .txt /tmp/foo6.txt | foo6.txt | .txt /tmp/foo7.txt | foo7.txt | .txt /tmp/foo8.txt | foo8.txt | .txt /tmp/foo9.txt | foo9.txt | .txt |
실행 전에 임시 파일을 touch foo{0..10}.txt로 만들었으며, 명령행 도구는 전체 경로, 파일 이름, 확장자를 "|" 문자로 구분해 출력한다.
지금까지 "마법의" 플러그인이 어디서 왔는지를 말해주지 않고 설명을 진행했다. 가장 먼저 살펴볼 내용은 모듈 최상단에 위치한 import 문이다. 다음과 같이 시도해보자.
plugin_available = True
from plugin import *
from plugin import __all__ # 등록된 플러그인 전체 목록을 얻기 위해
except ImportError:
plugin_available = False
|
import 문은 놀랄 만큼 간단한 플러그인 아키텍처를 설명하는 비밀의 열쇠를 제공한다. 공식 파이썬 문서는 일반적으로 "from package import *" 구문 사용을 권장하지 않지만, 플러그인을 작성할 때에는 이런 구문을 사용해도 좋다. 하지만 이럴 경우, 플러그인 작성자는 플러그인 디렉터리에 위치한 __init__.py에 항목을 생성할 책임이 있다.
"""Lists all of the importable plugins""" __all__ = ["move_to_tmp", "print_file_path_ext"] |
"from package import *" 구문을 사용하면 패키지 내부에 속한 모든 모듈을 *로 import한다. 다음으로 실제 __all__ 목록을 import 해 사용자에게 사용 가능한 플러그인 목록을 보여주는 수단으로 활용한다. 마지막으로 조금 마법과 같은 내용을 소개하겠다. 명령행 도구는 어떤 플러그인 action을 사용할지 실행 전까지 알지 못하므로 eval을 활용해 명령행에서 action 문자열을 호출 가능한 함수로 변환한다. 마법의 행은 다음과 같다.
action_plugin = eval(arguments[1]) |
일반적으로 eval을 사용할 때 아주 조심해야 하지만, 플러그인 메서드를 활용하기 위한 목적으로 제격이다.
이제 플러그인 아키텍처 동작 원리를 이해했으므로, 실제 플러그인을 살펴보자. 플러그인 아키텍처가 동작하려면 파이썬 사이트 패키지 디렉터리나 현재 작업 디렉터리에 플러그인 디렉터리가 위치해야 한다. print_file_path.py라는 플러그인에는 plugin이라는 메서드가 있다. 이게 바로 플러그인 개발자가 반드시 구현해야 하는 API다.
Listing 3. 예제 plug-in
#!/usr/bin/env python
# encoding: utf-8
"""
prints path, name, ext, plugin
"""
def plugin(rec, verbose=True):
"""Moves matched files to tmp directory"""
path = rec["path"]
filename = rec["filename"]
ext = rec["ext"]
print "%s | %s | %s" % (path, filename, ext)
|
이 plugin은 아주 단순한 함수지만, pathtool 모듈이 생성하는 사전인 rec 매개변수를 받는다. 사전은 다음 API를 포함한다.
{"path": path, "filename": file, "ext": ext, "size": size,
"unique_id": unique_id, "mtime": mtime, "ctime": ctime}
|
이 예제에서, 호출될 매 시간마다 특정 파일 객체 값을 출력하도록 값을 인쇄하기 위해 사전 키를 활용한다. 플러그인 작성자는 파일 변환, 이름 변경, 파일 아카이브 등을 수행하는 다른 유용한 action을 작성할 수 있다.
이 기사는 파이썬에서 명령행 도구를 확장하기 위한 유용한 수단으로 적당하게 단순한 플러그인 아키텍처를 보여준다. 하지만 여기서 주목해야 할 몇 가지 사항이 있다. 먼저, 참고자료에 포함된 easy_install과 함께 구현하는 좀 더 복잡한 플러그인 시스템이 있다. 이 플러그인 시스템은 사용자가 특정 도구를 위한 플러그인을 정의하도록 진입 지점 생성을 허용한다. 다음으로 이 기사에서 소개한 명령행 작성 방식을 따르면 단지 한 가지 "action" 플러그인만 수용 가능하다. 무제한 "연결된" 콜백 action을 받아들이는 명령행 도구로 수정하는 구현 방식은 독자들에게 연습 문제로 남겨두겠다.
연결된 플러그인을 만들 때, 사용하는 API 본질을 고려해 설계를 해야 한다는 사실을 잠재적으로 깨달을 것이다. 이 기사에서는 결과를 생성하는 플러그인 코드 기초를 만들었다. 여기서 소개한 프로그램이 플러그인을 함께 연결하려면, 작업을 수행하는 동시에 사전 레코드를 뒤로 돌려야 한다. 이 기사가 명령행 도구 형식에 맞춰 각자 플러그인을 작성할 수 있는 영감을 불러일으켰기를 바란다.
| 설명 | 이름 | 크기 | 다운로드 방식 |
|---|---|---|---|
| 예제 CLI 플러그인 코드 | cli_plugin_code.zip | 15KB | HTTP |
교육
- Ian Bicking PyCon Plug-in Presentation: egg에 대한 내용을 살펴보자.
- Creating command-line tools in Python(developerWorks, 2008년 3월): 간단한 명령행 도구 생성 방법을 익히자.
- Plug-in: 위키백과에서 플러그인 관련 내용을 읽어보자.
- 파이어폭스 부가 기능: 멀티미디어 파일 재생이나 특수한 그래픽 형식을 보기 위한 특정 함수를 수행하도록 브라우저를 확장해보자.
- Setuptools를 활용한 동적 서비스 발견과 플러그인은 확장 가능한 응용/프레임워크에 "플러그인"하는 라이브러리 생성을 지원한다.
- 파이썬 람다 문은 함수 객체가 필요할 때 활용할 수 있다.
- Pathtool은 파일 시스템을 돌아다니는 효율적인 API다.
- AIX와 UNIX developerWorks 영역은 IBM® AIX® 시스템 관리에 대해 다양한 측면을 풍부하게 다루며, 유닉스 기술을 확장하는 데 도움을 준다.
- AIX와 UNIX 입문: 좀 더 많은 지식을 배우기 위해 이곳을 방문하자.
- developerWorks 기술 행사와 웹 캐스트: developerWorks 기술 행사와 웹 캐스트를 놓치지 말자.
- 포드캐스트: IBM 기술 전문가의 이야기를 들어보자.
제품 및 기술 얻기
- IBM 평가판 소프트웨어: 다음번 개발 프로젝트를 위해 developerWorks에서 직접 소프트웨어를 내려받아 활용하자.
토론
- AIX와 유닉스 포럼에 참여하자.

Noah Gift는 오라일리에서 나온 "Python For Unix and Linux" 공동 저자다. Gift는 저자이자 연사이자 컨설턴트이자 공동체 리더로 IBM developerWorks, Red Hat Magazine, 오라일리, MacTech에 기고한다. Gift가 운영하는 컨설팅 회사 웹 사이트는 www.giftcs.com이며, 개인 웹 사이트는 www.noahgift.com이다. Noah는 또한 아틀란타 파이썬 사용자 그룹 홈 페이지인 www.pyatl.org를 담당하는 현재 조직장이다. Gift는 칼 스테이트 로스 엔젤레스에서 CIS 석사 학위를 받았으며, 칼 폴리 산 루이스 오비스포에서 영양학 학사 학위를 받았다. 애플과 LPI 인증 시스템 관리자이며 칼텍, 디즈니 피처 애니메이션, 소니 이미지웍스, 터너 스튜디오에서 일한다. 남는 시간에는 부인 Leah, 아들 Liam과 함께 피아노를 연주하며 종교적인 수양을 하면서 보낸다.