sys.exitとシェルのリターンコード

Pythonのsys.exitという、プロセスを終了させる関数を使ったときにちょっとはまりました。

シェルでリターンコード0は正常終了、それ以外は異常終了

シェルの慣習として、0が正常で、0以外は異常というルールがあります。
但し、0-127の間にない数を使うとこのルールが保証されるとは限らないそうです。

Pythonのドキュメントにそう書いてあります。

http://www.python.jp/doc/nightly/library/sys.html#sys.exit

整数を指定した場合、シェル等は 0 は”正常終了”、 0 以外の整数を”異常終了”として扱います。多くのシステムでは、有効な終了ステータスは 0-127 で、これ以外の値を返した場合の動作は未定義です。

sys.exitに渡した数とシェルの$?(リターンコード)が一致しない場合がある!

ボクのUbuntu11.10の環境で以下のコードを実行してみると、sys.exitには0以外の整数を渡したけどシェルの$?(直前のコマンドのリターンコードを取り出せる特殊変数)を比べると一致しない場合が確かにありました。

$ python -c "import sys; sys.exit(0)"; echo $?
0
$ python -c "import sys; sys.exit(1)"; echo $?
1
$ python -c "import sys; sys.exit(127)"; echo $?
127
$ python -c "import sys; sys.exit(128)"; echo $?
128
$ python -c "import sys; sys.exit(256)"; echo $?
0

このように、256をsys.exitに渡すとシェルは0と評価されます。

Pythonでsys.exitを使う場合はboolを使うかエラーメッセージを渡す

なので、sys.exitを使う場合は

import sys
return_code = do_something(...)
sys.exit(bool(return_code))

このようにboolで評価して引数に渡すか、

sys.exit("Failed")

などと、エラーメッセージを渡す方が良いみたいです☆

どうしてこんなことが気になったのか?

なんでこんなことを調べたかというと、自前でテストコード実行用スクリプト書いてJenkinsのジョブとして実行していたら、テストが失敗しているのに青アイコンで"SUCCESS"と画面に表示されてしまうという問題があったからです。

Jenkinsのジョブはリターンコードを見て成功・失敗を判別するので、テストコード用スクリプトがたまたまテストが失敗した場合にリターンコード256を返していてこういう結果になったんだと学びました♪♪♪

with文はカンマ区切りで複数のコンテキストマネージャを渡せる

これ知りませんでした。
Python2.7からはwith文にカンマ区切りで複数のコンテキストマネージャを渡すことが出来て、with文のネストを1行で書けるらしいです。

http://www.python.jp/doc/release/reference/compound_stmts.html#with

ここにある通り、

with A() as a:
    with B() as b:
        suite

これは、

with A() as a, B() as b:
    suite

こう書けます。
組み込みのopen関数でファイルを開くときにwithと共に使うと必ずcloseしてくれるので、一度に二つファイルを開いて処理するプログラムを書くときとかに便利ですね。

file1 = open("foo.txt", "r")
file2 = open("bar.txt", "r")
with file1, file2:
    for x in file1:
        do_something(x)
    for y in file2:
        do_another_one(y)

logging.disableで一時的にログ出力を抑制する

テストコードを実行するときファイルやコンソールにログを出したくない

テストコードを実行するときにファイルやコンソールにログを出力するととても邪魔くさく感じることが多いです。
ファイルに出力される場合は放っておけばいいけど、コンソールにログを出されるとテスト実行の進捗状況や結果が判りにくくなって困ります。
しかも、ログを出すこと自体が入出力なので、テストの実行時間が余計にかかって良い事がありません☆

Pythonのlogging.disableは指定したログレベル以下のログ出力を無視させる

Pythonのloggingモジュールにはdisableという関数が定義されていて、引数にログレベルを渡します。
例えば、

import logging

logging.disable(logging.FATAL)

こうすると、FATAL以下のログは一切出力されません。
つまり、最高レベルのFATAL以下なので、実質的に一切ログが出力されなくなります。

