3/26メモ

heroku

わかりやすい。

heroku 上で Rails アプリが動くところまでざっと見てみました。てか、heroku の前にまともに Rails の勉強しなきゃ。

ほほう。

vagrant

上の heroku の動画で vagrant を使っていて、docker や virtualbox はコマンドラインからガリガリ使っていたけど、vagrant は名前くらいしか知らなかったのでちょっとメモしておく。

うんうん、virtualbox のイメージも空から降ってくるべきですよね。

swig

sirenmruby から MRI に移植してるんだけど、どうせなら SWIG で OpenCASCADE ラッパーな ruby-occt を作ってゴニョゴニョすべきかなと思いつつ

を見てたんだけど、PythonOCC では大量の SWIG 用ラッパーファイルで頑張ってらっしゃる。*.i だけなら流用できるのかなー、でも python なんちゃらって文字があるぞ。めんどいなあ…。そもそも siren は OCCT の無駄に多いクラスや機能を適度にまとめて使えることが利点だから、フル機能の OCCT を使いたいんだったら PythonOCC やネイティブな OpenCASCADE を使えばいいと思ってたりして悩みどころです。

Kinect 360 買っちゃった

前々回と前回の OpenCV まわりに関連して、とうとう Kinect を購入しました。Kinect for Windows じゃなく 360 でもとりあえずは研究に使えるらしいし、ドライバや SDK が厳格そうな Windows で開発するわけでもない、360なら価格も中古で低価格程度ということで Amazon で5000円の中古をポチ。ヤフオクで2000円弱で出品されてるんですが、動作保証と電源ケーブル同梱版、手続きの早さで割高だけど Amazon のを購入しました。

月曜日(明日)届く予定です。楽しみ。Kinect の到着を待つ間、Python で書いていた(というかコピペしてた)ステレオマッチング処理を Ruby で書き直そうと思ってドキュメントを見てたら ruby-opencv には StereoSGBM()ない模様

こんな時に颯爽と実装してプルリクをぶん投げられる人になりたい…。

デジタルデバイス処分

PS3、楽天 KOBO、NEC LifeTouch NOTE、Lavie Tab W 708 など使ってないデバイスを処分中。ハンディカムや液タブも売り出そうか悩み中です。

OpenCVでステレオマッチング(ステレオカメラ)

前日より引き続き、OpenCV でステレオ画像による深度計算をやっています。 昨晩、スマホの単眼カメラで撮影した2枚のステレオ画像の深度計算で割と良さそうな結果が出たので、Amazonで安いWebカメラを二つ注文。早速届いたので遊んでみます。

環境

  • LMDE2 64bit
  • OpenCV2
  • Python2.7

カメラ

ロジクールの安いWebカメラを2台購入。購入時には1台1081円でした。田舎でも前日の深夜に注文した商品が翌日午後には届くのって素晴しすぎますね!引きこもって生きてゆけそう… _(:3」∠)_

こんな感じで、ディスプレイの上に二つならべて設置しました。

Webカメラは可動域が多いので直線状に並べて2つのレンズに角度がつかないようにするのが大変でした。

スクリプト

カメラを2台使用するので、昨晩のスクリプトは使わず、下記のページを参考にしました。

試行錯誤な日々: openCVを利用したステレオカメラの作り方 http://asukiaaa.blogspot.com/2016/07/opencv.html

動画

思っていたよりも滑らかなグラデーションがついていませんが、リアルタイムで処理できるのは楽しいですね。 もうちょっとパラメータの調整が必要なのかな。

OpenCVでステレオマッチング

かなりの今更感はさておき、OpenCV でステレオ画像による深度計算をやっています。

環境

  • LMDE2 64bit
  • OpenCV2
  • Python2.7

スクリプト

入力画像と深度計算の結果

cv2.StereoSGBM() のパラメータをいじくり回したり、単眼のスマホカメラで撮影した入力画像の位置合わせをしたりしていると、それなりに深度が出るようになりました。

