Pythonでなんか作ってみる -3ページ目

誰もそれ(IT)のことを分かってくれない(2)

続きです。一つの記事だとどうしても投稿できなかったので。


ITの仕事をずっとしていると、こうした恐ろしい状況に慣れてしまいかねませんが、IT産業以外に目を向けて見ましょう。私達ユーザーは、自動車を買う場合、その車がカタログ性能どおりに動かないということを心配する必要はありません。ましてや、納入された車のキーを回して、エンジンがかからないなんてことは想像もしませんし、実際そうしたことはまず起こらない。100万円で契約した車が、途中で200万円になったり、1000万円になったりするなんてことは考えもしませんし、そんなことは絶対にない。


日本車は比較的優秀だから例えに出されるのですが、カタログ性能どおりに動かないことがよくあってリコールが起きているように見えます。また、100万円で買った車で、事故を起こして1億円になることを想像しないのもどうかと思います。

え?事故を起こすのは運転手の責任?

実際、今のIT業界の能力でも、大抵の機能は思ったとおりに動くシステムは構築できると思います。しかし、ITシステムは各サブシステムが密接に関連しすぎていて(まさしくそれが『こんぴゅーた』を使う利点でもあるのですが)一部の機能が思いもしない動きをするために全体を駄目にしてしまいます。
そして、それを避けるという無謀な目標のために、設計が膨らみ、要求仕様が多岐にわたり、複雑性が爆発するのでしょう。


しかるに、あえて苦言を申し上げますが、現状ではIT産業のプロフェッショナリズムに疑問符を付けざるを得ません。我々から見たときに、経験が浅い、これではマネジャとは言えないという人がプロジェクトマネジメントをやっています。がっかりするくらいスキルのない人が平気で現場にやってくる。そもそも、ユーザーと要件をきちんと定義しようという気概も姿勢もないし、開発が始まっても現場の状況を把握できていない。


要するにそんなベンダに懲りずに発注しているのが、諸悪の根源なんではないでしょうか。
悪いと思うなら発注しない、プロフェッショナルはそれ相応の値段がすることを理解し、悪かろう安かろうは数売って打率を稼ぐ。

ITじゃないからって安心は出来ません。業者を甘やかせば、ビルだってエレベータだって品質は下がるのです。

誰もそれ(IT)のことを分かってくれない

経営者がITを理解できない本当の理由

いままで散々言われてきたことなのだから、何をいまさらって感じがするが、ところどころ突っ込むと。


なんとも歯切れが悪い説明です。当然、経営者から「本当にそれだけの予算で収まるのか」と質問が出ます。ここでも正確に答えるようとすると、「先ほど述べた金額はあくまでも見通しであります」となって、さらに経営陣を悩ませてしまう。正直に書けば、実際にはここまで丁寧には言いません。こんな説明をしていたら、予算を獲得できず仕事を進められませんから、腹をくくって「大丈夫です。何年何月には絶対に稼働させます。費用は何十億円、これ以上、一銭もかかりません」と言い切ります。言い切りますけれど、実態はどう頑張ってみても、本当にこうなるかどうか解らない。そういった状況で、我々は経営トップを説得し、仕事を進めなければいけないのです。


こんなことで腹をくくってるから、そして、それで失敗しても許してくれるから、誰もわかってくれない、わかる必要がない業界になってしまったんじゃないだろうか?


日本において「品質、納期、コストの3点ともクリアーできた開発プロジェクトは26.7%」だそうです。つまり「73.3%の開発プロジェクトが成功していない」ことになります。


つまり、最初から平均打率2割5分なわけです。
平均10割に近づけたいなら、4つも5つもプロジェクトを並行させる必要があるということです。
それを最初から計画に盛り込まないから、「絶対完成させる」とか「バグは0が必須」とか「命に係わるシステムだから間違いは許されない」とか言ってしまうのです。

Python 2.5

Python 2.5がリリースされました。
勢いでダウンロードはしたけど、日本語ドキュメントが出るまで様子見するかな?

http://journal.mycom.co.jp/news/2006/09/20/340.html
パフォーマンスも向上しているそうですが、今の使い方だとあまり関係ないし。

