Python + Mercurial で開発してる時の小技(.pycと.origを一気に消す)

これ完全に僕だけかもしれないんですが、PythonMercurialで開発していると、いつの間にか「.pyc」と「.orig」で溢れかえって、なんか ls した時にちょっと見ずらいなぁとか思ってました。

なので、下記のようなエイリアスを.bashrcに書いて、時たま自分のローカル環境で叩いてたりします。

alias delpyc='find . -name "*.pyc" | xargs rm -fr '
alias delorig='find . -name "*.orig" | xargs rm -fr '

delpycと打てば, カレントディレクトリ以下の.pycを消してくれ、delorigは.orgを消してくれます。

みたいな小技って割とあんまり知らなかったりするので、これやっとくと良いよ的なものがあったら是非教えてください。

いじょ

ページの一番下まで来たらイベントを発生させる(TwitterのTLみたいに)

Twitterみたいに、ページの一番下まできたら、次のページを読み込む(ページング)みたいなのって、割とメジャーだから、どっかにあるかと思ったけど、(情弱で探せなくて)割となかったので、自分がやった方法というかプラギン(GitHub - jimyi/jquery_bottom: jQuery plugin to add a “bottom” event that will be triggered when the user has scrolled to the bottom or within proximity to the bottom of an element.)の使い方を書いておく。

1. そもそも「一番下に来た」とかいうイベントはない

社内のSkypeでJSに詳しい人に聞いてみて分かったことは、そもそも JS or jQury にそんなイベントはない。という事、だから画像が表示されたとか、スクロールのイベントを監視してとかの方法だったらできそう。

なので、情弱な僕はアホの子のように「jQuery Event Bottom」 でぐぐる。そしたらそんな感じのプラギンがあった。lucky!

http://www.jimyi.com/blog/2010/04/25/jquery-plugin-bottom-event/
GitHub - jimyi/jquery_bottom: jQuery plugin to add a “bottom” event that will be triggered when the user has scrolled to the bottom or within proximity to the bottom of an element.

2. jquery.bottom-1.0.js

上の記事に使い方は書いてあるので割愛するけど簡単に言うと以下の2行

$(window).bottom(); //windowオブジェクトに「bottomイベント」を追加
$(window).bind("bottom", function() { alert('hoge'); } ); //追加した「bottomイベント」をbindする。

この2行だけで、windowオブジェクトにbottomイベントが追加されて、ページの一番下まで来ると、bindした関数が実行される。

3. どんな仕組み?

このプラグインは「scrollイベント」を監視して、オブジェクトの高さ(上ではwindowオブジェクト)とスクロール位置から、ページの一番下まで来ただろうという時に、「bottomイベント」を発生させてる。

なのでスクロールする度に、bottomイベントを発生させるかどうかの監視イベントが実行されるし、高速で上下させると、bottomイベントが連続して発生してしまう可能性があるので気をつけて俺。

4. Tips

使う上でこうした方がいいかなーと思った事。

何かをローディング中は、処理をスキップしよう

さっきも書いた通り、いやらしい手つきで、マウスホイールを上下にスクロールさせると、bottomイベントは連続して発生する。そんな時は上の1番目の記事みたいに、ローディング中かどうかを判定して処理をスキップさせた方がよいかも

処理が終わったらunbindしておこう。

たとえば、bottomイベントが不要になったら(もうロードするデータがないとか)ちゃんとunbindしておくと良さそう。bottomイベントだけじゃなくて、jquery-bottomの中で使われてる、scrollイベントもunbindしておいてあげると良いかも。(スクロールすると毎回監視イベントが動くので。)

$(window).unbind('bottom');
$(window).unbind('scroll');
bootomイベントの発生条件を調整する (追記 20110707)

オプションにproximityというパラメータがあるけど、これはデフォルトでは0になっていて、スクロールした距離?とウィンドウの高さが完全に一致した時に、bottomイベントを発生させるという事。