PCD 化

PCL(Point Cloud Library)のフォーマットである PCD 形式に変換し、pcl_viewer で確認します。

スクリプトのPCD化部分次のとおり。


pcl_viewer で開いたところ。

数百万円するレーザー計測器には到底及ばないものの、それなりにカラーボックスやその上に置いている箱の形状が出てますね! もっとフワフワな点群になるかと思っていたので、ちょっと予想外でした。

参考

括弧を省略したLispコードのプロセッサーを書いてみました。

supoo(旧実装)

これまで業務でも趣味でも Lisp をガリガリ書くということはなく、せいぜい Emacs の設定ファイルを書くことにしか使ったことがありませんでした。 構文は非常にシンプルで、「これなら字句解析も構文解析も自分で書けそうだ。C と Lisp の勉強がてらやってみよう!」と思い立って書いたのが「supoo」です。

はい、考えが甘かったです。

lex や yacc を使わずにそれなりに動く実装を目指していたのですが、いつまで経ってもパーサ周りのエラーと例外処理に悩まされ続け、コードも複雑化してしまいました。C99ベースで正規表現ライブラリも使っていません。

なぜ既存の解析器を用いなかったかというと、単に lex や yacc ファイルの読み書きがあんまり出来なかったからというのも理由の一つですです。こちらが王道というのは薄々気付いていたので、過去にも何度も挑戦しましたが、その度に挫折…。これらを使った電卓レベルの入門ページはインターネットにたくさんあるんですが、それなりにプログラミング言語の形をしたものを作りたい場合はしっかりとマニュアルを読んで、実際に使用している他言語の実装を読まないとダメのようだーと思っていました。

streem

そういうのを書いたことも忘れて半年以上経った最近、まつもとゆきひろさんが一昨年の暮れあたりから開発している新プログラミング言語「streem」のコードを Github で見て、lex.lparse.yMakefile を眺めているうちに、自分でも書きたくなっていきました。ここ数年の一般的なツールや概念を用いて、何もないところから言語を組み立てていくところを見学することはとても勉強になります。

プログラミング言語を新しく作るところを見たいだけであれば、古い ML やら CVS やら漁れば色々と見れますが、その当時の歴史的な背景まで知っていなければ「なぜそういう実装になったのか」の経緯を本質的に知ることができないような気もしていました。今、リアルタイムで見るからこそ得るものもありそうです。

streem はパイプで連結してちょっとした処理をささっとこなすことができる「より良いシェル(スクリプト)」のようなものを目指しているのかな、と感じました。 パイプで繋げる考えをそのままにシェルスクリプトのようなものを進化させようとした Windows Power Shell を思い出しましたが、方針だけは評価できるものの、実際に出来上がったものは読みにくて書きにくい ガッカリ残念感の哀愁が漂っていたような印象を受けた覚えがあります1。 WPS と比較するのもアレなんですが、”顧客が本当に必要だったもの” は streem の方ですよね!

さて、そんな streem の lex.lparse.yMakefile をひっぱってきて、いろいろといじくり回しているうちに大体の記述は分かるようになってきました。まだパースだけで、式木の構築すらやっていないのですが、入れ子になった括弧を処理して、四則演算を行い、印字する程度のことはできました。

lex と yacc (正確にいうと flex と bison)を使ってなかった旧実装でも、関数定義式(defun)や制御式(if等)まで力技で実装していたので、それに比べたらまだまだラクガキレベルです。 ただ、やっぱり先人の知恵である解析器の便利さを実感することができました。

括弧

Clojure や Scheme を含む Lisp 系の言語は、S式を表現する際に括弧 () を多用します。Lisp 界隈のページを見ると、「括弧が書きづらさや読みにくさにつながって、初心者が寄りつかない」という記事をよく目にします。式木を表現したいだけであれば、Python のようにインデントでも可能ですので、そういう動きも以前からあったようです。