急にアクセスが増えたのは

http://djangoproject.jp/djaboo/に載ったからか?

たいした情報が無くてすみません。


デコレータ

地味に一人か二人は見てくれているのだろうか?

この間、作った関数の処理時間を知りたくなって、もっとも簡単な方法は、単に時間を計ることだと、Python IAQに書いてあったので、デコレータにしてみた。


def timer(fn, *args):
"Time the application of fn to args. Return (result, seconds)."
import time
start = time.clock()
return fn(*args), time.clock() - start

>>>timer(max, range(1e6))
(999999, 0.4921875)


これを元に、timerを高階関数にする。

def timer(fn):
"Time the application of fn to args. Return (result, seconds)."
def __timer__(*args,**kwargs):
import time
start = time.clock()
fn(*args,**kwargs)
return time.clock() - start
return __timer__


こう使う

>>>timer(max)(range(1e6))
0.4921875

関数の方が実行時間しか返さなくなったのはご愛嬌だ。

プログラミングの問題

詳しくはこちらを参照として、以下のような問題。

数の並びがある。 最初の数は、続く数を何個集めるかを表す。 それに続いて、与えられた個数分の数をまとめたら、 次に出てくる数は、最初と同様、何個集めるかを表す数である。 このような並びの数を与えて、 指定されたように数をまとめた状態にするプログラムを作れ。


2chで見たような気がする。


def are(x):
q=list(x)
while len(q) > 0:
a=q.pop(0)
if len(q) < a:
raise StopIteration
yield tuple(q[:a])
del q[:a]

print list(are([1, 5, 3, 55, -45, 6, 2, 8, 7, 0, 1, 2]))
print list(are([1,2,3,4]))


もうちょっとすっきりしそうな気もするけど。

Djangoで家計簿を作ろう(2)

続いてViewやらTemplateやら弄っているうちに、全体のモデルがはっきりしてきたので、最初から作り直した。

モデルはこんな感じになった。

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

class Kouza(models.Model):
name = models.CharField('名称',maxlength=200)
def __str__(self):
return self.name
class Meta:
verbose_name = '口座'
verbose_name_plural = verbose_name
class Admin:
pass

class Kaesuate(models.Model):
name = models.CharField('名称',maxlength=200)
def __str__(self):
return self.name
class Meta:
verbose_name = '返すあて'
verbose_name_plural = verbose_name
class Admin:
pass

class Koumoku(models.Model):
from datetime import datetime
kaesuate = models.ForeignKey(Kaesuate, verbose_name='返すあて')
kouza = models.ForeignKey(Kouza, verbose_name='口座')
siyoubi = models.DateTimeField('使用日', default=datetime.today())
meimoku = models.CharField('名目',maxlength=200)
kingaku = models.IntegerField('金額')
bikou = models.CharField('備考',maxlength=500,blank=True)
yotei = models.BooleanField('予定')

def __str__(self):
return "%s %s %d %s %s" % (self.siyoubi.strftime('%m/%d'),
self.kaesuate,
self.kingaku, self.meimoku,
self.bikou)

class Meta:
get_latest_by = 'siyoubi'
ordering = ['siyoubi']
verbose_name = '項目'
verbose_name_plural = verbose_name
class Admin:
pass

class CardKind(models.Model):
name = models.CharField('名称',maxlength=200)
hikiotosi = models.IntegerField('基本引落し日',blank=True)
kouza = models.ForeignKey(Kouza, verbose_name='口座')
def __str__(self):
return self.name
class Meta:
verbose_name = 'カードの種類'
verbose_name_plural = verbose_name
class Admin:
pass

class Card(models.Model):
koumoku = models.ForeignKey(Koumoku, verbose_name='項目')
kind = models.ForeignKey(CardKind, verbose_name='種類')
tuki = models.IntegerField('支払い月',blank=True)
sumi = models.BooleanField('支払い済み')

def __str__(self):
if self.sumi:
sumi='済'
else:
sumi='未'
return "%s %s %s" % (sumi,self.kind,self.koumoku)
class Meta:
verbose_name = 'カード払い'
verbose_name_plural = verbose_name
class Admin:
pass

