Chap #9January 02, 2013

프로그래밍하기: Python

7장 8장을 통해 컴파일형 언어인 C언어를 사용하여 프로그래밍하는 방법을 알아보았다. 특히 8장에서는 이맥스가 다른 프로세스와 통신하는 방법을 알아보았고, 이를 활용하여 man, cpp, gcc 와 이맥스가 어떻게 상호작용하는지 살펴보았다. 이번장에서는 인터프리터형 언어로 대표되는 Python 언어1를 어떻게 이맥스에서 사용하며, 점진적(incrementally)으로 개발하는 REPL (read-eval-print loop) 개발 방법론이 어떻게 적용되는지 살펴볼 것이다.

코드 작성하기

우리가 작성할 코드는 7장~8장에서 작성했던 시저암호화 프로그램이다. 이번장을 더 읽기 전에 직접 한번 구현해 보도록 하자.

#!/usr/bin/python

import os
import sys

def ceaser(plain, shift, prime):
    def _group(c):
        return chr((ord(c) - ord("a") + shift) % prime + ord("a"))
    return "".join(map(_group, plain))

if __init__ == "__main__":
    PRIME = 17
    SHIFT = 11

    if len(sys.argv) != 2:
        print("usage: %s [text]" % sys.argv[0])
        exit(1)

    txt = sys.argv[1]
    enc = ceaser(txt,  SHIFT, PRIME)
    dec = ceaser(enc, -SHIFT, PRIME)

    print("'%s' =enc->> '%s' =dec->> '%s'\n" % (txt, enc, dec))

코드를 작성하는 동안 적절한 코드 색깔 (syntax highlight)과 자동 들여 쓰기가 되었는가? 어떻게 사용자가 입력하는 언어에 해당하는 주모드를 자동으로 결정한것일까? 이맥스는 몇가지 힌트와 추측을 통하여 파일에 (파일에 해당하는 버퍼) 해당하는 주 모드를 결정한다. 첫번째는 파일의 확장자, C언어에서는 enc.c.c, enc.py.py를 보고 해당하는 주모드를 추측한다. C-h v: help variable을 통해서 auto-mode-alist: 자동 주모드 리스트 변수의 값을 살펴보자.

auto-mode-alist is a variable defined in `files.el'.
...
Value: (
 ...
 ("\\.cs$" . c++-mode)
 ("\\.CPP$" . c++-mode)
 ...
 ("\\.[ch]\\'" . c-mode)
 ...
 ("\\.py\\'" . python-mode)
 ...
 (".*/emacs-book/chap[0-9]+/.*.md$" . emacs-book-mode)
 (".*/linux-[0-9]\\.[0-9]+\\.[0-9]+*/.*\\.[ch]$" . linux-c-mode))

alist 타입의 auto-mode-alistcar은 파일 이름에 해당하는 정규식과 cdr의 해당 주 모드 심볼의 리스트를 담고 있다. 사용자가 파일을 열면 파일이름을 바탕으로 일치하는 주 모드를 찾는다. 즉 .c 파일은 c-mode.py파일은 python-mode로 주 모드가 설정되는 것이다.

재미있는것은 필자가 이멕스책 (emacs-book) 디렉토리의 각 챕터 디렉토리의 파일들을 열면 이맥스 책을 작성하기에 편한 모드로 설정하고, 리눅스 커널 (linux-[0-9]) 디렉토의 파일을 열면 리눅스 커널 소스를 작업하기에 적합한 모드로 설정하게 된다.

그러면 .py의 확장자를 갖지 않는 /usr/bin/pip파일을 열어 볼까? 이멕스가 어떻게 해당 파일이 Python으로 작성되었는지 결정한것일까? 두번째로 이맥스가 파일의 첫라인의 인터프리터 선언문을 보고 주어진 파일에 대당하는 주 모드를 결정한다.

#!/usr/bin/python

파일에 Python을 인터프리터로 선언한 경우, 이맥스는 auto-mode-interpreter-regexp: 인터프리터 선언 정규식 변수를 통해 해당하는 주모드를 추측한다.

auto-mode-interpreter-regexp is a variable defined in `files.el'.
Its value is "#![   ]?\\([^     \n]*/bin/env[   ]\\)?\\([^  \n]+\\)"
...

즉 "#!/usr/bin/env python" 과 "#!/usr/bin/python"은 모두 "python"을 키위드로 interpreter-mode-alist: 인터프리터 주모드 리스트 변수를 통해 앞서 살펴본 auto-mode-alist와 같은 방법으로 주 모드를 결정한다. 이 변수는 아래와 같은 값을 가지고 있다.