上記リンクでは、Scheme の ML のやりとりが紹介されています。自分も最初は、bison で () に加えてインデントによるネストをサポートしてみようかなと一瞬だけ考えましたが、電卓レベルと言えどちゃんと動いてる構文解析にこれから手を加えるのも億劫になるくらいなので、実用的に動いている実装のパーサから手を入れるのに及び腰になるのは理解できました。

ただ、コミュニティうんぬんの話は置いておいても、何十年も使い続けられている Lisp ですので時代ごとにこういう動きがあったと思いますが、未だに括弧を多用する構文が残っているということは、何か本質的な問題点があったのかもしれません。

supoo.rb

さて、そうは言ってもやっている事自体勉強&趣味なので、車輪の再発明上等でインデントでS式を書けるようにしてみました。

次のようなアプローチです。

  1. supoo はまだ電卓レベルなので parse.y を使いません。評価には GNU Common Lisp (clisp) を使います。
  2. 仮にちゃんと動いていても、yacc 構文では書きません。オレオレなインデント記法→ Lisp 記法を実現するフィルタとして実装します。

フィルタのスクリプト(supoo.rb)は次のようになりました。

中身はただのテキスト・プロセッサーです。要はインデントレベルを検知しながら、必要に応じて () を突っ込んでいるだけ。

次のように使います。

# 実行
ruby supoo.rb < indended-lisp.supoo | clisp -q -

# 通常の Lisp 構文に変換
ruby supoo.rb < indended-lisp.supoo > general-syntax.lisp

例えば、次のようなインデント記法のスクリプトがあったとします。

前置記法は変わらないですが、インデントによるS式のネスティングをしています。 必要以上に改行とインデントを入れなくても良いように、通常の () 構文も使えるようにしています2

このスクリプトを supoo.rb に食わせると

という Common Lisp なコードが印字されます。

clisp で実行すると

$ ruby supoo.rb < indended-lisp.supoo | clisp -q -
0 1 1 2 3
(1 . A)
--- TEST ---
1 2 3 4 5

のように正しく実行することができました。


  1. 結局 bash on Windows の提供という選択肢をとる Microsoft も最近では憎めないですが。 

  2. リストを各要素1行ずつに分けて書きたくないですよね。 

mrubyがやってきた!

OpenCASCADE の勉強のために、仕事とは関係ないところで、自分なりの簡易三次元 CAD ビューアを作成しています。
形状をパラメトリックに定義したり、対話的に制御したり、一連の処理をモジュール化する上で必要になってくるのがマクロ環境。マクロと言ってしまうと、使い勝手の悪いバッチ処理的なイメージがそこはかとなくありますが、強力なインタプリタ環境を組み込みたいと思いました。

アプリケーションを組む上でもトライアンドエラーのデバッグ効率は上がりますし、CAD ソフトにおける純粋な三次元幾何演算をハード的な部分であるとすれば、「ある決まった工業製品の組み立てモデルをシミュレーションする」といったより論理的な、ソフト的な役割のライブラリ化にも役立つと思っています。

さて、OpenCASCADE にはこのブログでも何回か書いたとおり、DRAW TEST HARNESS というコマンド・ドリヴンな対話型デバッグシェルが含まれています。単機能評価だけで言うと、Tk による最低限の UI とシェルっていう組み合わせは別に捨てたもんじゃないのですが、ユーザに提供するマクロ環境として Tcl/tk を選択するのはいささか時代遅れな感じは否めません。シェルスクリプトや Perl、AWK に馴染みのある人ならば、あんまり気にせずに使い込なせると思うんですが、本質とは違うところでの労力を強いるのは極力避けたいところでもあります。

対話的にも動作することができ、モジュール化しても軽快に動いてくれるインタプリタ環境を探していました。
最低限、

  • 基本的な計算(四則演算、数学関数の呼び出し)
  • 変数の保持と解釈
  • 条件式、繰り返し構文