逆に

import loggging

logging.disable(logging.NOTSET)

NOTSETは最低レベルのログレベルなので、こうすればログの抑制が解除されたと同じ状態になります。

setUpとtearDownでlogging.disableを使ってしまう

そういうことで、テストコードを書くときにログの出力を一時的に無理矢理止めるにはこういうことをしてしまえばいいです。

import logging
import unittest

class FooTest(unittest.TestCase):
    def setUp(self):
        logging.disable(logging.FATAL)

    def tearDown(self):
        logging.disable(logging.NOTSET)

    def test(self):
        ... # テスト処理

参考

logging.disableをインタプリタで試した結果はこんな感じです。

>>> import logging
>>> logging.error("a")
ERROR:root:a
>>> logging.disable(logging.FATAL)
>>> logging.error("a")
>>> logging.disable(logging.NOTSET)
>>> logging.error("a")
ERROR:root:a
>>> logging.FATAL, logging.CRITICAL
(50, 50)
>>> logging.NOTSET
0

DjangoのORマッパーでSQL文を簡単に出力するサンプル

ForeignKeyとfilterのメモ - 憧れ駆動開発

この記事でDjangoのORMapperをちょっと試そうとしてうまくいかなかったって話があったけど、簡単にDjangoが出力するSQL文を見たいときはどうしたらいいのかな?

# -*- coding:utf-8 -*-
from django.db import models


class Bread(models.Model):
    class Meta:
        db_table = "bread"
        app_label = "sample"

    name = models.CharField(max_length=255)
    price = models.IntegerField()


class BreadStore(models.Model):
    class Meta:
        db_table = "bread_store"
        app_label = "sample"

    bread = models.ForeignKey(Bread)
    name = models.CharField(max_length=255)


def main():
    print BreadStore.objects.filter(bread__name="foo").query


if __name__ == "__main__":
    main()

実行結果

$ PYTHONPATH=.:foo DJANGO_SETTINGS_MODULE=foo.settings python foo/sample.py 
SELECT `bread_store`.`id`, `bread_store`.`bread_id`, `bread_store`.`name` FROM `bread_store` INNER JOIN `bread` ON (`bread_store`.`bread_id` = `bread`.`id`) WHERE `bread`.`name` = foo

QuerySet.queryからSQL文を見られる

上のコードの通り、QuerySet.queryをprintするとSQL文が見られる。
DjangoのORMapperを使ったときにどういうクエリが発行されるのか簡単に調べたいときは重宝する。

ちょっと試しのときはMetaのapp_labelを忘れないこと

上のコードのMetaって内部クラスを見るとapp_labelって属性を定義している。
これがないとこういうエラーになって動かない。

Traceback (most recent call last):
File "foo/sample.py", line 5, in
class Bread(models.Model):
File "/home/psappho/.virtualenvs/django/local/lib/python2.7/site-packages/django/db/models/base.py", line 54, in __new__
kwargs = {"app_label": model_module.__name__.split('.')[-2]}
IndexError: list index out of range

app_labelを書いておけば大丈夫。

models.pyをパッケージにしてモデル定義を分割したいときもMeta.app_labelが使える

Djangoのモデル定義は基本的にDjangoアプリのパッケージのmodels.pyに書いておかないとsyncdbの対象とかにならない。
何でなのかわかんないけど、"models"ってモジュールに書いてあることをわざわざ見ている。

だからmodels.pyに沢山モデルのコードを書いてしまって長くなった場合に、いくつかのモデル定義ごとにファイルを分割したくなるけど、何もしないとsyncdbの対象とかにならない。

例えば

app
└foo
   └models
      └spam
      └ham
      └egg

こういう風にmodelsをパッケージにしてspam, ham, eggにモデル定義を分割したい場合はMeta.app_labelを付けておくこと。

[app/foo/models/spam.py]

from django.db import models

class Spam(models.Model):
    class Meta:
        db_table = "spam"
        app_label = "foo"
    name = models.CharField(max_length=255)