class SyunyuKind(models.Model):
name = models.CharField('名称',maxlength=200)
def __str__(self):
return self.name
class Meta:
verbose_name = '収入の種類'
verbose_name_plural = verbose_name
class Admin:
pass

class Syunyu(models.Model):
koumoku = models.ForeignKey(Koumoku, verbose_name='項目')
kind = models.ForeignKey(SyunyuKind, verbose_name='種類')
def __str__(self):
return "%s %s" % (self.kind,self.koumoku)
class Meta:
verbose_name = '収入'
verbose_name_plural = verbose_name
class Admin:
pass

前借先(Kaesuate)と、銀行の口座(Kouza)を明確に分け、カード払いを示せるように、カード種別(CardKind)とカード払い(Card)のモデルを追加した。また、収入は、金額の±では無く、カード払いと同じように、収入種別(SyunyuKind)と収入(Syunyu)のモデルを追加した。
カード払いも収入も、項目へのリレーションを持つが、逆方向へのリレーションは持たない。
DjangoがDBのJOINを隠蔽してうまいことやってくれることを信じているが、やってくれるのだろうか?

ちなみに、あっちこっちローマ字表記なのは、英語名を考えるのに手が止まるのを嫌ってのことである。
変数名称とかに悩むと、すっごい時間がかかり、モチベーションが下がりまくるので、いんちきローマ字で表すことにした。
後でリファクタリングすりゃいいやとか考えていたが、データベースにデータを入れたまま、モデルを安全に更新する方法が分からない。

基本的に前回まで入れていたテストデータはまっさらに消えた。

Djangoで家計簿を作ろう

家計簿っていうか、将来の収支を期待して先に使ってしまう「前借」という経済的綱渡りを管理したいので、貸借表に近いのだが、今までは表計算ソフトを使っていた。
これが、自分で作っているのに何故か使いづらい。カードの支払いが絡むと良く分からなくなる。自分で作っておいてユーザフレンドリーでない。
で、せっかくなのでDjangoを使ったWebアプリを作ろうって感じで。

まあ、Webアプリにしたからといって使いやすくなるわけじゃないが、Webにしとけば嫁も気軽に参照できるし。
実際には、Webアプリ系家計簿サービスを探したら、ちょうどいいのがなかったので。
ちなみにざっとぐぐると、Django使って2日で家計簿作ったとか、簡単に見つかるが、そんなに早くは出来なさそうです。

Djangoはチュートリアルを参照しながら、管理画面作成まで出来たところ。
作ったモデルは現段階で、こんな感じ。
models.py

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

class Kouza(models.Model):
name = models.CharField(maxlength=200)

def __str__(self):
return self.name

class Admin:
pass

class Koumoku(models.Model):
kouza = models.ForeignKey(Kouza)
siyoubi = models.DateField('使用日',blank=True)
meimoku = models.CharField('名目',maxlength=200)
kingaku = models.IntegerField('金額')
bikou = models.CharField('備考',maxlength=500,blank=True)
kakutei = models.BooleanField('確定')

def __str__(self):
return "%s %s %d - %s | %s" % (self.yotei.strftime('%m/%d'), self.meimoku, self.kingaku, self.bikou, self.kouza)

class Admin:
pass

チュートリアルに従ってシェルで遊んでみたら日本語をMySQLが受け付けなくて困った。
多分文字コードの問題だと思うから、とりあえず気にしないことにした。

Kouzaは口座のことで、前借したものをどの収入で返せるかという分類。たとえばボーナスという口座では、年に2回収入があり、
ボーナスの予定額からあといくら使えるかというのを示す。
Koumokuは項目で、個々の前借を示す。確定フィールドを付ける事で、まだ使用していないが使用する予定の物も示せるようにした。
収入の方をどうするか悩み中。

ライフゲーム(多次元拡張)

前回のライフゲームは2次元限定だが、ライフゲームのロジック自体は1次元や、3次元以上に簡単に拡張できる。
面倒なので表示系の処理は考えないとすると、前回のプログラムから書き換える関数は、positionsのみで良い。
これは、任意の点の周辺(それ自身も含む)の点の集合を返す関数であるから、与えられた点の次元に応じて返す集合を変えればよい。