を備えているものだったら何でも良いと思っていました。ほぼ全ての言語にあるようなものばかりですね。もちろん、書き易さやメンテナンスのやり易さも条件のひとつです。

さて、最初に思いついたのは Python です。Python のコーディングは、私自身、累計でも1万行も書いていないと思いますが、欧米製の大手 CAD ソフトでは Python をマクロ環境として組み込むものが増えてきているようです。フランスのパリに Open CASADE の勉強会に行った時に開発メンバーが見せてくれたデモでも、OpenCASCADE の C++ コードを Python から呼んでいたようです。また、2011年から OpenCASCADE のコミュニティ・エディションをメンテナンスしている Thomas Paviot 氏も PythonOCC という Python ラッパーのプロジェクトを立ち上げています。

大手 CAD や OpenCASCADE との連結でも実績のある Python ですが、組み込み関連の情報を漁っていると、 mruby というものを見つけました。

mruby とは、Ruby の開発者 Matz 氏が最近精力的に開発を進めている組み込み向けの Ruby 実装らしいです。本家 CRuby に比べて依存関係が少なく、小さな端末でも動きやすいよう設計されているようで、Vim 関連の記事でよく参考にさせていただいている mattn 氏頻繁にコミットされている事がブログからも伺えます。

私はガチガチの関数手続き型の頭と、シェルスクリプトのようなゆるゆるのシェル展開ありきの頭の2ビットでコーディングをしていたので、Ruby のような「ちょうど良い」言語には向いていないんだろうなーと食わず嫌いをしていて手をつけなかったような気がします。いや、嫌っていたワケではないのですが、あれ楽しい!これ楽しい!と目移りしている間に、いつの間にか、ちゃんとやってみる機会を逃したというのが正しいかもしれません。少なくとも、ちゃんとやりたい言語の一つであった事は間違いありません。

# 余談ですが、Lisp もちゃんと時間をとってやりたいです。Emacs の設定ファイルのために elisp をごちょごちょ書いたような記憶しかありせん。
# オライリーの「初めての Ruby(オーム社刊、Yasugi 著、2008年)」で紹介されている Ruby の系図に、Ruby が Lisp の考え方も引き継いでいるような図があったので楽しみ。

これは好機とばかりに、mruby を試してみることにしました。

Hello, mruby!

mruby を make するのは簡単でした。他所でも紹介されているように、git で clone して make するだけです。Ruby の環境をまともに触るのも初めてでしたので、初めて知ることがたくさんありました。

まず、Makefile は存在しているのですが、Makefile の中で呼ばれているのは ruby minirake というコマンド。make から minirake が呼ばれて、最終的に gcc がコンパイルする形になってます。コンパイルの動作を処理している minirake は全て Ruby で実装してあり、Ruby 初心者が最初に見た時は「?」マークの連続でした。(でも、それほど時間をかけずに読んでいけました。Ruby すごい!)

頭に mini と付いているのは、きっと mruby のための小さな仕組みであり、本家 CRuby では Rake という Ruby のための Make 環境があるんだろうな、とも理解できました。

Ruby には gem というモジュール管理機構があるようです。Perl で言う CPAN のようなものだと思いますが、rake ファイルにターゲットを追記しておくだけで新しい gem を作ることができました。本家 Perl と軽量目的の mruby を比べるとアレかもしれませんが、分かりやすさ・透明性と簡単さは驚きです。(これなら自分もできるかも!と思わせてくれるのにはテンションが上がります)

