Pymacs

これは、README file for Pymacs: を調査した時のメモである。

Presentation:
What is Pymacs?:
Warning to Pymacs users:
History and references:
Installation:
Emacs Lisp structures and Python objects:
Opaque objects
Usage on the Emacs Lisp side
Special Emacs Lisp variables
Usage on the Python side
User interaction:
Debugging
The *Pymacs* buffer:
Example 2 ?- Yet another Gnus backend:
Example 2 ?- The problem:
Example 2 ?- Python side:
Example 2 ?- Emacs side:
Example 3 ?- The rebox tool:
Example 3 ?- The problem:
Example 3 ?- Python side:
Example 3 ?- Emacs side:

Presentation:

What is Pymacs?:

Pymacs は、emacs と Python の双方向通信を可能とする道具である。

Pymacs は、Python を emacs 拡張言語とすることを目指している。

elisp から、Python モジュールをロードすることができる。

また Python 関数から、emacs のサービスを使ったり、elisp オブジェクトを直接操作したりすることができる。

このソフトの目標は、 elisp からも Python からも自然に記述でき、デバッグが容易で、エラーをスマートに処理でき、両者に跨った再帰を許すことである。

Pymacs をインスコするのに、emacs や Python をコンパイルとかリンクしなおす必要はない。

Emacs は、子プロセスとして Pyhotn を起動し、Pymacs が、両者の通信をつかさどる。

http://pymacs.progiciels-bpi.ca/manual/ に HTML 版のマニュアルがある。

正式版は http://pymacs.progiciels-bpi.ca/archives/Pymacs.tar.gz で配布している。 (Debian Package もあるが、20030630版から更新されていない)

Warning to Pymacs users:

このマニュアルは、Pythonに関して深い知識があることを前提にしている。一方 elisp については、さして深い知識は求められない。

Pymacsに関する質問や議論は、専用のメーリングリストがないので、python-list@python.org に行なうこと。

プロジェクトはβで仕様は固まっていないので、今後の変更に後方互換性が確保される保証はない。 問題報告先は syver.enstad@asker.online.no 仕様に関する意見は python-list@python.org

History and references:

関連する先行する仕事としては Cedric AdjihさんのPyEmacsがある。

starve for ...
... を渇望する
superb
優秀な
ponder
じっくり考える

Installation:

Install the Pymacs proper:

debian的には、パッケージがあるので下のように行えばよい。

$ sudo aput-get -f install pymacs pymacs-elisp

正しく pymacs.el がインストールされたことの確認は、emacs で以下を実行して、エラーにならないことで確認する。

M-x load-library RET pymacs

pymacs.py が正しくインスコされたことの確認は、Python インタプリタを立ち上げて、以下のようにPymacs モジュールをインポートして行なう。