#1次元ライフゲームバージョン
def positions(point):
for dx in (-1,0,1):
yield (point[0]+dx,)

もちろん、誕生/死滅の閾値が2次元のままだと動かない。
これを1次元、2次元、3次元の場合と作っていけばいいわけだが、まあ一々書けないので再帰的に処理すると次のようになる。

def positions(point,pos=None,axis=0):
"""指定された任意の次元の点から、その周辺(指定された点を含む)を
示す点のタプルを生成するジェネレータ

1次元の点からその周辺領域を生成
>>> list( positions((1,)))
[(0,), (1,), (2,)]

2次元の点からその周辺領域を生成
>>> list( positions((1,1))) #doctest: +NORMALIZE_WHITESPACE
[(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)]

3次元の点からその周辺領域を生成
>>> list( positions((1,1,1))) #doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
[(0, 0, 0), (0, 0, 1), (0, 0, 2),
(0, 1, 0), ...,
(1, 1, 1),
..., (2, 1, 2),
(2, 2, 0), (2, 2, 1), (2, 2, 2)]
"""
if not pos:
pos = list(point)
if axis == len(pos):
yield tuple(pos)
else:
for delta in (-1,0,+1):
pos[axis] = point[axis] + delta
for p in positions(point, pos, axis+1):
yield p

関数の次の行から始まっているのはPythonで特徴的なテスト記法であるdoctestです。
最初に受け取った点の座標をリストに変換後、各項に+1,0,-1の演算を施して周辺集合を返す。ジェネレータにしてあります。
ちなみに、3次元までしか確認していないし、2次元以外ではライフゲームの動作も確認していませんが。

ライフゲーム

時々ふとライフゲームを作りたくなる。
Cでも書いた。Javaでも書いた。ならばPythonでも書かねばならぬだろう。


def born_cell(point,cells):
for pos in positions(point):
if pos not in cells:
cells[pos] = {'own':0,'side':0}
if pos == point:
cells[pos]['own'] += 1
else:
cells[pos]['side'] += 1

def positions(point):
for dx in (-1,0,1):
for dy in (-1,0,1):
yield (point[0]+dx, point[1]+dy)

def next(cells):
new_cells={}
for pos,cell in cells.iteritems():
if (cell['own'] > 0 and cell['side'] == 2) or cell['side'] == 3:
born_cell(pos,new_cells)
return new_cells

def lifegame(cells):
yield cells
while 1:
cells = next(cells)
yield cells

def create(init_str):
new_cells={}
x,y = 0,0
for line in init_str.splitlines():
for pos in line:
if pos=='o':born_cell((x,y),new_cells)
x += 1
y += 1
x = 0
return new_cells

def view(cells, lt=(0,0),rb=(9,9)):
"cellsを2次元文字列で表示する"
for y in xrange(lt[1], rb[1]+1):
for x in xrange(lt[0], rb[0]+1):
cell = cells.get((x,y), {'own':0})
if cell['own'] == 1:
print 'o',
else:
print '.',
print

実際にライフゲームの処理を担うのは最初の4つの関数 born_cell, positions, nextそれにlifegameである。
createは文字列形式の初期配置からcellのデータ構造を作成する関数。viewはその逆をして表示する関数である。

以下のように使う。

>>> cells=create("""\
.o.
..o
ooo""")
>>> for cell in lifegame(cells):
view(cell)
print
. o . . . . . . . .
. . o . . . . . . .
o o o . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .

. . . . . . . . . .
o . o . . . . . . .
. o o . . . . . . .
. o . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .

表示はアレだがグライダーが飛んでいく・・・。
ちなみに、セルのデータ構造は配列ではなく、辞書になっているので表示されていなくても無限にグライダーは飛んでいってる。
表示の簡易化のために初期配置を(0,0)にした(0,0)-(9,9)を表示しているだけなので、そこんところを何とかすればメモリの許す限り広大なライフゲームが出来るはず。