さてはて、自分のアプリケーションに組み込む場合は Windows 環境で動かさなければならないので、Windows 環境、とりわけ Visual Studio でビルドできるかというのは大きな問題でした。そして、結果から言うと杞憂でした。
Windows では TortoiseGit と MSYSGIT を使っていますので、Windows に Ruby を入れて PATH を通したあと、MSYS 環境に clone して make するだけでした。途中、bison がないぞって怒られたので、bison.exe を入れたらすんなりと mruby.exe(インタプリタ本体)、mirb.exe(mruby.exeに対話シェル機能を入れたもの)、mrbc.exe(Rubyコードをバイトコードにコンパイルするやつ) が出来てしまいました。これらバイナリは libmruby.a をスタティックリンクしているようで、スタンドアロンで動作可能っていう嬉しさ。さすが組み込み向けです。

コーディングをほとんどしていないのに、こんな側面から mruby がどんどん好きになってしまいます。

Visual Studio で

自分のアプリケーションを Windows 以外のプラットフォームでも提供するんだったら、Qt なんかでチャキっと組んだ方がいいと思うんですが、私は Qt を使ったことがありません。Qt はすごくやりたいのですが、それはまた別の機会として、当面の目標である Windows でそこそこ動くものを作らねばなりません。

そういう理由で、ユーザ・フロントエンドを C#、OpenCASCADE や mruby のラッパー DLL を C++/CLI で実装しよう(※)と思いました。ラッパー DLL も C++/CLI 依存の部分とネイティブ C++ 準拠の部分を分けてコーディングする事により、最悪、別のプラットフォームになった場合、ネイティブ C++ の部分だけ抜き出してビルドし直せばいいと思いました。MFC を使った COM 実装に比べれば、C++/CLI の制約なんて目をつぶる事ができそうですし、そんな高度な事もする予定がないので、当面はこれで問題がないはずです。

※Microsoft の公式推薦の C++/CLI 入門書籍でも、「C++マネージド拡張の失敗という前例もあるし、特に理由がなければ C++/CLI なんて使うな」と前書きに書いてありましたね。あれは声出して笑いました。

さて、さっそく MSYS環境で make した mruby.dll を Visual Studio で作成した C++/CLI の wmruby.exe (デバッグの為、とりあえずEXEを作成)からリンクしてみて実行しました。コンパイル、リンクとも正常に完了しているものの、実行時には 0xc0000007b というエラーが出て、実行できません。調べてみると、どうもバイナリイメージが CIL とネイティブの間で上手く取りつげていないようです。

ここは mruby ではなく、MSYS と、Windows の目紛しく仕組みが変わるバイナリの取り扱いの不一致だろうって事で、あまり深く調べずに考え方を変えました。

mruby.dll を make するために rake ファイルを読み漁ってた時に vs2010 や vs2012 といった、いかにも Visual Studio で mruby をビルドするオプションっぽいのを発見していました。

openssl とか、gem が他のオープンソースライブラリを使う時、Visual Studio ではなく MSYS 環境のほうが色々と苦労が少なそうだなーと思っていたので、あえて MSYS で libmruby.a を make していましたが、そこが問題になりそうだったら選択肢がありません。

vs2010 を使って、ビルドし直してみると、Visual Studio によって libmruby.a ではなく libmruby.lib が生成されました。加えて、MSYS でビルドしていた自前の mruby.dll も Visual Studio で C++/CLI として libmruby.lib にリンクして再ビルドすると、拍子抜けするほど上手く行きました。

libmruby.lib さえビルドできてしまったら、あとはこっちのもんですね!

IIJ 版

これまで触っていた本家の mruby では、require やシェルコマンド実行のクオートなど、Kernel でサポートしているものもほとんどありませんでした。開発途上という側面と、最低限のものを提供するというスタンスがあると思われます。

本家 mruby のほか、IIJ さんも精力的に mruby の開発に手を出しているようで、本家から folk したリポジトリになっています。mattn 氏のブログを拝読していたら、先程述べた require やクオート、正規表現など「モバイル端末などの局所的な組み込みには要らなかもしれない!でもPCで実行する対話的アプリのモジュールなら要るよね!」的な機能を IIJ 版にどんどん追加されていっている様子が伺えました。記事にもありましたが、CRuby にも遜色がないほどの基本機能を提供できるようになっているようです。本当に凄いし、夢が広がります!

