お題は以前紹介した メディアにある画像ファイルをハードディスクに保存するスクリプトです。 実は我が家のパソコンはほとんどアルバムと化しており、メディアからハードディスクに移すスクリプトは FireFox と Linar 以外では私の家族に一番利用されているプログラムです。 画像ファイルをコピーするのにいちいち変なロゴのエディター (xyzzy のこと) が立ち上がるのは鈍くさいという家族の意見を容れ、普通のスクリプト言語で書くことにしました。
01: #! perl 02: 03: use strict; 04: use File::Copy; 05: use File::Compare; 06: use File::Find; 07: use Cwd; 08: 09: ## global parameters 10: my $DOC_DIR = 'D:/doc'; 11: my $MEDIA ='G:/'; 12: my $PHOTO_VIEWER = 'D:/WBIN/linar160/linar.exe'; 13: 14: # getting the string of "year(NN)-month(NN)" 15: sub get_year_month{ 16: my ($m, $y) = (localtime)[4,5]; 17: sprintf ("%02d-%02d", $y-100, $m + 1); 18: } 19: 20: # getting the starting number of photoNN, the directory in which photos should be saved 21: # This function should be called when the program is in the month directory. 22: sub get_first_photo_dir_number{ 23: my @pdirs = (glob "photo[0-9][0-9]"); 24: @pdirs ? 1+ substr($pdirs[-1], -2) : 1; 25: } 26: 27: #move into the directory "$DOC_DIR/y-m" 28: sub move_into_dir_of_month{ 29: my $dir_of_month = &get_year_month; 30: unless ($DOC_DIR eq cwd){ 31: chdir $DOC_DIR or die "Cannot move to $DOC_DIR: $!"; 32: } 33: unless (-d $dir_of_month){ 34: mkdir $dir_of_month or die "cannot create $dir_of_month: $!"; 35: } 36: chdir $dir_of_month or die "Cannot move to $dir_of_month: $!"; 37: "$DOC_DIR/$dir_of_month" ; 38: } 39: 40: #archive photos in the media into the HD 41: # This function should be called when the program is in the month directory. 42: sub archive_photos{ 43: my $photo_dir_number = shift; 44: my %dhash; 45: find({ 46: wanted => sub{push @{$dhash{$File::Find::dir}}, $_ if -f}, 47: }, $MEDIA); 48: for my $dir_from (sort keys %dhash){ 49: my $n = @{$dhash{$dir_from}}; 50: my $i = 0; 51: my $dir_to = sprintf("photo%02d", $photo_dir_number++); 52: mkdir $dir_to or die "cannot create $dir_to: $!"; 53: print "\n$dir_from ==> $dir_to\n"; 54: for my $fname (@{$dhash{$dir_from}}){ 55: my $copy_from = "$dir_from/$fname"; 56: my $copy_to = "$dir_to/$fname"; 57: copy($copy_from, $copy_to) or die "cannot make a copy for $copy_from: $!"; 58: if(0 == compare($copy_from, $copy_to)){ 59: unlink $copy_from; 60: print ++$i, "/$n\r"; 61: }else{ 62: die "an error occurs during coping $copy_from"; 63: } 64: } 65: } 66: %dhash; 67: } 68: 69: #main 70: my $dir_of_month = &move_into_dir_of_month; 71: my $first_photo_dir_number = &get_first_photo_dir_number; 72: if(&archive_photos($first_photo_dir_number)){ 73: exec (sprintf "%s %s/photo%02d", $PHOTO_VIEWER, $dir_of_month, $first_photo_dir_number); 74: }else{ 75: print "No photos in the media!\nGive Return:"; 76: < STDIN> 77: }大体 80 行くらいのコードになります。 リストは Perl のハッシュ表の値にはなれないのでリストを値としたければ、 リストのリファレンスを使う必要があります。 印象としては、awk と sed を混ぜたものをその適応範囲外まで拡張しすぎたという感じです。 崩壊寸前のダムみたいな感じで、いまいち。 ただし、ライブラリは優秀で、実行速度は速いです。
01: #! usr/bin/env python 02: 03: import glob, string, os, os.path, shutil, filecmp, re, sys 04: from datetime import date 05: 06: #global parameters 07: HD = 'D:/doc/' 08: MEDIA = 'G:/' 09: PHOTO_VIEWER = 'D:/WBIN/linar160/linar.exe' 10: PREGEXP = re.compile(".(gif|bmp|jpe?g|tiff?)$", re.I) 11: PHASH = {} 12: 13: def sum(ls): 14: total = 0 15: for x in ls: 16: total += x 17: return total 18: 19: def nPdir(dir): 20: lst = [ x for x in glob.glob(dir + "photo[0-9][0-9]") if os.path.isdir(x)] 21: return lst and int(lst[-1][-2:]) or 0 22: 23: def Search_Media(dir): 24: os.chdir(dir) 25: items = os.listdir(dir) 26: ls = [x for x in items if PREGEXP.search(x)] 27: if ls: 28: PHASH[dir] = ls 29: for d in [dir + x + '/' for x in items if os.path.isdir(x)]: 30: Search_Media(d) 31: 32: def Move_Photos(): 33: md = HD + date.today().strftime("%y-%m/") 34: np = nPdir(md) 35: pt = sum([len(val) for val in PHASH.itervalues()]) 36: sf = 0 37: i0 = True 38: 39: if not pt: 40: print "No photos in the media: give return" 41: sys.stdin.readline() 42: sys.exit() 43: 44: os.chdir(md) 45: 46: for d, fs in PHASH.iteritems(): 47: np += 1 48: pd = md + "photo%02d" % np 49: if i0: 50: pd0 = pd 51: i0 = False 52: os.mkdir(pd) 53: for f in fs: 54: f1 = d + f 55: f2 = pd + '/' + f 56: shutil.copyfile(f1, f2) 57: if filecmp.cmp(f1, f2): 58: os.remove(f1) 59: else: 60: print "copy failed: %s => %s\n" % (f1, f2) 61: sf += 1 62: print "%d/%d\r" % (sf, pt), 63: return [' ', pd0] 64: 65: if __name__=='__main__': 66: Search_Media(MEDIA) 67: os.execv(PHOTO_VIEWER, Move_Photos())大体 70 行になります。長さは大体 Perl で書いたものと同じです。 (このコードは一夜漬けで書いたのでかなりいけてないです。改良版はここにあります。) ソースの見栄えはとても良く、 インデントでブロックを表現するというアイデアは成功していると思います。 煉瓦のような雰囲気できっちりとしています。コーディングの自由度が少ないので、 (1年前の自分も含む)誰が書いても同じようなコードになり、読み取るのは容易です。 そのため、コメントの量も少なくて済み、変数名をコメント代わりに使う必要もありません。 変数はデフォルトで局所変数となるので、 Perl のように my で宣言する必要はありません。 また、リストの内包表現は Lisp の mapcar と remove-if-not が同時に出来るので便利です。 ライブラリは優秀で、実行速度は Perl より速い気がします。
スクリプト言語としての欠点を挙げると、
それから、Python には
対話モードがあり、個々の関数をテストできます。(注1)
この機能は大きめのプログラムを書くときに便利です。
main に相当する部分を、
if __name__=='__main__': のブロックに入れることによって、
Python によって直接読み込まれたとき以外は動作しないようにすることが出来ます。(注2)
個々の関数のテストは次のようにします。
3.3. 最後に Ruby
それでは、 Ruby ではどうなるでしょうか?
01: #! ruby
02:
03: require "FileUtils"
04:
05: #global parameters
06: Doc_dir = "D:/doc/"
07: Media = "G:/"
08: Viewer = "D:/WBIN/linar160/linar.exe"
09: PEXP = /\.(jpe?g|JPE?G|bmp|BMP|tiff?|TIFF?)$/
10: $photo_hash = Hash.new
11:
12: def photo_dir_max(dir)
13: Dir.chdir(dir)
14: d = Dir.glob("photo[0-9][0-9]").select{|f| File.directory?(f)}.last
15: d ? d.slice(5..6).to_i : 0
16: end
17:
18: def search_media(dir)
19: Dir.chdir(dir)
20: files = Dir.glob("*.*").select{|f| f =~ PEXP}
21: $photo_hash[dir] = files unless files == []
22: Dir.glob("*").select{|f| File.directory?(f)}.each{|d| search_media(dir + d + "/")}
23: end
24:
25: def move_photos ()
26: mon_dir = Doc_dir + Time.new.strftime("%y-%m/")
27: p_d_num = photo_dir_max(mon_dir)
28: i = count = 0; p_dir0 = ''
29: n_p_files = lambda{|h| n=0; h.each_key{|k| n += h[k].size}; n}.call($photo_hash)
30: if n_p_files == 0
31: then
32: puts "NO photo files in the media."
33: STDIN.readline
34: exit(0)
35: end
36: $photo_hash.each_key{|d|
37: p_d_num += 1
38: p_dir = mon_dir + sprintf("photo%02d/", p_d_num)
39: if i == 0 then p_dir0 = p_dir; i += 1 end
40: FileUtils.mkdir(p_dir)
41: $photo_hash[d].each{|f|
42: f1, f2 = d + f, p_dir + f
43: FileUtils.cp(f1, f2)
44: FileUtils.cmp(f1, f2) ? File.delete(f1) : printf("Copy failed: %s => %s\n", f1, f2)
45: count += 1
46: printf("%d/%d\r", count, n_p_files)
47: }
48: }
49: p_dir0
50: end
51:
52: search_media(Media)
53: exec(Viewer, move_photos())
長さは大体 50 行となり、3つの中で一番短くなります。
ソースの見栄えも悪くなく、データがピリオドの前から、後ろに
流れていくような感じです。ちょうど、Lisp コードが括弧の中から外にデータが流れるように
見えるのと同じ雰囲気です。
また、全てがメソッドなのですっきりしています。
問題は、遅いことです。
このプログラムではメディアからの読み出しが律速のはずなのに、ほかの2つに比べて随分遅いです。
これは、perl や python がバイトコンパイルしてから実行するのに対し、ruby が純粋なインタープリタで、
プログラムを1つ1つ解釈しながら実行することによると思います。
(これについてはお前の書き方が悪いとの指摘を受けました。
詳しくはここを見てください。
また、改良版はここにあります。
しかし、紫藤のプログラムのいけてない点は律速ではなく、改良版も perl や python に比べて遅いです。)
言語そのものは悪くないのに残念です。
遅いとストレスが溜まるので今回は見送り。
項目 | Perl | Python | Ruby |
---|---|---|---|
書きやすさ | ○ | ○ | ○ |
読みやすさ | △ | ◎ | ○ |
ライブラリー | ○ | ○ | △ |
実行速度 | ○ | ○ | △ |
ドキュメント | ◎ | ○ | △ |
ユーザー数 | ◎ | ○ | △ |
現在の人気を無視して、言語そのもののよしあしを考えると、 Python と Ruby はほぼ互角でしょう。しかし、今のところ Python の方が、 実行速度が速く、ライブラリが豊富なので、とりあえず Python を使うことにします。 また、Python には対話モードがあるのも Lisp に慣れた人間にとってはありがたいです。 Ruby は今後の健闘に期待します。Ruby を使うと他の2つよりコードが2割以上短くなるので 素質は十分だと思います。 (上の3つの例は、コードの長さの目安を得るために通常の書き方より少しつめて書きました。)
Perl や Ruby は純粋なスクリプト言語で、(もちろんそれなりに大きなプログラムも書けるようにはなっているものの) プログラムを短くすることに主眼が置かれています。 一方、Python はスクリプト言語としてもつかえる大規模プログラム作成言語で、デバックの容易さに主眼が 置かれています。プログラミング言語は適応範囲が広いほど学ぶ価値があるので、 その意味で Python を学ぶことは Perl や Ruby を学ぶより有用だと思います。 (このことから google が Perl や Ruby でなく、 Python を使っている 理由が分かるような気がします。)
ここにある内容は一部Python 早めぐりと重複しています。 Python のポリシーについては A morality tale of Perl versus Python (和訳) やThe Zen of Python (和訳)を見てください。
例)要素が非負の実数の場合、その平方根を返す。 [-3,-2,-1,0,1,2,3] ⇒ [0.0, 1.0, 1.4142135623731, 1.73205080756888]
01: # Perl 5 02: my @ls0=(-3,-2,-1,0,1,2,3); 03: my @ls1=(); 04: for (@ls0){ 05: push @ls1, sqrt($_) if $_ >= 0; 06: } 07: print "$_\n" for (@ls1); 08: 09: # Ruby 10: p [-3,-2,-1,0,1,2,3].select{|x| x>=0}.map{|x| Math.sqrt(x)} 11: 12: # Python 13: import math 14: print [math.sqrt(x) for x in [-3,-2,-1,0,1,2,3] if x>=0]
例)累積機の生成。 (数nを取り、「数iを取ってnをiだけ増加させ、その増加した値を返す関数」を返すような関数)
01: # Perl 5 02: sub foo { 03: my ($n) = @_; 04: sub {$n += shift} 05: } 06: 07: # Python 08: class foo: 09: def __init__(self, n): 10: self.n = n 11: def __call__(self, i): 12: self.n += i 13: return self.n 14: 15: # Ruby 16: def foo (n) 17: lambda {|i| n += i } 18: end使用例
01: >>> a=foo(10) 02: >>> a(3) 03: 13 04: >>> a(5) 05: 18
注2:
perl や ruby でもできるそうですが、この機能は、モジュールと通常のスクリプトの書式が同じであるという
python の特徴があって初めて威力を発揮すると思います。
perl や ruby でこの機能を有効に使った例をご存知の方はお教えください。