interpreter-mode-alist is a variable defined in `files.el'.
...
Value: (
 ("runhaskell" . haskell-mode)
 ...
 ("ruby" . ruby-mode)
 ...
 ("python" . python-mode)
 ...)

파일에 주모드가 결정되지 않으면 마지막으로 파일의 시그니처(file magic)를 바탕으로 주모드를 추측하는 magic-mode-alist: 내용 주모드 리스트하며, 주로 파일 앞부분에 시그니처가 분명한 'ps', 'xml', 'zip' 등의 파일들의 주모드가 이 리스트를 통해 결정된다.

도움말 호출하기

차 첫째 줄을 작성했으니 반은 이루었고, 다음 줄을 살펴보자. enc.py에서 두가지의 모듈, ossys을 사용한다.

import os
import sys

Python의 import 문은 어떠한 규칙을 가지고 있을까? 또 os 모듈은 어떠한 함수들을 제공하는가? 이를 알아보기 위해서는 각각의 문자열 위에서 C-c C-f: python-describe-symbol을 호출해 보자.

Help on module os:

NAME
    os - OS routines for Mac, NT, or Posix depending on what system we're on.

MODULE REFERENCE
    http://docs.python.org/3.2/library/os
...    

import문과, ossys 모듈은 정적으로 (문자적으로) 도움말을 찾아 보여줄 수 있다. 하지만 동적으로 코드의 의미가 결정되는 Python과 같은 해석형 언어의 경우 완벽하게 개발자의 요구를 충족시키지는 못한다. 이를 보안할 수 있는 Python을 정적으로 해석하여 오류 확인과 자동완성을 제공하는 pymacsropemacs 같은 프로젝트들이 있으니 관심있는 독자들은 참고하기 바란다. 또한 저자가 만든 pylookup도 Python의 Reference Manual을 이멕스를 떠나지 않고 쉽게 찾을 수 있게 도와 주는 기능을 제공한다.

Python 인터프리터 호출하기

이멕스를 떠나지 않고 Python의 인터프리터(대화형 쉘)를 호출 할 수 있다. C-c C-z: run-python을 실행하면 새로운 버퍼에 Python 프롬프트가 출력되었음을 볼 수 있을것이다. 새로운 버퍼로 이동하여 아래와 같이 입력해 볼까?

>>> def hello(): 
...     print("hello")
... 
>>> hello()
hello

첫인상은 Python을 직접호출한것과 크게 다른것이 없어 보인다. 하지만 이렇게 로드된 Python은 이멕스의 버퍼와 쉽게 인터렉션할 수 있다. 예를 들면 우리가 이멕스 리습(elisp)을 해석하기 위해 C-c M-x: eval-defun을 실행하듯이, Python에서는 C-c M-x: python-send-defun을 실행하여 버퍼의 함수정의를 Python 버퍼에 로드할 수 있다. 이러한 일관성은 ruby-mode, haskell-mode 등의 대부분읜 REPL 환경을 제공하는 모드에서 동일하게 유지된다.

자 그럼 작성된 ceaser 함수에 커서를 움직인후, C-M-x: python-send-defun를 실행하여 인터프리터에 로드해 보자.

def ceaser(plain, shift, prime):
    def _group(c):
        return chr((ord(c) - ord("a") + shift) % prime + ord("a"))
    return "".join(map(_group, plain))

로드된 함수가 Python 인터프리터 버퍼에서 실행가능한가?

>>> ceaser("abcd", 1, 3)
'bcab'

이멕스의 이러한 기능은 개발자가 작성한 함수를 테스트하는 주기를 단축시킬 뿐만아니라, 단순한 문법, 알고리즘을 쉽게 확인해 볼 수 있게 도와준다.

어떻게 일관적인 기능을 제공할 수 있었을까? 이멕스의 Python모드는 inferior-mode (또는 commit-mode)를 확장하여 inferior-python-mode를 제공하며, inferior-mode의 기능들을 통하여 Python 인터프리터와 커뮤니케이션한다.

  • C-M-x: python-send-defun: 커서에 위치한 함수를 인터프리터에 로드
  • C-c C-r: python-send-region: 선택된 영역의 파이선 코드를 인터프리터에 로드
  • C-c M-r: python-send-region-and-go: 로드후 해당 버퍼로 이동
  • C-c C-c: python-send-buffer: 전체 버퍼를 로드
  • C-c C-l: python-load-file: 특정파일을 로드

외부 프로그램 호출하기

Python은 스페이스 문자에 의미를 부여함으로써 반 강제적으로 개발자들이 코드의 일관적인 코드를 작성하게 한다. 그럼에도 불구하고 많은 개발자들이 같이 프로그래밍하게 되면 제각각의 코드가 작성되는데, Python은 일반적인 관행(규칙)을 PEP8로 정하고 모두가 지키도록 권한다. 이러한 툴들을 Beautifier (tidy, lint 등)라고 부르며, 특별히 Python은 pep8이라는 툴을 제공한다.

M-!: shell-command를 호출하고, "pep8 enc.py"를 직접 호출해 볼까? PEP8에서는 import문과 함수 정의 사이에 2개의 빈줄을 쓰기를 권한다는 출력결과를 확인할 수 있다.

enc.py:6:1: E302 expected 2 blank lines, found 1

7장에서 살펴보았듯이 이멕스는 컴파일러의 출력 결과를 파싱하여 오류가있는 구문들을 찾는데, 이를 재사용하여 pep8의 출력결과를 파싱해 보자.

M-x compile: 컴파일을 입력하고, "pep8 enc.py"를 입력해 볼까? 이제 익숙한 명령들을 M-g n: next-error, M-g p:previous-error을 이용해 쉽게 해당지점으로 이동이 가능하다. 저자는 위의 명령을 입력하는 대신 아래와 같이 간단한 코드를 작성하여 특정 키를 통하여 "pep8"을 호출 한다.

(defun pep8 ()
  (interactive)
  (compile (format "pep8 %s"
                   (file-name-nondirectory buffer-file-name))))

더 나아가 이멕스의 Python모드는 pycheckerpylintC-c C-v: python-check을 통하여 쉽게 호출 할 수 있으며, flymake-mode와 함께 코드의 중요 부분이 수정될때마다 자동으로 검사하게 할 수 있는데 이러한 부가적인 기능들은 관심있는 독자들에게 숙제로 남기도록 한다.


  1. 추상적인 인터프리터형, 컴파일러형 등의 언어적 특성의 분류는 사실상 특별한 의미가 없으며, 저자가 Python 류의 언어라고 하는것은 대화형 모드 (interactive shell)을 제공하는 언어들을 모두 일컫는다.

blog comments powered by Disqus