という事で早速、IIJ 版を clone して make してみました。linux では何ごともなくすんなり。
./mirb して

[ruby]
ls.each_line do |item|
puts item
end
[/ruby]

すると、ちゃんと実行できました。楽しい。

さて、Windows 環境ではどうかと言うと、ソケットを扱ってそうな gem があったり、コードを見てみると Windows 向けのコーディングがされていなかったりと、半ば予想はしていましたが、Visual Studio でビルドしようとしても失敗しました。当たり前だよネ。

Visual Studio っていう巨大なイレギュラー環境が立ちふさがって、作業がストップしてしまいましたが、丹念に乗り越えなければならない道だろうなぁと思います。今はビルド方法をまとめるくらいしか能力がないですが、Ruby をガリガリ使えるようになって、実装の事も理解できるようになったら issue 飛ばしたり、コーディング面でも協力できるようになれればなあ、と思いました。

Bash, Perl, Ruby, Pythonで正規表現置換

前回のC++/boost.NETに加え、各種インタプリタ言語でも同じ動作をするスクリプトを書いてみました。

シェルスクリプト(bash)

シェルスクリプトはそもそもグルー言語ですので、他のコマンドを呼び出して処理をすることが一般的です。下のサンプルではsedコマンドで置換処理をしています。bashだと正規表現マッチングは可能なので、ガリガリとスクリプトを書けばsedを使わずに実現できるかもしれません。

[bash]

!/usr/bin/env bash

