Code Aquarium

minazoko's blog -*- 水底のブログ -*-

Clojure-Pyでファイルリード

●テキストファイルを読む

行単位で遅延リードしてテキスト処理。
ファイル終了判定は空文字列との一致判定。pythonのreadlineは行末に改行をくっつけたまま読み込むのでファイル終端でなけれな空行でも空文字列にはならない。

(defn repeatedly [f]
  (lazy-seq (cons (f) (repeatedly f))))

(defn file-content-seq [f]
  (->> (repeatedly #(.readline f))
       (take-while #(not= "" %))))

(defn proc-line [line]
  (format "[%s]" (.rstrip line)))

(defn main [filename]
  (with-open [f (py/open filename)]
    (doseq [line (file-content-seq f)]
      (println (proc-line line)))))

(main (first *command-line-args*)) 

repeatedlyがなくて「おや?」と思ったけど、untranslated.cljに入ってた。他にもかなり重要そうな基本フォームが入ってるけどそんなんで大丈夫なの?

CSVファイルを読む

Pythonは標準ライブラリにcsvモジュールが用意されています。

(require 'csv)

(defn iterable->seq [iterable]
  (lazy-seq
   (try (cons (.next iterable)
              (iterable->seq iterable))
        (catch py/StopIteration _ nil))))

(defn proc-row [row]
  (.join "<>" row))

(defn main [filename]
  (with-open [f (py/open filename)]
    (doseq [row (-> (csv/reader f) iterable->seq)]
      (println (proc-row row)))))

(main (first *command-line-args*)) 

Pythonのfor-eachループはnextメソッドで値を取得し、StopIterationという例外throwが終了を表す。このループ終了仕様はちょっと厄介かもしれない。Pythonでは構文で例外を隠しているのだろうけど、Clojure-Pyでは明示的に例外処理しなきゃならない。
.nextの度にtry..catchを仕掛けるというのはパフォーマンス的に大丈夫なのかな?
Clojure-Pyではジェネレータ(types.GeneratorType)はSeqableをextendしていてseq関数でシーケンス化できる。でもイテレータクラスは継承無しのダックタイピングなのでジェネリックには扱えないような? ...このへん、もうすこし調べたい。

Clojure-PyでWin32API

Clojureからネイティブライブラリ

Pythonにはctypesというネイティブライブラリをダイレクトに扱うモジュールがあります。ということはClojure-Pyを使うとClojureからネイティブが呼べるということになりますね。

;;-*- coding:utf-8 -*-
(ns sample-ctypes
  (:require ctypes)
  (:require [clojure.string :as string]))

;; convert to unicode string
(defn u$
  ([s codec] (py/unicode s codec))
  ([s] (u$ s "utf8")))

;; get attribute from python-object
(defmacro pyattr [obj a]
  `(py/getattr ~obj ~(name a)))

;; get attribute thread macro
(defmacro pyattr.. [obj & attrs]
  `(-> ~obj ~@(map (fn [a] `(pyattr ~a)) attrs)))

;; Sample user32 MessageBoxW
(let [title (u$ "【サンプル CLojure-Py】")
      message (u$ (string/join "\n\n"
                               ["clojure-py からの〜"
                                "ctypes.windll からの〜"
                                "MessageBoxW を表示"]))
      message-box (pyattr.. ctypes/windll user32 MessageBoxW)]
  (message-box 0 message title 0x40))

Python2.7で試しました。

Pythonオブジェクトのメンバへのアクセス

Clojure-PyのドキュメントではJVMと同様に

(.attrname obj)

でオブジェクトのメンバにアクセスできると書いてありますが何故かうまくいきませんでした 書き方が違いました、最後に追記しておきます。

(py/getattr obj "attrname")

でメンバにアクセスできることが分かりました。*1
したがってMessageBoxを使うには。

(let [user32 (py/getattr ctypes/windll "user32")
      message-box (py/getattr user32 "MessageBoxW")]
  (message-box ...))

のようにする必要がありますが、面倒なのでスレッドマクロにしています。

Clojureの新しいメンバ参照方式(追記)

TLで教えてもらいましたが、Clojure族はこれまでメンバ変数もメソッドも . で参照していましたが、メンバ変数については .- という新しい記法が導入されているそうです。というかReadMe.mdに書いてありました。*2

(.user32 ctypes/windll)

はダメでしたが、

(.-user32 ctypes/windll)

なら行けました。これならユーティリティマクロなど書かなくても。

(let [message-box (-> ctypes/windll
                      .-user32
                      .-MessageBoxW)]
  (message-box 0 (u$ "本文") (u$ "タイトル") 0x40))

あるいは少し強引に

(-> ctypes/windll
    .-user32
    .-MessageBoxW
    (apply [0 (u$ "本文") (u$ "タイトル") 0x40]))

と書けますね。スッキリした。

*1:Clojure-PyからPythonの組み込み関数を呼ぶ場合デフォルトで参照されているpy名前空間を使います。

*2:.-__name__ なんてアンダーバーだらけで見逃しちゃったじゃないですかぁ〜(言い訳)