けどたまに完全に0にならないケースもあったりするので、どれくらいの誤差を許すのか設定できます。大体自分が試した環境だと、0.01位に設定しておけば、大体問題ない感じでした。

あまり大きい値を設定すると、ページ下に達する大分前にbottomイベントが発生してしまうので適宜調整すると良いかと。


もっと良いやり方とか、プラグインがあったら教えてくだしあ。

以上、朝ブロ(ぐ)終わり

jQueryのcontents

社内のSkypeで、こんなお題があがってきて、へーと思ったのでメモ。

下記DOMから、DOM的操作で「piyo」という文字列を抽出しなさい。

<div id='hoge'>hoge<span>foo</span><span>bar</span>piyo</div>

正規表現とか使わずに、jQueryのDOM操作でどうやるかってーとこうなるらしい。

$("#hoge").contents().last().text(); //=> piyo

contensはテキストノードも含めた、ノード一覧を返してくれるので、「hoge」テキストノード, spanタグノード、「piyo」テキストノードを返してくれるらしい。なるほど。

ae42++

DOMの中身ではなく、DOM自体を入れ替える

これは、なんどかやってすぐ忘れるから単なるメモ。例えば、こんな感じのdivがあるとする。

<div id='hoge'>
bucho is love
</div>

この中身の「bucho is love」だけを書き換えたい場合には、「$('#hoge').html()」で書き換えればいいけど、「id='hoge'」のdivタグを含めて、まるっと入れ替えたい時がある。としよう。