[ $# -ne 2 ] && exit 1
cat | sed -e "s/$1/$2/g"
exit 0
[/bash]

リプレイスメント置換文字は、sedの書式になります。グループ指示子はダラー($)ではなくバックスラッシュ(\)を用いています。

Perl

Perlの場合、正規表現による文字列操作は、関数でもクラスでもなく構文として組み込まれています。その為、コマンドライン引数から渡された「$1」のようなリプレイスメントの展開方法に、若干の工夫が必要です。

[perl]

!/usr/bin/env perl

exit 1 if ($#ARGV != 1);
for (<stdin>) {
eval "s/$ARGV[0]/$ARGV[1]/g" && print;

s/$ARGV[0]/$ARGV[1]/gee && print; # これではダメ

}
exit 0;
[/perl]

リプレイスメントが格納されている$ARGV[1]は展開されると、例えば「$1$2」という文字列になります。この展開後の文字列をリプレイスメントとして正規表現置換の処理に投げたいのですが、正規表現置換処理を行なった後に変数展開されるようでリプレイスメントのグループ指示子としての$nが正規表現置換処理に伝わりません。そこで、正規表現置換処理を行う前に$ARGV[1]を展開させるべく、eval関数に投げています。これにより、正規表現置換処理が評価される時点でリプレイスメントは「$ARGV[1]」ではなく「$1$2」という文字列として解釈され、指示子が正しく伝わるようです。
検索パターンに加え、リプレイスメント文字列も変数で持つという処理は多々あるはずなので、もっとスマートな方法が準備されているのかもしれませんが、性質さえ知っていれば公式を知らずとも期待する処理が可能である、まさに”TMTOWTDI / There’s More Than One Way To Do It(やり方はひとつじゃない)”という設計思想を持つPerlらしい実装です。理に適った挙動は見ていて気持ちがいいです。

Python

Pythonでは、シェルスクリプトやPerlと違って正規表現機能はクラス(re)として提供されています。下のサンプルではreの静的関数を叩いていますが、プリコンパイルしたオブジェクトとしても利用できたと思います。

[python]

!/usr/bin/env python

import sys
import re
v = sys.argv
if len(v) != 3: quit(1)
for line in sys.stdin:
print re.sub(v[1], v[2], line),
quit(0)
[/python]

リプレイスメントのグループ指示子は、sedと同じくバックスラッシュです。Cやシェルライクなエスケープ文字という捉え方をするなら、バックスラッシュがしっくり来ますね。(といっても、ダラーの場合でも同じく「シェルライク」ですけど :D )

ちなみに、len() がオブジェクトのメソッドではなく独立した関数になっているのは、評価対象(上の例の場合は v )が null オブジェクトでも、null チェックなしで利用できるようにする為だとどこかで読みました。Python の場合は空文字列(len=0)は null オブジェクトになるんですね。

Ruby

Rubyの場合は、シェルスクリプトを除いた他のどの言語よりもマニアックな感じがしますが、よく見てみると一番「ナチュラル」に理解し易い構文です。これだけの構文で比較するのはアレかもしれませんが、これだけの構文だけでここまで革新的な要素をたくさん見てとれるのは、rubyだからこそと言った感じでしょうか。ファイルディスクリプタまでオブジェクトであり、イテレータをせおっているあたり、カワイイです。

[ruby]

!/usr/bin/env ruby

if ARGV.size != 2 then exit 1 end
STDIN.each_line do |line|
puts line.gsub(/#{ARGV[0]}/, ARGV[1])
end
exit 0
[/ruby]

Rubyの何十倍もPythonのコードは書いていますが、Pythonは「VB.NETよりも使いものになる、綺麗なVB.NET的な何か」というイメージが拭えません。いいところ、悪いところの両方を知った上で使いこなせていけたらいいなあ、と思いました。

See also

PyGame で遊ぶ

PyGameとは、Python用のSDLラッパで「Pythonを使って比較的簡単にゲームが作れるよー」といったライブラリです。自前のIRCモジュールと組み合わせて、キャラクターの操作が可能なアバターチャット的な偽オンラインゲームなど、ガリガリ書いてみました。

今日はとりあえず、メモ代わりにPyGameを用いたスクリプトの基本形を貼っつけておきます。

[python]

!/usr/bin/env python

– coding: utf-8 –

import pygame
from pygame.locals import *

if name == "main":

pygame.init()
pygame.mouse.set_visible( True )
scr = pygame.display.set_mode( (640, 480) )
pygame.display.set_caption( 'test' )
c = pygame.time.Clock()

while True:
    c.tick( 60 )

    scr.fill( [ 63, 127, 127 ] )
    pygame.display.update()

    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        if event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                sys.exit()

[/python]

特に解説するところもないですね。読んだままの意味です。

参考リンク

Python で IRC クライアント的な

基本

無駄がなくて綺麗なコード。やっぱり Python って素敵。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket, string

# ソケット作成、接続、ログイン
irc = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
irc.connect( ( 'irc.example.com', 6667 ) )
irc.send( '''
    USER arianne * * arianne
   NICK arianne
   JOIN #hogefuga
''' )

while True:
   # recv() で、受信するまでブロッキング
   buf = unicode( irc.recv( 1024 ).strip(), 'utf-8' )
   # コマンドプロンプトの場合は、'cp932' を指定
   print buf.encode( 'utf-8' )
   # IRC サーバから PING が来たら PONG を自動応答する
   if string.split( buf )[0] == "PING":
      irc.send( "PONG " + string.split( buf )[1] + "\n" )
   # 文字列に反応してみたり
   if buf.find( u"こんにちは" ) > -1:
      irc.send( u"PRIVMSG #hogefuga :こんにちわぁ\n".encode( 'utf-8' ) )
モジュール化

車輪の再発明だと思いつつ、自分の使い易い形のクラスにしてみた。サーバー応答対応やエラーと例外処理が未実装だけれど「スレッド化した受信監視→外部から指定されたイベントハンドラ実行」は、これで・・・いけるはず・・・。もうちょっとスマートに書けるようになったらいいな。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import socket, string, threading
import logging

class ircc ( object ):
   """The IRC Client Class"""
   def __init__( self, hostname, port, server_encoding='utf-8',         terminal_encoding='utf-8' ):
      # ソケット作成
      self.server = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
      self.server.connect( ( hostname, port ) )
      self.server_encoding = server_encoding
      self.terminal_encoding = terminal_encoding
      script_encoding   = 'utf-8'
      logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s' )
   def __str__( self ):
      return 'ircc'
   def send( self, message ):
      # 渡された文字列をサーバに送信するだけ
      message = unicode( message, self.script_encoding )
      logging.debug( ">>> " + message.encode( self.terminal_encoding ) )
      self.server.send( message.encode( self.server_encoding ) + "\n")
   def recv( self, receive_size = 1024 ):
      # サーバから文字列を受信するまで待機
      buf = unicode( self.server.recv( receive_size ).strip(), self.server_encoding )
      for line in string.split( buf, '\n' ):
         logging.debug( "<<< " + line.encode( self.terminal_encoding ) )
      return buf
   # こういう細々とした IRC コマンドは、はたして個別に実装する必要があるのかニャー
   def quit( self, message = '' ):
      self.send( 'QUIT :' + message )
   def nick( self, nickname ):
      self.send( 'NICK ' + nickname )
   def join( self, channel ):
      self.send( 'JOIN ' + channel )
   def privmsg( self, to, message ):
      self.send( 'PRIVMSG ' + to + ' ' + ' :' + message )
   # ログイン・フェイズはエラー処理が重要になってくるので、メソッド化しておく。
   def login( self, loginname, nickname, description, wait_for_motd = True ):
      self.send( 'USER %s * * :%s' % ( loginname, description ) )
      self.nick( nickname )
      #if self.recv().find( '433' ):
      #    self.quit()
      #    return 1
      while wait_for_motd:
         if self.recv().find( ' 376 ' ):
            break
   # イベントハンドラ、send()、recv() それぞれのアドレスを渡してスレッド化
   def set_event( self, event_handler ):
      _irc_recv_thread( event_handler, self.send, self.recv )

class _irc_recv_thread( threading.Thread ):
   """ メッセージイベントを検知し、外部から定義された
        イベントハンドラを呼ぶスレッドオブジェクト """
   def __init__( self, event_handler, send_handler, receive_handler ):
      self.__event   = event_handler
      self.__send    = send_handler
      self.__receive = receive_handler
      self.__msg     = ''
      threading.Thread.__init__( self )
      self.setDaemon( True )
      self.start()
   def run( self ):
      while True:
         # メインループ
         self.__msg = self.__receive()
         if len( self.__msg ) > 0:
             for line in string.split( self.__msg, '\n' ):
                 if string.split( line )[0] == 'PING':
                     self.__send( 'PONG ' + string.split( line )[1] )
                     self.__event( line )
                 else:
                     break

使うのは、以下のようなイメージで。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import ircc

# イベントハンドラ
def check_message( msg ):
    if msg.find( u"こん" ) > -1:
        irc.privmsg( '#hogefuga', 'こん' )
    irc = ircc.ircc( 'irc.example.com', 6667, 'utf-8' )
    irc.login( 'arianne', 'arianne', 'Arianne the BOT', False )
    irc.join( '#hogefuga' )
    # イベントハンドラを登録
    irc.set_event( check_message )
    while True:
        pass # 何か他の事でもやる

んー、クラス化するメリットが少ないかも。日々使ってないとすぐ忘れるタチなので、wxPython/PyGame なんかと一緒に IRC プロトコルを使った通信将棋やチェスでも書きたいなぁ。

※ 2020/07/02 度重なるブログ移転・ブログシステムのアップデートにより崩れた記事を校正。Python なのにインデントがすべて吹き飛んでいるので間違いがあるかも…。