いつの間にかVisualStudio上でpythonが開発できることに今更気づきました。このところVSCodeでpythonのデバッグ環境を作っていたので、これなら本家のVisual Studioだってできるだろうと思いつき、調べてみたら2013くらいからできるようになったようです。
win10上でのpython環境は、Visual StudioではPython Toolsのパッケージをインストールしないとデバッグできませんが、pythonインタープリタ自体は自分で好きなものをインストールしろとのこと。runtimeには含めていないようです。配布を考えると、どっちが楽なんだろうか微妙なところですが、これまでwindowsではpythonはメジャーではなかったし、元々OS非依存で開発されていましたからこんな形になったんでしょう。
うちにある最新のwin10マシンにはVisual Stuido 2017 Communityが入れてありますが、真っ先にAnacondaが入れてあるため、そちらがデフォルトのpythonインタープリタになっています。もう一つのマシンには2015 Communityが入っていて、2013年に入れたおそらくpython.orgのpython27が入っていました。
しかしおかげで一つのマシン内に複数のpythonがインストールされている状態です。実際、Visual Studioの機能でd「python環境」というwindowがあり、そこでデバッグ/実行するときのpython環境を選べるようになっているくらいです。(いや、それCLIで実行するときはどうするんだよ、と突っ込みを入れたくなりますがそれも含めて考えてデバッグしろということでしょう。面倒くさい。)
この際、古いマシンの方のpythonをpython3にしようかと考えたんですが、ちょっとググってみたら、M$の推奨は悩んだらAnacondaが無難、Visual StudioのC++, C#と連携を考えるならIronPythonとのこと。調べると、IronPythonって、C#で実装されているとのこと。そのおかげで、Cythonもいけるらしい。しかしながら、python3がなくいまだに2.7というところが悲しいですが、ちょっと連携を試してみたいのでIronPythonを入れてみます。
Macもpythonは多数のバージョンがpyenvのおかげで入っていてひどいことになっています。python好きなんだけど、バージョン管理をなんとかして。(配布の時のハードルがかなり高いです)
2017年9月23日土曜日
2017年8月12日土曜日
Win10でPython
今まではPython使うときは、基本MacかLinuxでした。しかし、大抵の人はWindowsで動くプログラムを希望します。
仕方ないので、Windows10でのPython環境を構築することを考えて、anacondaで試してみました。特に地図表示が欲しいので、basemapパッケージをインストールしたいのですが、これまでwindowsだけは単にinstallコマンドだけではダメでした。しかし、最近やっと一発で行けるようになったようです。以下のコマンドでできました。
> conda install -c conda-forge basemap
> conda install -c conda-forge basemap-data-hires (地図データです)
参考にしたHPでは、basemapのバージョン指定をしていましたが、元のHPを見ると、バージョン番号は省いても最新のものを入れてくれるように今はなっていました。
ただ、このconda-forgeを使うと、conda自体を古いバージョンにしてしまうらしく、再度最新のものにしておかないと、今後の管理に支障をきたす恐れがあります。
> conda update conda
実際、自分が試した時もconda-forgeを使うと一つ古いバージョンに戻されていました。
さて実際に使ってみましたが(Python入門にあったロケットシミュレーションです)、なんかグラフの軸のラベルが文字化けして、□が出てくるだけです。(どうも日本語がダメです)おかしいと思い、半年くらい前に最初に試したMacで動かしてみると、packageのimportでエラーが出ます。どうもいくつか更新したものの相互依存関係が壊れているようなので、全部最新に更新します。それで動かしてみると、あれ?windowsと同じようにグラフの軸のラベルが文字化けしています。最初からこうだったけ?どうも何か問題が起きているようです。(どうも最新のmatplotlibのデフォルトフォントが日本語対応していないようです。plt.rcParams['font.family']='IPAexGothic'とフォントを指定していますが、システムにそれがない模様。)
ただ、windowsでもほぼ同様なpython環境が実現できているようです。
fontについてその後
少し調べてみましたが、以下のコマンドでシステムに登録されているTTFのフォントリストが得られます。
>> import matplotlib.font_manager
>> print([f.name for f in matplotlib.font_manager.fontManager.ttflist])
大量にフォントが出力されてきて、どれが日本語に対応しているのかわかりませんが以下で日本語が出力されました。
・win10
mpl.rcParams['font.family'] = 'HGMaruGothicMPRO'
・Mac
mpl.rcParams['font.family'] = 'AppleGothic'
(一部文字がまだ豆腐でした。Macは少し前にOSがSierraに変わったときにフォントが変わってしまったようです。)
conda-forgeとは
anacondaは元々、continuum社がパッケージをまとめたものですが、github社がその他のグループのリポジトリを集めて公開しているもののようです。そのため、より先進的なパッケージが集まっています。今回試したbasemap(windows版)が、anaconda.orgにはなくても、conda-forgeにはありました。
仕方ないので、Windows10でのPython環境を構築することを考えて、anacondaで試してみました。特に地図表示が欲しいので、basemapパッケージをインストールしたいのですが、これまでwindowsだけは単にinstallコマンドだけではダメでした。しかし、最近やっと一発で行けるようになったようです。以下のコマンドでできました。
> conda install -c conda-forge basemap
> conda install -c conda-forge basemap-data-hires (地図データです)
参考にしたHPでは、basemapのバージョン指定をしていましたが、元のHPを見ると、バージョン番号は省いても最新のものを入れてくれるように今はなっていました。
ただ、このconda-forgeを使うと、conda自体を古いバージョンにしてしまうらしく、再度最新のものにしておかないと、今後の管理に支障をきたす恐れがあります。
> conda update conda
実際、自分が試した時もconda-forgeを使うと一つ古いバージョンに戻されていました。
さて実際に使ってみましたが(Python入門にあったロケットシミュレーションです)、なんかグラフの軸のラベルが文字化けして、□が出てくるだけです。(どうも日本語がダメです)おかしいと思い、半年くらい前に最初に試したMacで動かしてみると、packageのimportでエラーが出ます。どうもいくつか更新したものの相互依存関係が壊れているようなので、全部最新に更新します。それで動かしてみると、あれ?windowsと同じようにグラフの軸のラベルが文字化けしています。最初からこうだったけ?どうも何か問題が起きているようです。(どうも最新のmatplotlibのデフォルトフォントが日本語対応していないようです。plt.rcParams['font.family']='IPAexGothic'とフォントを指定していますが、システムにそれがない模様。)
ただ、windowsでもほぼ同様なpython環境が実現できているようです。
fontについてその後
少し調べてみましたが、以下のコマンドでシステムに登録されているTTFのフォントリストが得られます。
>> import matplotlib.font_manager
>> print([f.name for f in matplotlib.font_manager.fontManager.ttflist])
大量にフォントが出力されてきて、どれが日本語に対応しているのかわかりませんが以下で日本語が出力されました。
・win10
mpl.rcParams['font.family'] = 'HGMaruGothicMPRO'
・Mac
mpl.rcParams['font.family'] = 'AppleGothic'
(一部文字がまだ豆腐でした。Macは少し前にOSがSierraに変わったときにフォントが変わってしまったようです。)
conda-forgeとは
anacondaは元々、continuum社がパッケージをまとめたものですが、github社がその他のグループのリポジトリを集めて公開しているもののようです。そのため、より先進的なパッケージが集まっています。今回試したbasemap(windows版)が、anaconda.orgにはなくても、conda-forgeにはありました。
2017年4月9日日曜日
Pythonで地図を描く:Basemap
anaconda関係で色々調べていたら、Basemapというパッケージのことを知りました。これは地図を描画してくれるライブラリとのこと。昔から、地図のGUIには苦労していたので、早速試してみました。(ちなみにコードはpython3でかいてます。遂に2.7から3系に移行する決心をつけました)
とりあえず実行例を示します。
環境はmac OSXです。(最近は、Linuxよりmacの方が楽になってきました(^_^;))
これは、matplotlibというグラフ描画パッケージが元にあり、その上で動くようです。しかもGUIまで自分で持ってます。以下にコードを示します。
さて、実際に使うときは任意の緯度経度にマークをつけたくなります。定番の日本標準時の明石の位置に赤丸を表示してみました。
コードは以下を追加することになります。
座標のXYはリストで与えてもいいようなので、簡単にGISデータの描画ができます。
PS
ただ気になったのは、これの海岸前の元データはどこからきたんだろう?ということです。自分が知っている限り、(日本のは)国土地理院しかありません。あそこは基本、利用はプライベートの利用は許可していますが、著作権はどこかに表示してね、というスタンスだったはずです。同様なデータがNatural Earthというサイトにありますが、この海岸線(及び国境線)のデータに関するライセンスの記述がありません。自分が見つけられないだけ?ならいいんですが、少しグレーな気がします。
とりあえず実行例を示します。
環境はmac OSXです。(最近は、Linuxよりmacの方が楽になってきました(^_^;))
これは、matplotlibというグラフ描画パッケージが元にあり、その上で動くようです。しかもGUIまで自分で持ってます。以下にコードを示します。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
# 緯度経度で範囲を指定する
north = 46.
south = 30.
east = 147.
#west = 128.
west = 128.
# 地図の表示('merc"は純粋に緯度経度を直行座標系にplotしている)
m = Basemap( projection='merc', llcrnrlat=south, urcrnrlat=north, llcrnrlon=west, urcrnrlon=east, resolution='l' )
# 陸地を茶色に, 湖を水色に
#m.fillcontinents(color='#8B4513', lake_color='#90FEFF')
m.fillcontinents(color='coral', lake_color='aqua')
# 海を濃い青に
#m.drawlsmask(ocean_color='#00008b')
#m.drawlsmask(ocean_color='blue')
m.drawmapboundary(fill_color='blue')
# NASA 'Blue marble' image
#m.bluemarble()
# 海岸線を引く
m.drawcoastlines(linewidth=0.5, color='black')
# 5度ごとに緯度線を描く
m.drawparallels(np.arange(25, 50, 5), labels = [1, 0, 0, 0], fontsize=10)
# 5度ごとに経度線を描く
m.drawmeridians(np.arange(125, 150, 5), labels = [0, 0, 0, 1], fontsize=10)
# 画面に表示
plt.show()
ちょっと色々試行錯誤したあとがコメントに残ってます。面白いのはNASA 'Blue marble'で、衛星写真ぽいテクスチャを貼り付けてくれます。さて、実際に使うときは任意の緯度経度にマークをつけたくなります。定番の日本標準時の明石の位置に赤丸を表示してみました。
コードは以下を追加することになります。
# 地図に色をつけると見えなくなる! zorderが配置の意味をつけるらしい
x = 135.
y = 35.
m.scatter(x, y, s=200, c='red', latlon=True, zorder=10)
これはかなり苦労しました。scatterというメソッドを最初は例題を見ていると、matplotlibのpltのメソッドをcallする例が多数でしたが、どうもうまくいきません。大体、なんで表示領域の座標系をpltが理解できるんだ?ということに気づき、継承しているBasemapからcallしてみたらやっとうまくいきました。(latlon=Trueという引数があり、これを呼ばないと全然位置があいません)また更に罠なのが、zorderという引数です。よく考えれば当たり前なんですが、どう見てもこれ大元にOpenGL使ってるよねという感じです。(引数の中にはalphaまでありました)だったら、描画位置の指定をしないと隠れてしまいます。ですから、マークは少しZ軸で手前に置いて描きます。座標のXYはリストで与えてもいいようなので、簡単にGISデータの描画ができます。
PS
ただ気になったのは、これの海岸前の元データはどこからきたんだろう?ということです。自分が知っている限り、(日本のは)国土地理院しかありません。あそこは基本、利用はプライベートの利用は許可していますが、著作権はどこかに表示してね、というスタンスだったはずです。同様なデータがNatural Earthというサイトにありますが、この海岸線(及び国境線)のデータに関するライセンスの記述がありません。自分が見つけられないだけ?ならいいんですが、少しグレーな気がします。
2017年3月20日月曜日
Pythonのanacondaを導入してみる
pyenv, spyderをmac osxに導入してみる。
SpyderはpythonのIDEで、anacondaパッケージ内に一緒に入っている。
(というか、anaconda内にpython,numpy等全部入ってしまっている模様。逆に事前に入れてあるpythonやライブラリとは独立した生態系になってしまい、混乱することもあり)
とりあえず、macではpyenvのインストールが必須とのこと
$ brew install pyenv
.bash_profileを修正(赤字部分を追加)
export PYENV_ROOT="${HOME}/.pyenv"
export PATH=/opt/lo/bin:/sbin:$PATH:/usr/local/share/python:${PYENV_ROOT}/bin
eval "$(pyenv init -)"
$ pyenv install -lで、インストール可能なバージョン一覧がでるので、確認。
Python3系を使いたい方
$ pyenv install anaconda3-4.0.0
$ pyenv global anaconda3-4.0.0
Python2系を使いたい方
$ pyenv install anaconda-4.0.0
$ pyenv global anaconda-4.0.0
pyenv globalは全体の環境で、anacondaの使うバージョンを指定しています。特定ディレクトリ以下では別のバージョンが使いたい場合、当該ディレクトリでpyenv local (バージョン)としてやります。
condaというバッケージ管理システムがあり、anaconda内のpythonやライブラリはcondaコマンドで管理します。(brewと共存してしまうのがややこしい。気をつけないと何を動かしているのかわからなくなりそう)
それまでbrewでインストールしたpythonはpipコマンドでライブラリのパッケージ管理をします。
(補足:anacondaの環境にはこれまで使っていたchainerが当然入っていません。anaconda内にもpipコマンドがありますので、こいつでanaconda環境内に再度chainerをインストールしてやれば、とりあえずこれまで通りchainerを動かすことはできますが。。。)
(補足:anacondaの環境にはこれまで使っていたchainerが当然入っていません。anaconda内にもpipコマンドがありますので、こいつでanaconda環境内に再度chainerをインストールしてやれば、とりあえずこれまで通りchainerを動かすことはできますが。。。)
またpythonの仮装環境を作るvirtualenvに対する、pyenv-virtualenvというのもあり、condaのpyenv localとsource activateがバッティングしても、こちらが解決してくれるとのこと。
これまでpython2系ばかり使ってきましたが、そろそろpython3系にも手を出したいと考えていました。しかし、このあちこちに異なるバージョンのpythonが入ってしまう状況、頭が混乱しそうで使い方を整理してから始めないと。。。
2016年2月20日土曜日
TensorFlowで学習プログラム
TensorFlowで学習するプログラムを作ってみました。
一番単純な、2入力1出力です。(論理積を学習させます)
以前、chainerとかのサンプルも探しましたがどれも学習結果を使う所が見つかりませんでした。TensorFlowだとダイレクトにプログラムしないといけないので、その辺りは楽にできました。
とりあえず、サンプルです。
最後に、学習結果を確認しています。結果はこうなります。(一応、期待通りの結果になりました)
一番単純な、2入力1出力です。(論理積を学習させます)
以前、chainerとかのサンプルも探しましたがどれも学習結果を使う所が見つかりませんでした。TensorFlowだとダイレクトにプログラムしないといけないので、その辺りは楽にできました。
とりあえず、サンプルです。
# -*- coding: utf-8 -*- import tensorflow as tf import numpy as np # 入力データの定義 4行2列(データの定義方法がchainerとは違うようです) # x_data = [ # np.array([0., 0.]), # np.array([0., 1.]), # np.array([1., 0.]), # np.array([1., 1.]) # ] x_data = np.array([ [0., 0.], [0., 1.], [1., 0.], [1., 1.] ]) # 結果データの定義(4行1列) # y_data = [ # np.array([0.]), # np.array([0.]), # np.array([0.]), # np.array([1.]) # ] y_data = np.array([ [0.], [0.], [0.], [1.] ]) # 機械学習で最適化するWとbを設定する。Wは4行2列のテンソル。bは4行1列のテンソル。 W = tf.Variable(tf.random_uniform([4, 2], -1.0, 1.0)) b = tf.Variable(tf.zeros([4, 1])) y = W * x_data + b loss = tf.reduce_mean(tf.square(y_data - y)) optimizer = tf.train.GradientDescentOptimizer(0.5) train = optimizer.minimize(loss) # 学習を始める前にこのプログラムで使っている変数を全てリセットして空っぽにする init = tf.initialize_all_variables() # Launch the graph.(おきまりの文句) sess = tf.Session() sess.run(init) # 学習を1000回行い、100回目ごとに画面に学習回数とWとbのその時点の値を表示する for step in xrange(1001): sess.run(train) if step % 100 == 0: print step, sess.run(W), sess.run(b) # 学習結果を確認 x_input = np.array([ [0., 0.], [0., 1.], [1., 0.], [1., 1.] ]) y_res = tf.Variable(tf.zeros([4, 1])) y_res = W * x_input + b print sess.run(y_res) # 4行1列の結果を期待しているのだが、4行2列になってしまう? # 学習が十分進めば、どちらの列も同じような結果になるからいいか。 print sess.run(b)
最後に、学習結果を確認しています。結果はこうなります。(一応、期待通りの結果になりました)
[[ 0.00000000e+00 0.00000000e+00] [ -2.62142518e-23 1.62012971e-23] [ 7.70256410e-23 -1.24630115e-22] [ 9.99999881e-01 1.00000012e+00]] [[ 0.00000000e+00] [ -2.62142518e-23] [ -1.24630115e-22] [ 6.69862151e-01]]最後の、変数y_resとbの出力だけを貼り付けました。 どうしてもまだ理解できないのが、y_resの計算結果は4行1列になるはずなんですが、4行2列になってしまいます。(代入する直前でy_resを4行1列で定義しても上書きされてしまいます。bは4行2列でも、4行1列でも構わないようです)ただ結果をみると各行の値はどちらの列をとっても同じような値なのでよさそうです。 まだ深層学習の理論的な本を買って読み始めた所でよくわかっていませんが、TensrFlowの方がPrimitiveにプログラムしなければいけない分、直感的にわかります。(でもなんとなく、chainerの方がこの後進めていくのによさげな気もしますので、しばらく悩みます)
2016年1月6日水曜日
python:virtualenvメモ
ここのところchainer, TensorFlowとpythonを使ってみることが多かったのですが、他の方はよく
$ virtualenv
というコマンドを使って、そこに環境を構築していました。名前からすると「何らか」の仮想環境を構築するものなのでしょう。ちょっと調べてみると、python専用のライブラリを「試しで」作るときに、メインの環境にダメージを与えないようにするものでした。要は、VMwareやDocker(こっちの方が間隔的に近い)の様な仮想環境ですが、python限定なのが特別です。(ただ、試しにやってみたら、頑張れば、その中でのみ連携して動くJavaやc++のライブラリ、プログラムも入れれそうな感じです。ただ、Javaやc++のライブラリが特別な別のパッケージを要求していると、かなり大変そう。)
1.インストール
pipコマンドが使えるようになっているなら簡単です。
$ pip install virtualenv
これだけです。(中身は10行くらいのpythonスクリプトです)
2.仮想環境の構築
以下のコマンドだけです。
$ virtualenv testenv
これで、testenv/というディレクトリが作成され、配下に以下のディレクトリが作られます。
bin/ include/ lib/ local/
ここに当該OS環境におけるpython環境がコピーされてきます。(後で追加したpackage等は入っていません。使いたい場合はここで再構築です。)
3.仮想環境の実行
この環境でpythonのあるpackageを試してみたくなったら、以下の手順になります。
$ cd testenv
$ source bin/activate
この"activate"は先ほどコピーされたbin/以下に入っており、これを実行することにより、pythonバイナリ、ライブラリはtestenv/以下のものを参照するようになります。(プロンプトにtestenvの文字が追加され、仮想環境下で動いていることがわかります)
つまり、この環境下で"pip"コマンドでインストールすると、testenv/以下にインストールされることになり、本体OSのpythonには影響を与えません。
4.仮想環境の終了
以下のコマンドです。
$ deactivate
5.仮想環境の削除
お試しが終了して、必要なくなったらディレクトリ毎削除すればOKです。
$ rm -rf testenv/
と、非常にお手軽に色々なpythonのパッケージを試すことができます。
$ virtualenv
というコマンドを使って、そこに環境を構築していました。名前からすると「何らか」の仮想環境を構築するものなのでしょう。ちょっと調べてみると、python専用のライブラリを「試しで」作るときに、メインの環境にダメージを与えないようにするものでした。要は、VMwareやDocker(こっちの方が間隔的に近い)の様な仮想環境ですが、python限定なのが特別です。(ただ、試しにやってみたら、頑張れば、その中でのみ連携して動くJavaやc++のライブラリ、プログラムも入れれそうな感じです。ただ、Javaやc++のライブラリが特別な別のパッケージを要求していると、かなり大変そう。)
1.インストール
pipコマンドが使えるようになっているなら簡単です。
$ pip install virtualenv
これだけです。(中身は10行くらいのpythonスクリプトです)
2.仮想環境の構築
以下のコマンドだけです。
$ virtualenv testenv
これで、testenv/というディレクトリが作成され、配下に以下のディレクトリが作られます。
bin/ include/ lib/ local/
ここに当該OS環境におけるpython環境がコピーされてきます。(後で追加したpackage等は入っていません。使いたい場合はここで再構築です。)
3.仮想環境の実行
この環境でpythonのあるpackageを試してみたくなったら、以下の手順になります。
$ cd testenv
$ source bin/activate
この"activate"は先ほどコピーされたbin/以下に入っており、これを実行することにより、pythonバイナリ、ライブラリはtestenv/以下のものを参照するようになります。(プロンプトにtestenvの文字が追加され、仮想環境下で動いていることがわかります)
つまり、この環境下で"pip"コマンドでインストールすると、testenv/以下にインストールされることになり、本体OSのpythonには影響を与えません。
4.仮想環境の終了
以下のコマンドです。
$ deactivate
5.仮想環境の削除
お試しが終了して、必要なくなったらディレクトリ毎削除すればOKです。
$ rm -rf testenv/
と、非常にお手軽に色々なpythonのパッケージを試すことができます。
2015年11月8日日曜日
pythonの超軽量webフレームワーク Bottle:Rapiroを制御する
さて本題ですが、webでRapiroを制御できるようにします。元々、これがしたくてpythonで動く簡単なフレームワークがないか探していました。
とりあえず、作ったweb画面を以下に示します。
何のひねりもありませんが、前進、後退、右旋回、左旋回と停止を指示できるようにします。Rapiroを制御するPythonプログラムではRapiroの運動制御を行っているArudinoに、制御用コンピュータとして搭載してあるRaspberry Piからシリアルで各コマンドを送るだけです。そのコマンドを、上記ページのボタンを押したら、送ってやるようにしてやればいいだけです。
Raspberry Piに以下の様なファイル(ディレクトリ構成)を作ります。
rapiro_study/
- bottle.py
- index.py
views/
- title.tpl
bottle.pyは、フレームワーク本体です。本来ならpythonのパッケージライブラリに入れるべきなんでしょうが、1ファイルと簡単な構成なのでわかりやすいようにここに一緒に置いています。index.pyがwebを制御する本体のプログラムです。(といっても、これだけです)views/以下に、htmlを置いておきます。(このディレクトリ名は固定のようです)今回は一つしか画面を用意していないので、一つしかありません。(title.tpl)これは拡張子が"tpl"ですが、内容は全く"html"です。
title.tpl
index.py
$ python index.py
その後、PCで以下のURLをブラウザで表示します。
http://rapiropi.local:8000/
URL名の最後が".local"とちょっと普通と違いますが、家庭内のPCを特にDNSを作らなくてもURLでアクセスできるようにパッケージを入れてあるからです。Webのボタンを押したら、本来なら別のページに飛ぶためのhtmlファイルを指定するところなんですが、今回は単にpythonに制御用の指示を出したいだけなので、単純に文字列を送信して、index.pyではそれを受けとって対応するシリアルコマンドを送ったら、また一つだけのホームページを表示するようにしています。ホームページの方は文字の大きさや何かを指定するために、CSSを使わず個別に指定しています。大したページでもなかったので、CSSを作るのが面倒くさかっただけです。
実際に動かしてみたところ、「ちょっと反応が鈍いかな?」という感じですがちゃんと動いてくれました。
制御用にGETとPOSTの両方を使っていますが、単に勉強用に両方を使ってみただけです。コマンドと関係はありません。(特に送信時に個別のデータはつけてませんし)JavaScriptの練習の時にはよくPOSTで送信していましたが、いざhtmlの定義だけでやるとき、最初は"submit"ボタンしか送信する方法が思いつかず悩みました。調べていたら、ボタンにformをつけて、そこで一つだけJavaScriptを使う方法(onclick="document.frm.submit();")が簡単なので、それを多用しています。実は、中々この方法がわからず、最後はJavaScriptを各ボタン毎に書かないといけないだろうかと思いながら、でもこれはhtmlじゃなくtplのbottle用のファイルだから動くんだろうかと悩んでいました。
結局、bottleを使いながらJavaScriptも使えるようですがちょっと特別なお作法があるようです。今回は処理的なところはPythonでやりたかったので、色々調べてみました。
とりあえず、作ったweb画面を以下に示します。
何のひねりもありませんが、前進、後退、右旋回、左旋回と停止を指示できるようにします。Rapiroを制御するPythonプログラムではRapiroの運動制御を行っているArudinoに、制御用コンピュータとして搭載してあるRaspberry Piからシリアルで各コマンドを送るだけです。そのコマンドを、上記ページのボタンを押したら、送ってやるようにしてやればいいだけです。
Raspberry Piに以下の様なファイル(ディレクトリ構成)を作ります。
rapiro_study/
- bottle.py
- index.py
views/
- title.tpl
bottle.pyは、フレームワーク本体です。本来ならpythonのパッケージライブラリに入れるべきなんでしょうが、1ファイルと簡単な構成なのでわかりやすいようにここに一緒に置いています。index.pyがwebを制御する本体のプログラムです。(といっても、これだけです)views/以下に、htmlを置いておきます。(このディレクトリ名は固定のようです)今回は一つしか画面を用意していないので、一つしかありません。(title.tpl)これは拡張子が"tpl"ですが、内容は全く"html"です。
title.tpl
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>RAPIRO</title> </head> <body> <h1>RAPIRO Control page</h1> <form name="frm" method="get" action="stop"> <input type="button" onclick="document.frm.submit();" value="停止" style="font-size: 400%; width:200px; "> </form> <form name="frmF" method="post" action="forward"> <input type="button" onclick="document.frmF.submit();" value="前進" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 50%; top: 40%;"> </form> <form name="frmB" method="post" action="back"> <input type="button" onclick="document.frmB.submit();" value="後退" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 50%; top: 60%"> </form> <form name="frmR" method="post" action="right"> <input type="button" onclick="document.frmR.submit();" value="右旋回" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 70%; top: 50%"> </form> <form name="frmL" method="post" action="left"> <input type="button" onclick="document.frmL.submit();" value="左旋回" style="font-size: 100%; width:100px; height:100px; position: absolute; left: 30%; top: 50%"> </form> </body> </html>
index.py
#!/usr/bin/python # -*- coding: utf-8 -*- from bottle import route, run, template, request ''' __debug__を追加。(通常実行時は、__debug__ = 1) Macでデバッグしたい時は、$ python -O rapiro3.pyで最適化をかけて実行する。 ''' if __debug__: import serial com = serial.Serial('/dev/ttyAMA0', 57600, timeout=10) # 出力を一カ所にまとめる。(デバッグ時には標準出力にだすようにする) def out(comStr): if __debug__: com.write(comStr) else: print comStr # localhost:8000 @route('/') def title(): # views/title.tplを呼ぶ return template('title') # localhost:8000/show @route('/stop', method='GET') def stop(): # GETパラメータの取得(username, men) command = request.forms.submit print("in stop", command) out('#M0') # 停止(初期姿勢) # views/title.tplを呼ぶ return template('title') # localhost:8000/forward @route('/forward', method='POST') def forward(): print("in forward") out('#M1') # 前進 # views/title.tplを呼ぶ return template('title') # localhost:8000/back @route('/back', method='POST') def back(): print("in back") out('#M2') # 後退 # views/title.tplを呼ぶ return template('title') # localhost:8000/right @route('/right', method='POST') def right(): print("in right") out('#M4') # 右回り # views/title.tplを呼ぶ return template('title') # localhost:8000/left @route('/left', method='POST') def left(): print("in left") out('#M3') # 左回り # views/title.tplを呼ぶ return template('title') # ビルドインサーバの実行 run(host='rapiropi.local', port=8000, debug=True, reloader=True)これだけです。実行するときは、Rapiroのコンピュータ(rapiropiです)にログインし、以下のコマンドを実行します。
$ python index.py
その後、PCで以下のURLをブラウザで表示します。
http://rapiropi.local:8000/
URL名の最後が".local"とちょっと普通と違いますが、家庭内のPCを特にDNSを作らなくてもURLでアクセスできるようにパッケージを入れてあるからです。Webのボタンを押したら、本来なら別のページに飛ぶためのhtmlファイルを指定するところなんですが、今回は単にpythonに制御用の指示を出したいだけなので、単純に文字列を送信して、index.pyではそれを受けとって対応するシリアルコマンドを送ったら、また一つだけのホームページを表示するようにしています。ホームページの方は文字の大きさや何かを指定するために、CSSを使わず個別に指定しています。大したページでもなかったので、CSSを作るのが面倒くさかっただけです。
実際に動かしてみたところ、「ちょっと反応が鈍いかな?」という感じですがちゃんと動いてくれました。
制御用にGETとPOSTの両方を使っていますが、単に勉強用に両方を使ってみただけです。コマンドと関係はありません。(特に送信時に個別のデータはつけてませんし)JavaScriptの練習の時にはよくPOSTで送信していましたが、いざhtmlの定義だけでやるとき、最初は"submit"ボタンしか送信する方法が思いつかず悩みました。調べていたら、ボタンにformをつけて、そこで一つだけJavaScriptを使う方法(onclick="document.frm.submit();")が簡単なので、それを多用しています。実は、中々この方法がわからず、最後はJavaScriptを各ボタン毎に書かないといけないだろうかと思いながら、でもこれはhtmlじゃなくtplのbottle用のファイルだから動くんだろうかと悩んでいました。
結局、bottleを使いながらJavaScriptも使えるようですがちょっと特別なお作法があるようです。今回は処理的なところはPythonでやりたかったので、色々調べてみました。
2015年9月23日水曜日
pythonの超軽量webフレームワーク Bottle
pythonでおもちゃを動かすプログラムを作っていて、それをwebからコントロールしたくなりました。
何か楽な手段はないかと探したら、pythonによる超軽量なwebフレームワーク”Bottle”があることを知りました。特徴は、bottle.pyというファイル一つ使うだけでいいこと。
1.bottleのインストール
インストールというほどのこともありません。以下のコマンドでbottle.pyをDLしてくるだけです。
wget http://bottlepy.org/bottle.py
ただこのコマンドを実行してみたところ、githubに飛ばされて、そこからDLしてきました。
2.最初のサンプル
bottle.pyの中に各種モジュールが入っているので、必要なものをimportしたpythonプログラムを書いてやります。超シンプルなものを以下に示します。(hello_world.pyで作成)
これで、以下を実行します。(bottle.pyは同じ階層に置いておきます)
$ python hello_world.py
次にブラウザで、http://localhost:8080/hello にアクセスすると、ブラウザに”Hello World!”の文字が表示されます。 最初に文字コードを指定してやれば、日本語ももちろんいけます。
# -*- coding:utf-8 -*-
さて、次に考えないといけないのは、ページ上に入力欄やボタン、リンクに飛ばすことができるよう、htmlを別途用意して、それを表示させるにはどうしたらいいか?どうもテンプレート(.tpl)ファイルを用意してやればいいようです。そちらについては、また今度結果をまとめようと思います。
1.bottleのインストール
インストールというほどのこともありません。以下のコマンドでbottle.pyをDLしてくるだけです。
wget http://bottlepy.org/bottle.py
ただこのコマンドを実行してみたところ、githubに飛ばされて、そこからDLしてきました。
2.最初のサンプル
bottle.pyの中に各種モジュールが入っているので、必要なものをimportしたpythonプログラムを書いてやります。超シンプルなものを以下に示します。(hello_world.pyで作成)
from bottle import route, run @route('/hello') def hello(): return "Hello World!" run(host='localhost', port=8080, debug=True, reloader=True)
これで、以下を実行します。(bottle.pyは同じ階層に置いておきます)
$ python hello_world.py
次にブラウザで、http://localhost:8080/hello にアクセスすると、ブラウザに”Hello World!”の文字が表示されます。 最初に文字コードを指定してやれば、日本語ももちろんいけます。
# -*- coding:utf-8 -*-
さて、次に考えないといけないのは、ページ上に入力欄やボタン、リンクに飛ばすことができるよう、htmlを別途用意して、それを表示させるにはどうしたらいいか?どうもテンプレート(.tpl)ファイルを用意してやればいいようです。そちらについては、また今度結果をまとめようと思います。
2015年7月24日金曜日
日本の海岸線データの加工
かなり国土地理院のデータを間引いて、なんとか100MBのデータを数MBまでにしましたが、ノートPCなんかで使うにはまだまだサイズが大きいです。思い切って1/100に間引いてみましたが、日本の海岸線が途切れてしまいます。
やはり少し頭を使って、元になる国土地理院の海岸線データの特徴を調べてみます。
1.海岸線データの固まり方
先にも言いましたが、元データは県別のxmlデータになっていて、行政界、島毎にタグが分かれています。それを緯度経度だけ抽出してくると以下ののようになります。
途中、2行の空白行がありますが、そこが行政界の区切り、あるいは島のデータの始まりを表します。上記サンプルを見ると、2つ目のデータの固まりのデータの始まりと終わり(6行目と13行目です)が同じ緯度経度になっています。つまりこれは島だということです。
前回のプログラムでは、以下に注意しました。
しかし、特に島のデータに顕著にでましたが、あまり間引く数が大きいときはデータの固まりの最後(空白行の直前のデータ)も残してやらないと島が閉曲線になりません。
2,改良版プログラム
ということで、島の最後のデータも残してやるように改修しました。
さてこれで1/100に間引いてみましたが、どうもデータのサイズが思うように小さくなりません。
3.改良版の処理結果を見る
再度、処理結果を調べてみました。
確かに島のデータは残されるようになっていますが、思いの外国土地理院のデータには小島がしっかりはいっているようで、同じ緯度経度が2回連続する固まりが大量にありました。こいつらは改良版のプログラムではどんなに間引率を大きくしても残されるようにしてあるため、思ったようにデータのサイズが小さくならなかったようです。
でも実際日本地図を使うようなときはこんな小島、表示もされないのでデータの無駄です。
4.小島のデータを削除する
手作業で削除するかとも思ったんですが、すぐに諦めました。さすがにデータのサイズが小さくならない原因だけあって大量に存在しています。あきらめて、2行同じ緯度経度のデータが続く箇所はフィルタするようなプログラムを作りました。(行政界の他のところで偶然そういう場所がないか、という恐れはありますがまあ少しくらい抜けてもそれに気づくほど拡大して使うことはないでしょう。もし使う必要があるなら、それはもう手作業でなんとかしてやるしかありません)
かなり手抜きプログラムですが、これでサイズを1/100にし、かつ2行しかないような小島のデータを削除して、データサイズが1.4MBまで縮小できました。結果を以下に示します。
先日の海岸線とほとんど見た目は変わりません。よく見れば、小さな小島は消えているんですがね。
やはり少し頭を使って、元になる国土地理院の海岸線データの特徴を調べてみます。
1.海岸線データの固まり方
先にも言いましたが、元データは県別のxmlデータになっていて、行政界、島毎にタグが分かれています。それを緯度経度だけ抽出してくると以下ののようになります。
35.04924833 136.83906528 35.05145167 136.83815250 35.05175111 136.83802834 35.05899417 136.87838278 35.05921944 136.87846166 35.05952000 136.87857805 35.05949694 136.87866195 35.05947139 136.87874555 35.05914306 136.87861805 35.05894917 136.87855139 35.05899417 136.87838278 35.05730000 136.87774750 35.05771861 136.87788778
途中、2行の空白行がありますが、そこが行政界の区切り、あるいは島のデータの始まりを表します。上記サンプルを見ると、2つ目のデータの固まりのデータの始まりと終わり(6行目と13行目です)が同じ緯度経度になっています。つまりこれは島だということです。
前回のプログラムでは、以下に注意しました。
- 行政界、島の区切りを示す空白行は残す。
- 空白行を出力したところで間引きカウンタを0クリアして、空白行に続く先頭データは積極的に出力する。
しかし、特に島のデータに顕著にでましたが、あまり間引く数が大きいときはデータの固まりの最後(空白行の直前のデータ)も残してやらないと島が閉曲線になりません。
2,改良版プログラム
ということで、島の最後のデータも残してやるように改修しました。
#!/usr/bin/python # coding: utf-8 import sys if __name__ == "__main__": argv = sys.argv # コマンドライン引数を格納したリストの取得 argc = len(argv) # 引数の個数 # print argv # print argc fi = open(argv[1], 'r') fo = open(argv[2], 'w') # n行に1回出力するようにする ct = 0 preLine = "" for line in fi: if line.strip() == "": # 島等のデータとの境界である空白行は残すようにする if (preLine.strip() != ""): # 空白行の直前のデータは残す(島の閉曲線を守るため) fo.write(preLine) fo.write(line) ct = 0 # 島のデータは少ないので1行でも残す preLine = line continue if ct == 0: fo.write(line) ct = ct + 1 preLine = line if ct == 100: # 100行に1回出力する ct = 0 fi.close() fo.close()
さてこれで1/100に間引いてみましたが、どうもデータのサイズが思うように小さくなりません。
3.改良版の処理結果を見る
再度、処理結果を調べてみました。
35.08614861 136.89070723 35.08342250 136.86787278 35.05776194 136.84691778 35.06403806 136.84048584 35.05175111 136.83802834 35.05175111 136.83802834 35.05899417 136.87838278 35.05899417 136.87838278
確かに島のデータは残されるようになっていますが、思いの外国土地理院のデータには小島がしっかりはいっているようで、同じ緯度経度が2回連続する固まりが大量にありました。こいつらは改良版のプログラムではどんなに間引率を大きくしても残されるようにしてあるため、思ったようにデータのサイズが小さくならなかったようです。
でも実際日本地図を使うようなときはこんな小島、表示もされないのでデータの無駄です。
4.小島のデータを削除する
手作業で削除するかとも思ったんですが、すぐに諦めました。さすがにデータのサイズが小さくならない原因だけあって大量に存在しています。あきらめて、2行同じ緯度経度のデータが続く箇所はフィルタするようなプログラムを作りました。(行政界の他のところで偶然そういう場所がないか、という恐れはありますがまあ少しくらい抜けてもそれに気づくほど拡大して使うことはないでしょう。もし使う必要があるなら、それはもう手作業でなんとかしてやるしかありません)
#!/usr/bin/python # coding: utf-8 import sys if __name__ == "__main__": argv = sys.argv # コマンドライン引数を格納したリストの取得 argc = len(argv) # 引数の個数 # print argv # print argc fi = open(argv[1], 'r') fo = open(argv[2], 'w') # n行に1回出力するようにする ct = 0 preLine = "" for line in fi: if line.strip() == "": # 島等のデータとの境界である空白行は残すようにする if (preLine.strip() != ""): # 空白行の直前のデータは残す(島の閉曲線を守るため) fo.write(preLine) fo.write(line) ct = 0 # 島のデータは少ないので1行でも残す preLine = line continue if preLine != line: fo.write(preLine) else: preLine = "" line = "" continue preLine = line fi.close() fo.close()
かなり手抜きプログラムですが、これでサイズを1/100にし、かつ2行しかないような小島のデータを削除して、データサイズが1.4MBまで縮小できました。結果を以下に示します。
先日の海岸線とほとんど見た目は変わりません。よく見れば、小さな小島は消えているんですがね。
2015年7月21日火曜日
日本の海岸線データを手にいれる
レポートを書くとき、何かと使うことの多いのが地図データです。しかも緯度経度で海岸線や県境のデータがあるとベストです。(緯度経度で地点をマークして一緒にEXCELで散布図にしてやれば正確な表現ができます)
実は国土地理院では昔から公開していますが、数年前からやたら複雑な書式のxmlなので尻込みしていました。少し長い休みがあるので、重い腰をあげてデータ整理でもしてみましょう。
1.海岸線データを入手する
ぐぐれば簡単に見つかります。DLするとき何に使う?とか簡単なアンケートに答えないといけませんが、「趣味」の項目もありますから特に気張らずに。(ただ、ライセンス的にフリーというわけではないので注意はしてください。個人目的、研究用等に使うならご自由にという程度です)
データは県ごとに分かれています。
2.xmlフォーマット
各県ごとにデータが分かれています。しかも、県の中でも行政界ごとにxmlのタグが分けられているようです。以下に例を示します。
xmlのタグ、<gml:posList>で識別されています。数字は一目で緯度経度とわかります。(国土地理院なので、今は当然世界測地系です)
3.海岸線データを取り出す
テキストエディタで取り出してもいいんですが、なにせ数があります。こちらは単に日本地図が描きたいだけなんですが、きちんと県別、さらに行政界毎にデータがタグで分離されているため、テキストエディタで手作業でやってたら死にます。
とりあえず、xmlなので、以下のプログラムでこのタグ部分だけ抽出してやります。
以下の様な感じで海岸線のデータだけ標準出力にでてきますので、それをファイルにリダイレクトしてやります。
35.55061306 140.11532166
35.55081833 140.11538084
35.55206806 140.11517666
35.55243944 140.11512389
35.55278833 140.11507416
4.データの間引き
さてこうしてできた海岸線のデータですが、単に数値の羅列ですがなんと100MB強あります!県毎にsheetを分ければなんとか日本の海岸線を散布図で描けますが、一つのファイルにまとめてしまうと、サイズが(行数が)大きくEXCELが扱えないといってきます。どうせそんな細かいところまで見ないので、データの間引きをします。そのために用意したプログラムを以下に示します。
今回は起動時に入力ファイル、出力ファイルを読むようにしました。あと間引く時、元のデータでは行政界毎、及び島毎に空行が入っていました。ここを無視して間引いいてしまうと、小さな島だとデータがなくなってしまいかねません。ちょっとだけ工夫しました。間引く数は実際に試して実験です。結局、10分の1(約10MB)にしないとまともにEXCELは動きませんでした。使うPC環境にもよりますが、もう少し間引いた方がいい気がします。またどうせかなりの縮尺で使うつもりなので、こだわって島のでデータを消さないようにしました。小さな島は消したいんですが、属性として地名は入っているんですが、それが島かどうかは人間が判断してやらないといけません。(GISデータは最後は人力で編集しないと、いいデータにはなりませんね(>_<))
5.完成
以下に実際に作成した日本(白)地図を示します。
使うときは例で示したように、別途示したい位置の緯度経度のデータを用意して、データ系列を追加してやります。注意が必要なのは、普通にやると追加したデータのグラフも散布図の折れ線になってしまいます。単にX-Yの座標だけの散布図にしたい場合が多いでしょうから、その時はデータ系列を追加してから、追加したデータを選択し、グラフの種類を変えてやります。(「複合グラフ」といいます)
実は国土地理院では昔から公開していますが、数年前からやたら複雑な書式のxmlなので尻込みしていました。少し長い休みがあるので、重い腰をあげてデータ整理でもしてみましょう。
1.海岸線データを入手する
ぐぐれば簡単に見つかります。DLするとき何に使う?とか簡単なアンケートに答えないといけませんが、「趣味」の項目もありますから特に気張らずに。(ただ、ライセンス的にフリーというわけではないので注意はしてください。個人目的、研究用等に使うならご自由にという程度です)
データは県ごとに分かれています。
2.xmlフォーマット
各県ごとにデータが分かれています。しかも、県の中でも行政界ごとにxmlのタグが分けられているようです。以下に例を示します。
<gml:LineStringSegment> <gml:posList> 35.55061306 140.11532166 35.55081833 140.11538084 35.55206806 140.11517666
xmlのタグ、<gml:posList>で識別されています。数字は一目で緯度経度とわかります。(国土地理院なので、今は当然世界測地系です)
3.海岸線データを取り出す
テキストエディタで取り出してもいいんですが、なにせ数があります。こちらは単に日本地図が描きたいだけなんですが、きちんと県別、さらに行政界毎にデータがタグで分離されているため、テキストエディタで手作業でやってたら死にます。
とりあえず、xmlなので、以下のプログラムでこのタグ部分だけ抽出してやります。
# coding: utf-8 import xml.dom.minidom if __name__ == "__main__": dom = xml.dom.minidom.parse("sample.xml") for line in dom.getElementsByTagName("gml:posList"): print line.firstChild.data
以下の様な感じで海岸線のデータだけ標準出力にでてきますので、それをファイルにリダイレクトしてやります。
35.55061306 140.11532166
35.55081833 140.11538084
35.55206806 140.11517666
35.55243944 140.11512389
35.55278833 140.11507416
4.データの間引き
さてこうしてできた海岸線のデータですが、単に数値の羅列ですがなんと100MB強あります!県毎にsheetを分ければなんとか日本の海岸線を散布図で描けますが、一つのファイルにまとめてしまうと、サイズが(行数が)大きくEXCELが扱えないといってきます。どうせそんな細かいところまで見ないので、データの間引きをします。そのために用意したプログラムを以下に示します。
#!/usr/bin/python # coding: utf-8 import sys if __name__ == "__main__": argv = sys.argv # コマンドライン引数を格納したリストの取得 argc = len(argv) # 引数の個数 # print argv # print argc fi = open(argv[1], 'r') fo = open(argv[2], 'w') # n行に1回出力するようにする ct = 0 for line in fi: if line.strip() == "": # 島等のデータとの境界である空白行は残すようにする fo.write(line) ct = 0 # 島のデータは少ないので1行でも残す continue if ct == 0: fo.write(line) ct = ct + 1 if ct == 10: # 10行に1回出力する ct = 0 fi.close() fo.close()
今回は起動時に入力ファイル、出力ファイルを読むようにしました。あと間引く時、元のデータでは行政界毎、及び島毎に空行が入っていました。ここを無視して間引いいてしまうと、小さな島だとデータがなくなってしまいかねません。ちょっとだけ工夫しました。間引く数は実際に試して実験です。結局、10分の1(約10MB)にしないとまともにEXCELは動きませんでした。使うPC環境にもよりますが、もう少し間引いた方がいい気がします。またどうせかなりの縮尺で使うつもりなので、こだわって島のでデータを消さないようにしました。小さな島は消したいんですが、属性として地名は入っているんですが、それが島かどうかは人間が判断してやらないといけません。(GISデータは最後は人力で編集しないと、いいデータにはなりませんね(>_<))
5.完成
以下に実際に作成した日本(白)地図を示します。
使うときは例で示したように、別途示したい位置の緯度経度のデータを用意して、データ系列を追加してやります。注意が必要なのは、普通にやると追加したデータのグラフも散布図の折れ線になってしまいます。単にX-Yの座標だけの散布図にしたい場合が多いでしょうから、その時はデータ系列を追加してから、追加したデータを選択し、グラフの種類を変えてやります。(「複合グラフ」といいます)
2015年1月2日金曜日
CouchDBのMapReduceをアプリ(python)で動かす
前回、Futon上でMapReduceを定義、動かしてみました。サンプル的にDBの集計をしたい場合はそれでいいんですが、やはりアプリ(python)からMapReduce処理を指示し、集計処理をしてみたいです。
とりあえず前回、日付毎の集計をするMapReduceを”eachdate”に作成しましたので、これをpythonからCallしてみます。(Keyを指定せずに、全部を対象にしています)
ところがこれを実行すると以下の結果になってしまいました。
なんかkeys(日付)毎に集計されるのを期待したのですが、keys無視で集計されています。色々、調べまくってみると、どうもデフォルトではreduce関数の入力はMapの結果だけではなく、Reduceの結果も再度受け入れてしまうようです。(そのため、全部集計してしまう:注)これを期待どおり、keys毎に集計したい場合は以下のOptionを入れるそうです。
こうすると、期待どおり日付毎の集計をしてくれます。
(ちなみに、ここにはReduceを行わないoptionとして'reduce=False'というのもあります)
注:Reduce関数の出力が再度Reduce関数の入力に入ってしまう、というのは説明が足りてませんでしたね。(元が英語だったので、意図を解釈しきれてなかった)これは、Mapの結果が大きいと、Map関数が入力データをsplitして複数のMap結果が生成されるそうです。(具体的にどのくらい大きいとsplitされるのかはまだ調べてません。)そうすると、各Map結果に対してReduce関数が動き、最後にそれらのReduce関数の結果をまとめるため、もう一度Reduce関数がCallされるんだそうです。同じReduce関数がCallされるので、そのままだと再集計をしてしまう、ということが起きるようです。それを避けるため、rereduceというflagがあり、Reduce関数内ではそのflagでsplitされたMap関数を処理するReduce関数なのか、最後に集計するReduce関数なのかをif文で識別して動作する、という方法を使うそうです。ここで述べたgroupとsplit、rereduceの関係はまた調べて整理しないとダメですね。
さて、MapReduceのコードをFutonで入力するのもいいですが、python上でコーディングしてしまう方法もあります。
何のことはありません、関数定義を文字列として渡しているだけですね。
後、これらの結果を見るとreduce定義の引数のところで(keys, values)としましたが、特に(key, value)でもいいようです。まあわかれば当たり前の話ですが、関数定義の時の引数名なんで、順番が大事なだけですね。
PS:
最後に、ログのとり方についてだけ追記しておきます。特に今回悩んだような症状のとき、Reduce関数には何が引数に入っているか知りたくなります。そんな時、ログ関数があります。
こんなふうにlog()を入れるだけで、/var/log/couchdb/couch.logに記録されます。これを見ると、group=Trueがある時とない時の違いがひと目でわかります。
groupの指定をしていないと以下のログが記録されます。
id情報まで表示されるので見難いのですが、Map関数を通しても、すべての日付のリストがkeysに入っており、対応するpriceもvaluesにリストで5個集められてしまっていることがわかります。(これでは、日付毎の集計なんてされないはずだ)
一方、group=Trueの指定をすると以下のログになりました。
今度は、一回のreduce関数がcallされる時にはkeysには日付ごとのリストが集められていることがわかります。(つまり3回reduce関数はcallされています)これではっきりと、想定した動作をなぜしなかったのかがわかります。
とりあえず前回、日付毎の集計をするMapReduceを”eachdate”に作成しましたので、これをpythonからCallしてみます。(Keyを指定せずに、全部を対象にしています)
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['kakeibo'] result = db.view("application/eachdate") for r in result: print r
ところがこれを実行すると以下の結果になってしまいました。
< Row key="None," value="2850">
なんかkeys(日付)毎に集計されるのを期待したのですが、keys無視で集計されています。色々、調べまくってみると、どうもデフォルトではreduce関数の入力はMapの結果だけではなく、Reduceの結果も再度受け入れてしまうようです。(そのため、全部集計してしまう:注)これを期待どおり、keys毎に集計したい場合は以下のOptionを入れるそうです。
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['kakeibo'] result = db.view("application/eachdate", group=True) for r in result: print r
こうすると、期待どおり日付毎の集計をしてくれます。
< Row key=u'5/10', value=1300>
< Row key=u'5/11', value=650>
< Row key=u'5/12', value=900>
(ちなみに、ここにはReduceを行わないoptionとして'reduce=False'というのもあります)
注:Reduce関数の出力が再度Reduce関数の入力に入ってしまう、というのは説明が足りてませんでしたね。(元が英語だったので、意図を解釈しきれてなかった)これは、Mapの結果が大きいと、Map関数が入力データをsplitして複数のMap結果が生成されるそうです。(具体的にどのくらい大きいとsplitされるのかはまだ調べてません。)そうすると、各Map結果に対してReduce関数が動き、最後にそれらのReduce関数の結果をまとめるため、もう一度Reduce関数がCallされるんだそうです。同じReduce関数がCallされるので、そのままだと再集計をしてしまう、ということが起きるようです。それを避けるため、rereduceというflagがあり、Reduce関数内ではそのflagでsplitされたMap関数を処理するReduce関数なのか、最後に集計するReduce関数なのかをif文で識別して動作する、という方法を使うそうです。ここで述べたgroupとsplit、rereduceの関係はまた調べて整理しないとダメですね。
さて、MapReduceのコードをFutonで入力するのもいいですが、python上でコーディングしてしまう方法もあります。
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['kakeibo'] map_fun = '''function(doc) { emit(doc.date, doc.price); }''' reduce_fun = '''function(keys, values) { return sum(values); }''' result = db.query(map_fun, reduce_fun, group=True) for r in result: print r
何のことはありません、関数定義を文字列として渡しているだけですね。
後、これらの結果を見るとreduce定義の引数のところで(keys, values)としましたが、特に(key, value)でもいいようです。まあわかれば当たり前の話ですが、関数定義の時の引数名なんで、順番が大事なだけですね。
PS:
最後に、ログのとり方についてだけ追記しておきます。特に今回悩んだような症状のとき、Reduce関数には何が引数に入っているか知りたくなります。そんな時、ログ関数があります。
reduce_fun = '''function(keys, values, rereduce) { log(rereduce); log(keys); log(values); if (rereduce) return sum(values); else return values; }''
こんなふうにlog()を入れるだけで、/var/log/couchdb/couch.logに記録されます。これを見ると、group=Trueがある時とない時の違いがひと目でわかります。
groupの指定をしていないと以下のログが記録されます。
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/10","102d4440a25efcd79349859b67000302"],["5/10","102d4440a25efcd79349859b67000a91"],["5/11","102d4440a25efcd79349859b67000eee"],["5/11","102d4440a25efcd79349859b67001c9b"],["5/12","102d4440a25efcd79349859b67002b7f"]] [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [300,1000,400,250,900]
id情報まで表示されるので見難いのですが、Map関数を通しても、すべての日付のリストがkeysに入っており、対応するpriceもvaluesにリストで5個集められてしまっていることがわかります。(これでは、日付毎の集計なんてされないはずだ)
一方、group=Trueの指定をすると以下のログになりました。
[Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/10","102d4440a25efcd79349859b67000a91"],["5/10","102d4440a25efcd79349859b67000302"]] [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [1000,300] [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.109.0="">] 127.0.0.1 - - POST /kakeibo/_temp_view?group=true 200 [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/11","102d4440a25efcd79349859b67001c9b"],["5/11","102d4440a25efcd79349859b67000eee"]] [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [250,400] [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: false [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [["5/12","102d4440a25efcd79349859b67002b7f"]] [Sat, 03 Jan 2015 07:31:45 GMT] [info] [<0 data-blogger-escaped-.290.0="">] OS Process #Port<0 data-blogger-escaped-.2666=""> Log :: [900]
今度は、一回のreduce関数がcallされる時にはkeysには日付ごとのリストが集められていることがわかります。(つまり3回reduce関数はcallされています)これではっきりと、想定した動作をなぜしなかったのかがわかります。
2014年12月31日水曜日
CouchDBをpythonで操作する
さて実際のDB操作のための実装に移ります。例題として、ちょっと理科年表のデータを入れてみます。(地球半径とか高度ごとの気圧です)
ちょっと見にくいですが、Raidus, SeaLevelGravity, StaticPressureの3個のドキュメントを作成しました。これをプログラムで参照、変更してみます。
Futon上で最初にデータベースを作成してて困ったのが以下のStaticPressureです。
「高度:気圧」のペアのリストの構造なので、JSON形式でも扱いやすいしpythonでも辞書型で扱い易いと思ったんですが、Futon上からはどうしても思うように入力できません。結局、valueにはカンマ区切りの数字の羅列(高度1、気圧1、高度2、気圧2、。。。)という形式にしました。(そのうち3次元テーブルもでてくるから、このフォーマットの方が汎用性高いでしょう。javaにもpythonにもsplit()関数があって、valueを文字列で取得、個々の数字を簡単に切り出せますし。)
しかし、どうも辞書形式が登録できないのが気に入りません。頑張って上図のdataのように、「文字列:数字」のペアなら辞書として登録できることがわかりました。(どうもこれはFutonというより、CouchDBの制約のようです。後に実装でpythonから数字ペアの辞書を登録しますが、CouchDB上では最初の数字は文字列と認識されてしまいます)
ということで、pythonから直接、数字ペアを登録してみます。ドキュメント「Radius」にdataというFieldを作成しておいて、以下のコードで実行してみます。
見た目は数字ペアの辞書が登録できたように見えます。
ところが、以下のプログラムで確認してみると、
実行結果は以下の様になります。
全然ダメです、最初の数字が文字列として認識されてしまっています。
余談:
ところでCouchDBは古いリビジョンを残していて、「適当」なところで自動的にガベージコレクションしてくれるそうです。ただ、プログラムを作って、動かしている方としてはHDDの容量も気になるので、意図的に古いのを削除したくなります。python-couchdbでは以下のメソッドで古いリビジョンを削除します。
説明見てたら同じようなメソッドが2個あったので試してみましたが、やはりcleanup()の方はダメで、そもそもエラーが出てきました。(説明にはそこまで書いてなかった)
ちょっと見にくいですが、Raidus, SeaLevelGravity, StaticPressureの3個のドキュメントを作成しました。これをプログラムで参照、変更してみます。
Futon上で最初にデータベースを作成してて困ったのが以下のStaticPressureです。
「高度:気圧」のペアのリストの構造なので、JSON形式でも扱いやすいしpythonでも辞書型で扱い易いと思ったんですが、Futon上からはどうしても思うように入力できません。結局、valueにはカンマ区切りの数字の羅列(高度1、気圧1、高度2、気圧2、。。。)という形式にしました。(そのうち3次元テーブルもでてくるから、このフォーマットの方が汎用性高いでしょう。javaにもpythonにもsplit()関数があって、valueを文字列で取得、個々の数字を簡単に切り出せますし。)
しかし、どうも辞書形式が登録できないのが気に入りません。頑張って上図のdataのように、「文字列:数字」のペアなら辞書として登録できることがわかりました。(どうもこれはFutonというより、CouchDBの制約のようです。後に実装でpythonから数字ペアの辞書を登録しますが、CouchDB上では最初の数字は文字列と認識されてしまいます)
ということで、pythonから直接、数字ペアを登録してみます。ドキュメント「Radius」にdataというFieldを作成しておいて、以下のコードで実行してみます。
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['environment'] doc = db['Radius'] # ドキュメントの取得(コピーを作成するので注意) dl = { 1:1, 2:2, 3:3} # サンプルデータの辞書 doc['data'] = dl # ドキュメントの一部を変更 db[doc.id] = doc # ドキュメントの更新 #db.commit() # なくてもいい
見た目は数字ペアの辞書が登録できたように見えます。
ところが、以下のプログラムで確認してみると、
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['environment'] doc = db['Radius'] # ドキュメントの取得(コピーを作成するので注意) print doc['data']
実行結果は以下の様になります。
{u'1': 1, u'3': 3, u'2': 2}
全然ダメです、最初の数字が文字列として認識されてしまっています。
余談:
ところでCouchDBは古いリビジョンを残していて、「適当」なところで自動的にガベージコレクションしてくれるそうです。ただ、プログラムを作って、動かしている方としてはHDDの容量も気になるので、意図的に古いのを削除したくなります。python-couchdbでは以下のメソッドで古いリビジョンを削除します。
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['environment'] #db.cleanup() # これは古い設計 db.compact() # こっちが今の、古いリビジョンのドキュメントは削除する
説明見てたら同じようなメソッドが2個あったので試してみましたが、やはりcleanup()の方はダメで、そもそもエラーが出てきました。(説明にはそこまで書いてなかった)
2014年12月30日火曜日
CouchDBを設定し、pythonで操作してみる
NoSQLのデータベースで、高速性よりもフォーマットを自由に扱いたい場合、CouchDBの評判が高いようです。ちょっと試しに自PC上に(Ubuntu14.04)CouchDBを設定し、ついでにデータ操作をどうやるかを試すために、pythonでのサンプルを作ってみます。(オリジナルでJavaScriptのAPIは持っているそうです)
まずCouchDBのHP(launchpad.net)にいってインストール方法を調べます。Ubuntu14.04の場合は以下のようにするらしい。
(aptのリポジトリの状態を確認、更新するらしい。
自分の場合あ既にこのリポジトリが入っていたらしく、最新の状態ですと言われた)
既に(古いバージョンが)インストールされているとまずいので、一度削除します。
(自分は3個とも入ってない言われました)
いよいよインストールします。
ブラウザでhttp://127.0.0.1:5984/にアクセスします。
動いてるようですが、文字列しか表示されません。
couchdbのユーザー設定ないから?
(でも/etc/passwdで確認するとcouchdbのアカウントは登録されていました)
HPのドキュメントを見たら、現状はこれでいいようです。
http://127.0.0.1:5984/_utils
でFutonが動きます。大丈夫なようです。(データベースをWebベースで参照、変更、作成ができます)
PCを再起動しても自動的にデーモンは起動しています、Futonが動きました。
pythonでcoudbを使いたいので、/etc/couchdb/local.iniに以下を追加して再起動しろとのこと。
念の為に/usr/bin以下を知らべてみたら、couchjs(おそらくJavaScript用のモジュール)はあるが、couchpyは存在していません。(注:この設定は必要なかったかも、なくてもcouchpyさえインストールすれば動きました。でも本当は必要な筈...)しばらくぐぐった結果、以下のパッケージをインストールしたらよかった。
しかしcouchdbの再起動方法がわからない。(/etc/init.d以下にcoudbというスクリプトがあるという情報があったが、ubutu14.04だとそんなのない!どうやって最初に起動してるんだ?→後述)
PS:ちなみにこのpython-couchdb、昔はgoogle-Codeで開発されていたんですが、githubに移っていました。googleでも特に制約はないと思うけど、流行りですかね。
というか、再起動は必要なかった。(うまくいかないようなら、インストール直後にやったみたいにコマンドで直接、停止、起動をすればいいでしょう。)
試しにpythonでプログラムを書いてみました。試験用に用意したのは以下の様なデータです。
ぱっと見わかりにくいでしょうが、Fieldにわざと"name"と”名前”と英語、日本語の両方を使っています。
日本語のフィールド名でも動きました。(まあそれでも、英語にしといた方が無難だろうね)
ところでCouchDBはデフォルトではPCの外部からのアクセスは禁止してあります。(データの操作にhttp使うくせに、こういうとこはしっかりしてます)
これを許可しないと使いずらいので、以下の場所を編集します。(コメントにデフォルトでなっているのをはずして、全部のIPアドレスを許可する、といったところでしょうか)
/etc/couchdb/local.ini
;bind 127.0.0.1
↓
bind 0.0.0.0
特に起動用スクリプトが見当たらないんですが、勝手に再起動してくれるようです。(他のディストリビューションだと起動用スクリプトができるものもあるらしいですが、今度はcouchdbのアカウントを自分で作ってあげないといけないとか)
あとFuton上でもConfigurationというToolがありそこからもできそうですが、、、ちょっとよくわかりません。うっかいこの項目を削除という操作をしてしまったらしく、いきなりWeb上から何もできなってしまって(まあ意味から考えて通信ができなくなりますわな)、あわてて手動で設定ファイルを編集しなおしました。
次に以下のプログラムでidを直接指定します。
これで以下の出力がでてきます。
うまくいきました。(couchdb-pythonにAPIとかのマニュアルが見当たらないので、試行錯誤です)
せっかくなので、Map機能を使い検索してみます。FutonでTemplate Viewにして、
以下の関数をMap関数(左側の欄)として定義します。
デフォルトは、emit(null, doc);でどうも全文対象という意味らしいんですが、sexというFieldにマークをします。
これをapplication/sample_viewという名前で保存します。(このスクリプト自体がDB内に保存されるのに注意。Futonで参照できます。)
これを使ったサンプルを以下に示します。
keyの与え方はともかく、検索された後の他のタグを知る方法がちょっとクセがあります。
以下の様に出力されます。
とりあえず最低限、必要なことはわかりました。
まずCouchDBのHP(launchpad.net)にいってインストール方法を調べます。Ubuntu14.04の場合は以下のようにするらしい。
$ sudo apt-get install software-properties-common
(aptのリポジトリの状態を確認、更新するらしい。
自分の場合あ既にこのリポジトリが入っていたらしく、最新の状態ですと言われた)
$ sudo add-apt-repository ppa:couchdb/stable
既に(古いバージョンが)インストールされているとまずいので、一度削除します。
$ sudo apt-get remove couchdb couchdb-bin couchdb-common
(自分は3個とも入ってない言われました)
いよいよインストールします。
$ sudo apt-get install -V couchdb $ sudo stop couchdb $ sudo start couchdb
ブラウザでhttp://127.0.0.1:5984/にアクセスします。
動いてるようですが、文字列しか表示されません。
couchdbのユーザー設定ないから?
(でも/etc/passwdで確認するとcouchdbのアカウントは登録されていました)
HPのドキュメントを見たら、現状はこれでいいようです。
http://127.0.0.1:5984/_utils
でFutonが動きます。大丈夫なようです。(データベースをWebベースで参照、変更、作成ができます)
PCを再起動しても自動的にデーモンは起動しています、Futonが動きました。
pythonでcoudbを使いたいので、/etc/couchdb/local.iniに以下を追加して再起動しろとのこと。
[query_servers] python=/usr/bin/couchpy
念の為に/usr/bin以下を知らべてみたら、couchjs(おそらくJavaScript用のモジュール)はあるが、couchpyは存在していません。(注:この設定は必要なかったかも、なくてもcouchpyさえインストールすれば動きました。でも本当は必要な筈...)しばらくぐぐった結果、以下のパッケージをインストールしたらよかった。
$ sudo apt-get install python-couchdb
しかしcouchdbの再起動方法がわからない。(/etc/init.d以下にcoudbというスクリプトがあるという情報があったが、ubutu14.04だとそんなのない!どうやって最初に起動してるんだ?→後述)
PS:ちなみにこのpython-couchdb、昔はgoogle-Codeで開発されていたんですが、githubに移っていました。googleでも特に制約はないと思うけど、流行りですかね。
というか、再起動は必要なかった。(うまくいかないようなら、インストール直後にやったみたいにコマンドで直接、停止、起動をすればいいでしょう。)
試しにpythonでプログラムを書いてみました。試験用に用意したのは以下の様なデータです。
ぱっと見わかりにくいでしょうが、Fieldにわざと"name"と”名前”と英語、日本語の両方を使っています。
# coding: utf-8 import couchdb db = couchdb.Server('http://localhost:5984') sampledb = db['sample_db'] for id in sampledb: dbitem = sampledb[id] name = dbitem[u'名前'] print name
日本語のフィールド名でも動きました。(まあそれでも、英語にしといた方が無難だろうね)
ところでCouchDBはデフォルトではPCの外部からのアクセスは禁止してあります。(データの操作にhttp使うくせに、こういうとこはしっかりしてます)
これを許可しないと使いずらいので、以下の場所を編集します。(コメントにデフォルトでなっているのをはずして、全部のIPアドレスを許可する、といったところでしょうか)
/etc/couchdb/local.ini
;bind 127.0.0.1
↓
bind 0.0.0.0
特に起動用スクリプトが見当たらないんですが、勝手に再起動してくれるようです。(他のディストリビューションだと起動用スクリプトができるものもあるらしいですが、今度はcouchdbのアカウントを自分で作ってあげないといけないとか)
あとFuton上でもConfigurationというToolがありそこからもできそうですが、、、ちょっとよくわかりません。うっかいこの項目を削除という操作をしてしまったらしく、いきなりWeb上から何もできなってしまって(まあ意味から考えて通信ができなくなりますわな)、あわてて手動で設定ファイルを編集しなおしました。
ところでCouchDBを通常のDBとして使おうと思ったんですが、どうもサンプルを探すとCouchDBの特徴であるMap/Reduceにより全文中の特定のFieldを探してくる、という例ばかりひっかかります。(考えてみたら、フォーマット自由のドキュメント型DBなんだから、そういう使い方が本道なんでしょうね)
でも自分はとりあえず普通に(ユニークな)idで検索するのをやりたいんです。
とりあえず、以下のプログラムでまずDBを作成します。
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server.create('python-tests') db['johndoe'] = dict(type='Person', name='John Doe', sex='male') db['maryjane'] = {'type':'Person', 'name':'Mary Jane', 'sex':'female'} # あえてjson形式で指定してみる db.commit() # 特にcloseとかsaveは必要ない? # 不安なら時にcommitするか # 他のPCからすぐに編集した結果を参照したいアプリでは、あったほうがいい
次に以下のプログラムでidを直接指定します。
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['python-tests'] print db['maryjane']
これで以下の出力がでてきます。
<Document u'maryjane'@u'1-6d8ea1b66198a41c398662aa3ae07514' {u'type': u'Person', u'name': u'Mary Jane', u'sex': u'female'}>
うまくいきました。(couchdb-pythonにAPIとかのマニュアルが見当たらないので、試行錯誤です)
せっかくなので、Map機能を使い検索してみます。FutonでTemplate Viewにして、
以下の関数をMap関数(左側の欄)として定義します。
function(doc) { emit(doc.sex, doc); }
デフォルトは、emit(null, doc);でどうも全文対象という意味らしいんですが、sexというFieldにマークをします。
これをapplication/sample_viewという名前で保存します。(このスクリプト自体がDB内に保存されるのに注意。Futonで参照できます。)
これを使ったサンプルを以下に示します。
# coding: utf-8 import couchdb server = couchdb.Server('http://localhost:5984') db = server['python-tests'] #print db['maryjane'] result = db.view("application/sample_view", key="female") for r in result: print 'id',r['id'] print 'key',r['key'] print 'name', r['value']['name']
keyの与え方はともかく、検索された後の他のタグを知る方法がちょっとクセがあります。
以下の様に出力されます。
id maryjane key female name Mary Jane
とりあえず最低限、必要なことはわかりました。
登録:
投稿 (Atom)