Python 2.4.4 (#2, Oct 20 2006, 00:23:25)
[GCC 4.1.2 20061015 (prerelease) (Debian 4.1.1-16.1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from Pymacs import lisp
>>>

pymacs-srevices が正しくインスコされたことの確認は次のように行なう。

% pymacs-services </dev/null
<24     (pymacs-version "0.22")
Protocol error: `>' expected.
ここで、(pymacs-version version)が出力され、つぎに "Protocol error : `>'expected." と表示されればよい。

パッケージと同時にインスコされる例である rebox が正しくインスコされたことは、shell から以下のコマンドを実行して、エラーがでないことで確認する。

% rebox </dev/null

Prepare your .emacs file:

debian 的には /etc/emacs/site-start.d/50pymacs-elisp.el:で、以下の設定がされている。
(setq load-path (cons (concat "/usr/share/"
                              (symbol-name flavor)
                              "/site-lisp/pymacs-elisp") load-path))

;; Pymacs

(autoload 'pymacs-load "pymacs" nil t)
(autoload 'pymacs-eval "pymacs" nil t)
(autoload 'pymacs-apply "pymacs")

※ここで、マニュアルには、更に、"(autoload 'pymacs-call "pymacs")"とするように記載されているが、 debian では、そういう設定はしない、調べてみると pymacs-call 関数もちゃんと見えるから無問題と判断

python の通常のモジュールサーチパス以外に pymacs 用の Python モジュールを設置する場合は、 .emacs で↓のように設定する。

;;(eval-after-load "pymacs"
;;  '(add-to-list 'pymacs-load-path "your-pymacs-directory"))

ここで Python の復習になるけれど、ローカルモジュールのパス指定は、環境変数 PYTHONPATH などで行なう。(.pth)

機能確認は次のように行なう

M-x pymacs-eval RET `2L*111` ;(バッククォート注意)で↓のように表示されたら正解。
"2596148429267413814265248164610048L"

ミニバッファで、下のようにして os モジュール読み込み

M-x pymacs-load RET os RET
上のように入力すると,ミニバッファから接頭辞を聞いてくる
Prefix? [os-]
RET を押してデフォールト接頭辞を選択した場合
M-: (os-getcwd)			; os.getcwd が実行される
これは Python 的には、↓のような操作に相当する
>>> import os
>>> os.getcwd()
'/home/amt/Etude/Python/Pymacs'

Porting Pymacs:

デベロッパは Python 1.5.2 でテストしている。
stant
曲芸

Caveats:

emacs20 や、さらに古い emacs では、pythonの辞書(連想リスト)に関連して、python側でメモリリークが発生する問題がある。

caveat :: 警告・注意

Emacs Lisp structures and Python objects:

Conversions:

elisp側から Python関数が呼ばれた時は、引数として与えられた elispなデータ構造は、Pythonのオブジェクトに変換される。

逆に、Pythonから elisp関数を呼んだ時には、引数として与えられた pythonのオブジェクトは、elispなデータ構造に変換される。

この変換では、通常 mutable なlisp構造は、mutable なPythonオブジェクト型に変換される。(elisp文字列については後述するように別扱いとなる)

逆に、Python側で mutable なオブジェクトは、lisp 側では imutable になることが多い。その結果として、elisp 側でのデータ操作が Python 側では反映されない、ということが起こる。

Pymacs は、 Emacs に対するサードバーティ拡張をサポートしない。(mule-ucs なんかもそうなんだろうなぁ)

Pymacs の目標は、ユーザが elisp に深いりせずに python 側で emacs を機能拡張できることにある。

Simple objects:

elisp -> python 変換表
elisp python
nil/() None
int number
char number
string narrow string※
[A-Za-z][A_Za-z0-9-]* lisp.''symbol''(-は_に置換)
その他のシンボル lisp[''string'']

python->elisp 変換表
python elisp
[](Empty list) nil
number ※1 number
非unicode string ※2 string

Python 側での elisp シンボルの参照

lisp.[symbol]
英字ではじまり英数字およびハイフンからなる名前
lisp[string]
「英字ではじまり英数字およびハイフンからなる名前」以外の名前

Sequences:

elisp -> python 変換表
elisp python
nilでもコンスセルでもない最後のセルの cdr が nil になる普通のリスト Opaque Object※1
() None
vector Opaque※2

python->elisp 変換表
python elisp
List List
tuple ベクタ

※1 pymacs-forget-mutability が t なら、elisp list -> Python list

※2 pymacs-forget-mutability が t なら、elisp vector -> Python tuple

※3 elisp<->python双方向で ()は、[]に変換されることに注意

リストの変換について

なぜ、こういう仕様にしたか

Python では、tuple への参照も リストへの参照も O(1)

lelisp では ベクタへの参照は O(1) だけれど リストへの参照は O(N)

parsimoniously
倹約する
spare
スペア、予備品

Opaque objects

Emacs Lisp handles:

elisp 側から Python関数をコールした時、すでに関数引数は、elisp->Python 変換された後で、返り値もまた python -> emacs 変換される。(つまり emacs側からは、 p->e変換後のものしか見えない、ということになる)

elispなデータ構造でPyton非互換なもの(例えば、バッファとかウインドウとか)が渡されるときは、emacs lisp ハンドル が生成される。

elisp ハンドルが、Python 関数の返り値として elisp に渡されたり、Pyton から呼ばれる elisp 関数の引数として elisp 関数に渡された時、 elisp ハンドラは、 elisp のオブジェクトに戻る。

emacs lisp ハンドラ は Python 側では 内部 Lisp ハンドラクラス(あるいはそのサブクラス)のインスタンスである

今 object が elispハンドルで、そのもとが elispの列だったら、↓のようにしてPython側で elisp handle経由で直接 elisp なデータを操作できる。

object[index] 			;列の要素の参照
object[index] = value		;列の要素の置換
len(object)			;列の要素数

object が emacs 関数だったら、object(arguments)で、その関数を argumentsを引数にして呼びだすことができることがある(may)。

elisp handle.value()メソッド
object 自身を返す
elisp handel.copy()メソッド
object のコピーを返す
elisp の list
Python のリスト
elisp のベクタ
Python の tuple
elisp handle.str()メソッド
elisp handle の emacs lisp 表現な文字列を返す
elisp handel.repr()メソッド
elisp オブジェクト の python 表現な文字列を返す

Python handles:

python handle は elisp側で、Python オブジェクトを操作するためのツール

long 型や 複素数型や、ユニコード文字列やらモジュールやらクラスなど、elispにない pythonのデータを扱う。

そういうものを elisp 側で扱う時には、Python ハンドルをつかう。

Python ハンドルは、Python 側に復帰した時は、元のオブジェクトになる。

↓にインスタンスメソッドを elisp の名前に代入して、それを python 側で使う例を示す。

(setq matcher (pymacs-eval "re.compile('pattern').match"))
(pymacs-call matcher argument)

上の setq() は、分解して次のように書くことができる。

(setq compiled (pymacs-eval "re.compile('pattern')")
 matcher (pymacs-call "getattr" compiled "match"))

Usage on the Emacs Lisp side

pymacs-eval:

pymacs-eval()
(pymacs-eval text)

pymacs-call:

pymacs-call
(pymacs-call function [argument...])

例えば前項の例の

(pymacs-call matcher argument)
は python 的に書けば、↓のようになる
>>> matcher(augument)

pymacs-apply:

pymacs-apply
(pymacs-apply function arguments-liset)

pymacs-load:

pymacs-load
(pymacs-load module [prefix] [noerror])

トランポリン関数が call された後で引数は Python 側向きに変換され、返り値は、 elisp 用に変換されてから返る。

引数の一貫性は、Python 側でチェックされる。(interactive の引数は、例外的に elisp 側でチェックされる。)

今のところ(Python側の)キーワード付き引数を、elisp 側渡せるようにする予定がない? (この文の意味がよく判らん)

Expected usage:

Pymacs 開発者は、pymacs-eval, pymacs-call , pymacs-apply が頻繁に呼ばれるような運用を想定していない。 想定しているのは、pymacs-load で python モジュールがロードされ、その中の関数のトランポリン関数が通常の elisp関数と同じように使われるような運用を想定している。

Special Emacs Lisp variables

以下の変数の値で、Pymacs の動作を制御できる。

pymacs-load-path 以外のものは、実行時にいつでも値を変更してよい

pymacs-load-path は、任意の Pymacs 関数が呼ばれる前にセットしなくてはいけない

pymacs-load-path:

pymacs専用のpythonモジュールロードパスを格納

~も使える

動作

pymacs-trace-transit:

*Pymacs* バッファに、emacs-python間のトランザクションが保持される

pymacs-trace-transit の値による動作の違い

nil
最後の双方向トランザクションが保持される。
t
すべてのトランザクションが保持される。(デバッグ向き)
コンスセル (keep . limit)
limit のバイト数を越た場合には、先頭から keep のバイト数を残して捨てられる。
デフォールト設定
(5000 . 30000)

pymacs-forget-mutability:

デフォールト動作では、elispオブジェクトは、python側で自由に変更できる。

pymacs-forget-mutabilityが non-nil だと pymacs は python側に コピーを渡す。

通常はデフォールト値の nil のままを推奨。

Python -> emacs 方向では、このような制御をする変数はない。

pymacs-mutable-strings:

elispの string は 可変(長さは変えれないがコンテンツは可変) で、pythonのそれは 不可変。

デフォールトで pymacs-mutable-string は nil

この変数が non-nilならば、elisp文字列は elispハンドルとして渡される。

この変数は、pymacs-forget-mutability がセットされていたら無視される。

Timeout variables:

Pymacs がコケても emacs 側で操作が可能なように タイムアウト値を定める

時間は秒単位で設定

pymacs-timeout-at-start
デフォールトは 30秒

このほか、pymacs-timeout-at-reply pymacs-timeout-at-lineの2変数があるが、いずれも変更する必要はまずない。

Usage on the Python side

Python setup:

emacs 側からの入力が nil、数値、文字列で、Python 側からの出力が None、数値、文字列、である範囲では、Python 側では特段の設定は必要ない。

※ユニコード文字列は、その限りでないことに注意

それ以上のことをするときは Python 側で、↓のようにしてやることが必要になる。

from Pymacs import lisp
さらに Let クラスを使うんだったら
from Pymacs import lisp, Let

Response mode:

Pymacフレームワークで、Pythonがemacs からリクエストを受けると、replyがあるまで、emacs は Pythonをリッスンしている

Emacs Lisp symbols:

python側の lispオブジェクトは、アトリビュートにelisp側のシンボルを格納する。(実行中に動的にアトリビュートを作ることができる)

lisp.nil または lisl["nil"]は、None と同じ

上述の nil を例外として、lisp.symbol と lisp[symbol]は、シンボルタイプの内部表現であるオブジェクトを格納する。

こうしたオブジェクトは、Python の変数に代入して、elisp シンボルとして使うことができる

quote =lisp.quote

Python側でelispシンボルの値を代入するには、↓のいずれかのようにする。

lisp.symbol = value
lisp[string] = value

Symbol クラスには value() メソッドと copy() メソッドがある。

Python側でelispシンボルの値を参照するには↓のようにする。

lisp.symbol.value()
elisp ハンドル
lisp.symbol.copy()
これは lisp.symbol.value().copy() と等価

シンボルが引数を伴なう、elisp 関数であるとき、これを Python 関数のように扱うこともできる。

Dynamic bindings:

グローバル名をローカルスコープで上書きする時には Let クラスを使い次のように行う

try :
    let = Let()
    let.push(transient_mark_mode=None)
    ... user code ...
finally :
    let.pop()

Let.push() は、任意個のキーワード引数をとり、キーワードが ローカル拘束変数になる

この、push/pop の対を入れ子にすることもできる。

Let インスタンスを、del したり None を代入したりして意図的に破壊すると、let.pop/push が自動的に実行されて、ローカルスコープがなくなる。だから try/finally で、pop を確実に行なう必要は実はない。

つまり上のコードは、下のように書いても同じことである。

let = Let(transient_mark_mode=None)
... user code ...

Let クラスには、この他にも elisp 側の save-* スペシャルフォーム(例えば save-current-buffer とか) に対応する、 push_*/pop_* 他のメソッドがある、

push_excursion()/pop_excursion() :: save-excursion() に相当 push_match_data()/save_match_data() :: save-match-data() に相当 push_restriction()/pop_restrinction() :: save-restriction() に相当 push_selected_window()/pop_selected_window() :: save-selected-window() 相当 push_window_excursion()/pop_window_excursion() :: save-window-excursion() 相当

便利なように let.push() および、let.push_* メソッドは Let のインスタンスを返す。

これにより次のように書くことができる

let = Let().push_excursion()
if True : # if let: と書くこともできる
... user code ...
del let

ここで if 文中に let = のスコープを置いているのは、スタイル的に let のスコープを はっきりさせるためのテクニックである。

最後の del は、関数の終りまでスコープが続くときは不要である。

Raw Emacs Lisp expressions:

↓のように、lisp("elisp プログラム文字列") で、Python側で elispを実行できる。
lisp("""
    ...
    possibly-long-sequence-of-lisp-expressions
    ...
    """)
elispの式の値が、e->p変換されてlisp()関数の返り値になる。

User interaction:

elisp は、interactive() を使うと、対話的に呼出した時には、ユーザに聞き、非対話的に呼出した時にはユーザには聞かない、ということが出来るが、Python には、そういう機能がないので、それを実現する、という話

※、そんなこと elisp 側でやればいいじゃん、と思うんだが

Python モジュールをロードした後で、(それを emacs 側に渡す前に?) Pymacs は、ロードされたモジュール中の関数群が、対話的に呼出されるものか(つまり emacs でいうコマンドとなっているか)を判定する。

モジュールロード直後に、Pymacs は、関数がインタラクティブかどうか判断する。

関数に interaction アトリビュートが定義されていれば、それはインタラクティブ

関数の interaction アトリビュートの値に、対話の仕様がセットされる

対話仕様は、Python の関数か文字列

↓は、interactive() に引数なしの例、interaction アトリビュートの値は空文字列

from Pymacs import lisp

def hello_world() :
    "`Hello world' from Python."
    lisp.insert("Hello from Python!")
    hello_world.interaction = '' # <-ここで(interactive)の引数にnilを与えている

Pythonの古いバージョンでは、ユーザが関数に、アトリビュートを追加できないので、interaction アトリビュートをユーザ定義できない。

代りに次のようにする。

関数と同じモジュールに、大域変数 interactions を定義し、その値を辞書にして、関数名をキーで、その値が None 以外なら、それはインタラクティブな関数になる。

上の例を書き直すと下のようになる。

from Pymacs import lisp
interactions = {}

def hello_world() :
    "`Hello world' from Python."
    lisp.insert("Hello from Python!")

interactions[hello_world] = ''

elisp のinteractive スペシャルフォームは関数じゃないから Python 側で lisp.interactive() と書くことは出来ない。

Keybindings:

Python側で、elispのキーバインドを設定する話。

下の例を参照のこと

lisp.global_set_key((lisp.f1,), lisp.module_helper)

※ ここで module-helper が俺の emacs にはないが、これは何

tuple (list.f1,) は、elisp 側ではベクタ [f1] になる

elisp の [M-f2] は python 側では (lisp.M_f2,) になる

Debugging

The *Pymacs* buffer:

elisp-pythonのインタラクションが *Pymacs* バッファに残るという話。
(pymacs-eval "lisp('(pymacs-eval \"`2L**111`\")')")
"2596148429267413814265248164610048L"

上の例に対して、 *Pymacs* に下のログが残る

>44	eval("lisp('(pymacs-eval \"`2L**111`\")')")
<34	(progn (pymacs-eval "`2L**111`"))
>18	eval("`2L**111`")
<53	(pymacs-reply "2596148429267413814265248164610048L")
>45	reply("2596148429267413814265248164610048L")
<53	(pymacs-reply "2596148429267413814265248164610048L")
<
Python -> elisp に送られる elisp な式
>
elisp -> Python に送られる Python な式

Python の評価は、Pymacs.pymacs モジュールのコンテキストで実行される。

つまり reply の名前は、グローバルには Pymacs.pymacs.reply

emacsa 側では、この名前は、pymacs- 接頭辞をつけて pymacs-reply になる

pymacs-reply() は、期待された結果が送られる時のスペシャル関数

erro と pymacs-error は同様に、例外を発生する時のスペシャル関数

pymacs-expand は、elisp ハンドラ/シンボルの copy()メソッドから呼ばれる特殊関数

この他、メモリ管理等のプロトコルが入る

読み型は必要になったときに読む

Emacs usual debugging:

エラー発生時に、Python のバックトレースがミニバッファに表示され、elisp のバックトレ0スが *Backtrace* バッファに表示される。

Python のバックトレースをみるために、ミニバッファの表示行を拡張するには、下のようにする。

(setq resize-mini-windows t
 max-mini-window-height .85)

ここで、emacs に同梱されている ffap(find-file-at-point) が役に立つ。

ffap は、/usr/share/emacs/21.4/lisp/ffap.el にある。

ffap を有効にしていると、ファイル名にカーソルを置いて C-x C-f RET で、そのファイルを開くことができる。

以下コメント抜粋

;;; Commentary:
;;
;; Command find-file-at-point replaces find-file.  With a prefix, it
;; behaves exactly like find-file.  Without a prefix, it first tries
;; to guess a default file or URL from the text around the point
;; (`ffap-require-prefix' swaps these behaviors).  This is useful for
;; following references in situations such as mail or news buffers,
;; README's, MANIFEST's, and so on.  Submit bugs or suggestions with
;; M-x ffap-bug.
;;
;; For the default installation, add this line to your .emacs file:
;;
;; (ffap-bindings)                      ; do default key bindings
;;
;; ffap-bindings makes the following global key bindings:
;;
;; C-x C-f       find-file-at-point (abbreviated as ffap)
;; C-x 4 f       ffap-other-window
;; C-x 5 f       ffap-other-frame
;; S-mouse-3     ffap-at-mouse
;; C-S-mouse-3   ffap-menu
;;
;; ffap-bindings also adds hooks to make the following local bindings
;; in vm, gnus, and rmail:
;;
;; M-l         ffap-next, or ffap-gnus-next in gnus (l == "link")
;; M-m         ffap-menu, or ffap-gnus-menu in gnus (m == "menu")
;;
;; If you do not like these bindings, modify the variable
;; `ffap-bindings', or write your own.
;;
;; If you use ange-ftp, browse-url, complete, efs, or w3, it is best
;; to load or autoload them before ffap.  If you use ff-paths, load it
;; afterwards.  Try apropos {C-h a ffap RET} to get a list of the many
;; option variables.  In particular, if ffap is slow, try these:
;;
;; (setq ffap-alist nil)                ; faster, dumber prompting
;; (setq ffap-machine-p-known 'accept)  ; no pinging
;; (setq ffap-url-regexp nil)           ; disable URL features in ffap
;;
;; ffap uses `browse-url' (if found, else `w3-fetch') to fetch URL's.
;; For a hairier `ffap-url-fetcher', try ffap-url.el (same ftp site).
;; Also, you can add `ffap-menu-rescan' to various hooks to fontify
;; the file and URL references within a buffer.

Auto-reloading on save:

Python 側に↓のように書いておくと、更新時にオートリロードする
# Local Variables :
# pymacs-auto-reload : t
# End :

↓を .emacs に追加

(defun fp-maybe-pymacs-reload ()
  (let ((pymacsdir (expand-file-name "~/Pymacs/")))
    (when (and (string-equal (file-name-directory buffer-file-name)
			     pymacsdir)
	       (string-match "\\.py\\'" buffer-file-name))
      (pymacs-load (substring buffer-file-name 0 -3)))))
(add-hook 'after-save-hook 'fp-maybe-pymacs-reload)
Examples * Example 1 ?- Paul Winkler s:

Example 1 ?- The problem:

問題 リージョンを以下の python関数で変換する キーバインドは、[f7]に割当てる
def break_on_whitespace(some_string) :
   words = some_string.split()
   return '\n'.join(words)
equilibrium
平衡

Example 1 ?- Python side:

# -*- coding: utf-8 -*-
from Pymacs import lisp
interactions = {}

def break_on_whitespace() :
    start, end = lisp.point(), lisp.mark(lisp.t)
    words = lisp.buffer_substring(start, end).split()
    lisp.delete_region(start, end)
    lisp.insert('\n'.join(words))

interactions[break_on_whitespace] = ''

Example 1 ?- Emacs side:

(pymacs-load "manglers")
(global-set-key [f7] 'manglers-break-on-whitespace)

リージョンに日本語が含まれていると、それが utf-8 であっても動かない。

文字列をバイト列に変換して交換する方法を 20061219 の日記 に示した。

Example 2 ?- Yet another Gnus backend:

Example 2 ?- The problem:

Example 2 ?- Python side:

Example 2 ?- Emacs side:

Example 3 ?- The rebox tool:

Example 3 ?- The problem:

Example 3 ?- Python side:

Example 3 ?- Emacs side:


連絡先:webadmin.itsumi@gmail.com このページは muse.el で作成しています。 Emacs