[app/foo/models/__init__.py]

from app.foo.models.spam import Spam

追記: django.db.connection.queriesを見る方法について

Djangoの発行する生SQLが見たい - PYTHONIC BOOM BOOM HEAD

ボクの記事を見てconnection.queriesを見る方法もあるよって教えてくれた人がいました。

ただ、この方法はボクもこれは知っていたんですけど、これは本当にデータベースに流したSQLを後から確認する方法なので、対象テーブルが存在しないと実行できません。

あと、あんまり気にしなくていいけど、settings.DEBUG=Trueじゃないと見られません。

pdbとか対話型インタプリタでQuerySetに特定の操作をするとどういうクエリを出力するのか確認したいときは、QuerySet.queryをプリントするのが手軽かなーと思います。

この記事を書いていて気づいたんですけど、app_labelをつけるとかいう話は重要ではなかったですね(汗)
一番推したかったことはQuerySet.queryをプリントするってところでした。

レスをくれたaltnightさん、kk6さんに感謝です。

なにかを学ぶということ

僕の会社に要領が悪くてよく先輩に叱られている人がいます。
僕は一緒に仕事をしたことがないので、その人に不満を感じたり、怒りをぶつけようと思ったことがありません。
だから彼が先輩たちにいつもものすごく厳しい口調で叱られている様子を見ていると、なんでこんなに厳しいんだろうと不思議に思います。

僕が推察するに、どうも仕事に対する姿勢が先輩たちには気に入らないみたいです。

叱られている彼の特徴を書き出してみると、だいたいこんな点が先輩たちの癇に障るんじゃないかと思います。

仕事でミスしたときにきちんと振り返りをしない

ミスしてもきちんと原因を考えて理解しておけば、次に同じミスをする可能性はずっと低くなります。
この人もミスした原因を考えていないわけではないようなのですが、考察が足りないというか、本当に意味のある答えを出せず、とんちんかんなことを言ってしまって失敗するみたいです。

多分、先輩がミスをした原因を振り返る時間を取って一緒に考えてくれても、黙ったままで何も言えず、先輩をイライラさせてしまうのでしょう。
そうすると何か答えなければならないと焦って適当なことを言う。
それを聞くと先輩は「真面目に考えているのか!」って激怒する っていう、悪循環なんじゃないかと思います。

基本、こういう先輩って面倒見が良くて優しいので、うまく言葉に表すことができなくても自分の言ったことを整理して、一緒に考えてくれます。
だから黙っていないでしゃべった方がいいでしょう。

相手の気持ちをちゃんと理解せず受け答えをする

世の中にはバリバリ仕事ができるスキルの高い先輩がいます。
しかも、ちゃんと面倒を見てくれて、後輩の教育のことも考えてくれている良い人もたくさんいるんです。

そういう先輩だと、後輩の今の能力だとちょっと難しい仕事だけど、仮にミスしても最終的に自分が全部面倒を見るつもりで難しい仕事にチャレンジする機会を作ってくれることがあります。

そういうとき、先輩の真意を理解しないで「ミスしたら責任取れないのでやめておきます」とか言ってしまう真面目な人がいます。
でも、こういう場合、先輩の方は「せっかく良かれと思ってやらせてみようと思ったのに何だよ!?」って落胆するのです。

ミスして他人に迷惑をかけてはいけないと思うことは大事ですが、ミスすることも織り込み済みでチャレンジする機会をくれていることをきちんと理解しないと自分の為にもならないってことですね。

自分の力不足が明らかなのに勉強しない・勉強している姿勢を見せない

仕事でミスしたり、仕事が遅かったりすると、やっぱり自分の力不足を感じますよね。
そういう力不足を補う為に勉強するのは大事なことだと思います。