そんな場合はこうすればいける。もっとスマートな書き方はあるだろうけど、単純に、そのdivタグの前か後ろに、書き換え用のdivを挿入して、もとあったdivを消してやればいい。

  #追記 これ駄目な。↓ 追記のreplaceWithでいい。
  $('#hoge').after("<div id='hoge'>love is bucho</div>");
  $('#hoge).last().remove();  //first()でも一緒、afterで追加した要素は、一連のJSプロセス?終了するまで、反映されないっぽい。

そんなある日のJS話ですた。

追記

monjudoh 先生 がコメントで教えてくれたけど、「replaceWith」でいいそうな。ブログに書いてよかた。過去に書いたヤツ、直しておいてください(誰か

  $('#hoge').replaceWith("<div id='hoge'>love is bucho</div>");

Flaskのセッション管理

最近 Flask というWebアプリフレームワークを、いじってて気付いた事をメモとっておく。

セッション管理の仕方が、面白かったというか自分はそういう風に実装した事なかったのでへーと思った。

僕のなかでのセッションデータの管理イメージ

別にこれが普通というわけではないのだろうけど、なんとなくこういうイメージ

  • サーバサイドでセッションデータを発行
  • セッションキーをCookieとかクライアントサイドに持たせる。
  • 違うページにいったら、セッションキーを元にセッションデータを取得

この場合、クライアントサイドにもつ情報は、セッションデータに紐づくキーであって、セッションデータそのものはサーバサイドのストレージなりなんなりにもってるイメージ。

事の発端

Flaskは MicroFrameworkをうたっているフレームワークなので、フレームワークが備える機能も必要最低限になっていて、足りないところは自分たちで補って好きにやっちゃいなよ的なスタンス。

例えば今回の話に関係するところでいうと、DBやORMといったデータストレージを担うような部分は無かったりする。(extentionはあるお)

で、どうやって サーバサイドでセッションデータ管理してるんだろうというのが事の発端。

結論を簡単に言うと、

「そんなんじゃない」「やってない」とか「サーバサイドでデータ持つとか言ってない」

と、釣られた魚には餌をやらんぞ言わんばかりの感じだった。

Flaskの場合

そもそも、sessionを使うときは

from flask import session

を使うわけなのですが、こいつのソースを追っていくと↓にたどりつく。

from werkzeug.contrib.securecookie import SecureCookie

もう眠いんで色々ハショるけど、Flaskでは基本的にセッションキーを含め、暗号化した実データをまるっと、SecureCookieとしてブラウザのCookieにセットしているという感じ。(だからFlask製のアプリを仮にインストールするときはSECRET_KEYをちゃんと自分しか分からないものにした方が良いよ)

セッションの考え方?

なんでこうしたのか考えたり聞いてみたりした事

  • そもそもDBやORM的なものがないからシンプルなやり方にした。
  • セッションの役割は、認証情報だったり最低限の情報を格納するもの。
  • セッションデータを、サーバサイドでわざわざ大量に管理するものではない。(機密性の高い情報を乗っけたりもしないとか)

というのがFlaskのセッション管理の方針なのかも。

セッションデータそのものをCookieに持たせる事のメリットもある。

  • サーバサイドでセッションデータを管理する必要がなくなり、スケールさせやすい
  • ユーザー数が多いほどセッションデータは肥大化していくので、以外に管理めんどい。(有効期限切れデータを消すとか)

んーなるほどなーと思ったという話。

余談

ちなみにDjangoだとセッションストレージはデフォルトはDBです。

https://docs.djangoproject.com/en/1.3/topics/http/sessions/

最後に結局のところ、(サーバサイドの)セッションストレージという概念自体がFlaskにはないから、仮にセッション管理方法をDjangoみたいに、セッションストレージ差し替えたりしたいという場合は、自分でがんばるしかないっぽい。(逆にあったら是非おしえてください。)

gunicorn を daemontools で監視する手順

gunicornをdaemontoolsで死活監視したことあるか的な質問がどこかに流れていたので、手順を簡単にメモっておく。OSの環境は debian(leny)を例にとります。 gunicorn て何よ?、daemontoolsて何よ?って人はこちからどうぞ。

1. daemontoolsのインストール

よくネットで調べると「daemontools-installer」が出てくるけど、もう最近のでは無いというか普通にdaemontoolsだけでインストールできるようになってる。

sudo apt-get install daemontools daemontools-run

2. gunicorn のインストール

お使いのPython環境にインスコ

pip install gunicorn

3. 簡単なWSGIアプリ用意

実際に動くアプリを用意しなければしょうがないので、gunicorn本家からコピペ。

http://gunicorn.org/run.html

# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.

def app(environ, start_response):
    """Simplest possible application object"""
    data = 'Hello, World!\n'
    status = '200 OK'
    response_headers = [
        ('Content-type','text/plain'),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

こいつを test.pyという名前で用意。試しに何もかんがえずに、下記のようにコマンドをうってみて、gunicornが起動する事を確認しませう。

gunicorn --wokers=2 test:app

これで多分起動するはず。確認できたら一旦killしておいてgunicornが立ち上がってない事を確認してください。

4. gunicorn 設定ファイル

上の例のように毎回 --workers とかパラメータを渡してもいいけど、それはメンドクサイので、gunicornの設定はファイル化しておいた方が吉。ここではgunicorn_conf.pyとする。

bind = "127.0.0.1:8000"
logfile ="/var/log/gunicorn/gunicorn.log"
workers = 1

4. runスクリプトの用意

deamontoolsで監視するとき、そのサービスの起動スクリプト(runファイル)が必要となる。とりあえず、これも公式のをコピ。。。拝借して試してみる。

gunicorn/gunicorn_rc at master · benoitc/gunicorn · GitHub

こいつを、run.shとかいう名前で保存してみる。

#!/bin/sh

GUNICORN=/usr/local/bin/gunicorn  #/usr/local/binにインストールされてるかどうかは確実ではない。お使いの環境によるのでしらべてくだしあ。
ROOT=/home/bucho
PID=/var/run/gunicorn.pid

APPt=test:app

if [ -f $PID ]; then rm $PID fi

cd $ROOT
exec setuidgid www $GUNICORN -c $ROOT/gunicorn.conf.py --pidfile=$PID $APP
  • wwwグループのwwwユーザーで実行させると仮定する。

と。ここまで早足で来たが、いまの構造を一旦整理、今回のディレクトリ構成はこんな感じ

■ 実作業場所
/home/bucho/
            ├── run.sh                   #=> gunicornのrunファイル
            ├── test.py                   #=> WSGIアプリ
            └── gunicorn.conf.py    #=> Gunicornの設定ファイル

■ deamontools用サービスディレクトリ
/var/bucho
           ├── run                         #=> /home/bucho/run.sh の シンボリックリンク 
           └── log                          #=> daemontools用ログディレクトリ 
                      ├── run      #=> ログ用のrunスクリプト
                      └── main 
 
■ deamontoolsの実サービスディレクトリ 
/etc/services/bucho/                   #=> /var/bucho以下の構成が最終的にここにできると監視開始
                                                    #=> 後述する作業で、buchoディレクトリも含めて一気作成登録するので
                                                    #=> 今はbuchoディレクトリ作らないでよい。
          
■ gunicorn のPIDファイル
/var/run/gunicorn.pid                  #=> gunicornのプロセスIDが記載されるファイル

■ gunicornのログファイル
/var/log/gunicorn/gunicorn.log   #=> gunicornのログファイル

  • runファイルは実行権限が必要
  • /var/log/gunicornとか書き込む場所にはwwwグループに書き込み権限必要

/var/bucho/log/runファイル は そのサービスのログを取るためのスクリプトを設置する。ぶっちゃけどういう内容を書いたらいいのかよくわかってなす。
今のところは、こんな内容を書いて、mainディレクトリ以下にmultilogの結果が出るようにしておく。

#!/bin/sh

exec 2>&1
exec setuidgid www multilog t ./main

5. サービスの登録

ここまでの作業で、上のディレクトリ構成の/var/buchoと/home/bucho以下のファイル群を用意したとする。こんどは実際に、サービスを、daemontoolsの監視下におくために登録する作業が必要となる。

sudo update-service --add /var/bucho

というコマンドを打つと登録完了、これは。/var/bucho以下の内容をdaemontools(/etc/service以下)に登録するよという意味。実際にサービス登録されたかどうかは、下記のようになコマンドを打てばよい。

sudo svstat /etc/service/bucho

起動からの経過時間とかでるので、それで確認できるかと。

実は、他のブログとかを参考にしていると、「update-service」を使わずに、/etc/service以下にbuchoディレクトリを作って、次にrunファイルを作ってという、やり方が紹介されているがあれは簡便化して紹介しているだけだと思う。実務的な作業の場合にはupdate-serviceを使った方が良いとのこと。

理由としては、daemontoolsは起動していると、常にsvscanという監視サービスが/etc/service以下をチェックしているので、そこで、いろいろ作業していると何か不具合が起きる可能性もあるから。 なので、/var/buchoという登録サービス用の一式をそろえた上で、update-serviceで一気に登録した方がよい。

という感じで、作業完了。本当にgunicronのプロセスがあがってるかどうかは、「ps aux | grep gunicorn」でも確認できるはず。試しにgunicornのプロセスをkill -9してみて、別のプロセスIDで再起動される事を確認すれば良い。

以下は daemontoolsと仲良くなって幸せに至るためのコマンド類

幸せにいたるコマンド類

起動:            sudo svc -u /etc/service/bucho
停止:             sudo svc -d /etc/service/bucho
再起動:          sudo svc -t /etc/service/bucho
稼働状況確認: sudo svstat /etc/service/bucho

参考

この辺も参考にさせていただきました。ありがとうございます。

@aodag にーやん thx!

余談

同じような監視系のツールで python製のsupervisorというのも良いらしいのでそっちも試そうかと。

Mac + Vim で ブラウザ を自動リロード

お久しぶりです。tell-k です。昔書いたエントリで、ブラウザをリロードするAutoHotKeyを紹介したのですが、

AutoHotKeyすげぇ! - Study03.net 対シンバシ専用

残念ながらAutoHotKeyMacでは使えません。FirefoxプラグインのMozReplという手もあったのですがプラギンをインストールするのが面倒なのと、最近Chromeをメインで使ってるのでなんかアレなので、別の方法を模索しました。

そこで、見つけたのが下記の二つの記事です。

vim でファイルを保存した時にChrome で開いているページをリロードするのはAppleScript で十分でした - LukeSilvia’s diary
Vim でファイルを保存すると Firefox がリロード OSX 版 - cooldaemonの備忘録

なんと、AppleScriptでブラウザをリロードできるじゃないですか。Firefoxでもcmd+r を投げる事でリロードできると。すばらしい。というわけで、vim プラグイン化しますた。もちろんMacでしか動きまへん。

GitHub - tell-k/vim-browsereload-mac: vimplugin. scripts to reflesh your browser. works only MacOS

インストールは install.shを用意してあるのでそれを適当に叩いてください。

ついカッとなってやった。反省はしてない。

簡単な説明

READMEにコマンドが書いてあるので簡単に説明すると、vimでファイルを開いて 

:ChromeReload 

て打つとGoogleChromeがリロードされます。

:ChromeReloadStart 

と打つと、バッファを保存「:w」したタイミングでChromeを自動リロードしてくれます。
うざくなったら、

:ChromeReloadStop 

として自動リロードモードを止めてください。こんなコマンドが、Firefox, Safari, Opera用に用意されています。

そんなケースは、ほぼ。。いや間違いなくないとは思いますが、全部のブラウザ(Chrome, Firefox, Safari, Opera)を一気にリロードしたくなったら、

:AllBrowserReload 

と打ってくださいw なんで作ったし俺

あと@podhmo 先輩に言われたヤツで、デフォルトではリロードした後にアクティブになるウィンドウはTerminalアプリなんですが、それをそのままブラウザをアクティブな状態にしたい場合には、

let g:returnAppFlag = 0

このグローバル変数を0にセットしてください。デフォは1がセットされています。

Terminal.appを前提に書いていますが、そもそも俺ターミナルじゃねーしという人は

let g:returnApp = "Terminal"

この辺の設定をお使いのアプリ名で書き換えてください。

謝辞

id:LukeSilvia さん, id:cooldaemon さん。 助かりました。ありがとうございますm( _ _ )m

関数のデフォルト引数を舐めて破壊的な事をするとエラいことになる罠

@aodag 先生に教えてもらった、Python童貞がはまるだろう罠。

pythonのデフォルト引数に破壊的な操作をすると、想像しなかった挙動なるのでメモメモ。

#!/usr/bin/env python
#-*- coding:utf8 -*-

def bucho_make_love(name, lovers=[]):
    lovers.append(name)
    return lovers

print bucho_make_love('haru')  #=> ['haru']
print bucho_make_love('ae35') #=> ['haru', 'ae35']
print bucho_make_love('shin')  #=> ['haru', 'ae35', 'shin']

デフォル引数「lovers」はリストなんだけど、そのリストにappendしていくと、どんどんloversに追加されていってしまうw

デフォルト引数に可変性のオブジェクトを使う時には気をつけましょう。勉強になりました。

なんでそうなるのか、ろくに調べもしないで想像したことを書いてみると、Pythonの世界では変数はすべてオブジェクトに対するリファレンスであるという事が関係しているような気がします。

なのでデフォルト引数は関数の実行時に毎回変数に新しいオブジェクトのリファレンスを突っ込むんじゃなくて、「引数がなかったら、この変数(lovers)は、このオブジェクト(リスト)に対するリファレンスを持つよ。このオブジェクトの初期値は[ ]だよ」という風な事を意味しているのだと理解した。

完全に眠気にまかして書いたので間違ってたらごめんなさい。