でも、実際に毎日欠かさず一生懸命勉強を続けることじゃなくて、「勉強しているんだってアピールすること」がまず大事なときもあります。
何か、Twitterのタイムラインで「Now Browsing...」とか付けて、人のブログとかのURLを流している人ってよくいますよね。
あれで本当に勉強しているかどうかは判らないし、もしかしたら単に「ちょっと見てみたけどこれを理解するのは時間かかりそうだから後でやろう」って、結局永遠に放置していることも多いと思うんです。
それでも、「勉強する意思があるんだろう」「意識高い人なんだろう」って印象を他人にもってもらう効果はあるはずです。

特に、会社の先輩とかは後輩が頑張って今の自分の限界を超えてくれることを期待するし、超えてくれたら嬉しいと思います。
そして、そういう取り組みは簡単には成就しないってことも解っています。
だからすぐに成果が出なくても真面目に勉強しているんだって姿勢を見せればちゃんと認めてくれます。

誰にも知られず特訓を重ねるっていうのは美しい行為かもしれませんが、結果が出るまで待たされる他人はなかなか心中穏やかではないってことを理解しておく方が大事なんじゃないかと僕は思います。

学校教育の勉強と仕事の勉強

問題の彼の抱えている難点として、「勉強」というものを学校教育の勉強をベースに捉えているんじゃないんでしょうか。
何か僕はそういうイメージを持ちました。

「学校教育の勉強」っていうのは、先生が勉強すべきことをきちんと明示してくれた上で、教科書とかプリントを用意してもらって習得のお膳立てを全部してもらうスタイルの勉強です。

でも、仕事の勉強っていうのは、自分が今できないこととか、自分が修得したいことを見つけて取り組んでいくものです。
そして、そういう意識を持って普段から勉強を続けていると、他人の仕事のやり方を見ても、自分の仕事のやり方の改善点を見つけやすくなります。

例えばmercurialの使い方をミスって失敗したことをきっかけにmercurialの使い方を毎日勉強していれば、他の人のmercurialの使い方を見たときに断然学べることが多くなるでしょう。
そういう取り組みを続けるうちに、今度は自分が人に教えられるくらいに上達するんだと思います。

普段から目的意識を持って取り組むと色々良い方向に回っていく

そういう風に、普段から自分で目標を設定して、勉強を続けていると、おのずと失敗に対する考察や振り返りをすることが多くなります。

習慣的にそういうことをするようになるわけですから、冒頭に言った「ミスした原因を考えて対策を立てる」っていうことも次第に上手になっていくはずです。

だから、自分で目標を立てて勉強するのはとても大事だし、そうしていることを周囲にちゃんと見せることもやっぱり大事なんだと僕は思います。

loggingのログレベルについて

pythonのloggingはLoggerとHandlerの両方にログレベルを指定できるのはなぜなんだろうと思って調べた。

http://www.python.jp/doc/nightly/library/logging.html#id4

リファレンスマニュアルにちゃんと説明が書いてある。

Handler.setLevel() メソッドは、
ロガーオブジェクトの場合と同様に、
適切な出力先に振り分けられるべき最も低い深刻度を指定します。
なぜ 2 つも setLevel() メソッドがあるのでしょうか? 
ロガーで設定されるレベルは、付随するハンドラにどんな深刻度のメッセージを渡すか決めます。
ハンドラで設定されるレベルは、ハンドラがどのメッセージを送るべきか決めます。

ロガーの中に複数のハンドラを指定できるけど、
まずログメッセージを送ったら

  1. そのログメッセージがロガーのログレベルに満たなければ何もしない。
  2. ロガーのログレベルを満たした場合、それぞれのハンドラにメッセージを送る。
  3. ハンドラに設定されているログレベルを満たすものだけ、処理される。

こうなることがわかってすっきりした。

vimで無名バッファを開く

vimで一時的に作業するとき、いちいち実ファイルやテンポラリファイルを開くのが面倒くさかったけど、「無名バッファ」というものを使えばいいことを学んだ。

:enew #現在の表示領域に開く
:vsplit + enew #垂直分割して開く

こうするらしい。

ちなみに一時ファイルを開くときは

:e `=tempname()`

こう。

参考リンク