目次
Python vs Ruby
あれこれ考察 (2022)
since 2022-12-24
ruby 3.1.2
irb(main):016:1* def add(a, b) irb(main):017:1* a + b irb(main):018:0> end => :add irb(main):019:0> add 1, 3 => 4 irb(main):020:1* def add_new a, b irb(main):021:1* a + b * 2 irb(main):022:0> end => :add_new irb(main):023:0> add_new 1, 3 => 7
irb(main):024:1* def fizzbuzz(num) irb(main):025:1* num irb(main):026:0> end => :fizzbuzz irb(main):027:0> 1.upto(10) { puts fizzbuzz(_1) } 1 2 3 4 5 6 7 8 9 10 => 1
irb(main):036:1* def fizzbuzz(num) irb(main):037:1* return 'FizzBuzz' if num % 3 == 0 && num % 5 == 0 irb(main):038:1* return 'Fizz' if num % 3 == 0 irb(main):039:1* return 'Buzz' if num % 5 == 0 irb(main):040:1* num.to_s irb(main):041:0> end irb(main):054:0> 1.upto(20) { puts fizzbuzz(_1) } 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz => 1
パターンマッチで書くと
irb(main):080:1* def fizzbuzz(num) irb(main):081:2* case [num % 3, num % 5] irb(main):082:2* in [0, 0] irb(main):083:2* 'FizzBuzz' irb(main):084:2* in [0, *] irb(main):085:2* 'Fizz' irb(main):086:2* in [*, 0] irb(main):087:2* 'Buzz' irb(main):088:2* else irb(main):089:2* num.to_s irb(main):090:1* end irb(main):091:0> end => :fizzbuzz irb(main):092:0> 1.upto(20) { puts fizzbuzz(_1) } 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz => 1
パターンマッチを Python 3.11 で試すと
>>> def fizzbuzz(num): ... match [num % 3, num % 5]: ... case [0, 0]: ... return 'FizzBuzz' ... case [0, *_]: ... return 'Fizz' ... case [*_, 0]: ... return 'Buzz' ... case _: ... return str(num) ... >>> for num in range(1, 21): ... print(fizzbuzz(num)) ... 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz
あれこれ考察 (-2012)
リストのreverse
ruby にも python にもリストに reverse メソッドがある。
ただし ruby の reverse! の挙動に相当するのが python の reverse() である。
Python
>>> a = [1,2,3] >>> a.reverse() >>> a [3, 2, 1]
Ruby
[1,2,3].reverse # => [3, 2, 1] a = [1,2,3] a.reverse! # => [3, 2, 1] a # => [3, 2, 1]
「すべてがオブジェクト」について
どちらも「すべてがオブジェクト」と言われているが。。
- Python
- 名前がついているものは基本的にオブジェクト。
- 関数やメソッドもただのオブジェクトなので、それを呼び出すためには()というメソッドコールの演算子が必要。
- Ruby
- 名前がついているものは基本的にメッセージ(メソッド呼び出し)。なのでメソッドコールの演算子は必須ではない。
- インスタンスのプロパティを直接参照する方法はなく、アクセッサーを作ってアクセッサーを呼ぶ。attr_accessor は @name に対して def name() と def name=() を行うことと等価。引数はプロパティの名前をシンボルとして与える。
- スコープの制御は先頭の記号で。先頭に @, @@, $ がついていれば非ローカルのスコープ。
メソッドコール演算子() についての Python の考え方は C 言語に近い。オブジェクト指向的だと言われている R 言語なども Python 的な立場。
文字列がミュータブルかどうか
- Python : 文字列はイミュータブル。別の変数に代入すると、別々に管理される。
- Ruby : 文字列はrubyではミュータブル。明示的に複製したいときは dup メソッド。C 言語の char[] と同じ感覚。
Ruby
以下は問題ないが。。
irb(main):001:0> a = "abc" => "abc" irb(main):002:0> b = a => "abc" irb(main):003:0> a = "de" => "de" irb(main):004:0> b => "abc"
以下でちょっとびっくりすることに。
irb(main):019:0> a = "abc" => "abc" irb(main):020:0> b = a => "abc" irb(main):021:0> a[0..0] = 'A' => "A" irb(main):022:0> a => "Abc" irb(main):023:0> b => "Abc"
あるいはこんな例も。
irb(main):008:0> a = "abc" => "abc" irb(main):009:0> b = a => "abc" irb(main):010:0> a.reverse! => "cba" irb(main):011:0> a => "cba" irb(main):012:0> b => "cba"
Python : スライスには代入できない。
>>> a = "abc" >>> b = a >>> a[0] = 'A' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'str' object does not support item assignment
Python チュートリアルに基づく比較
以前書いたブログエントリー http://d.hatena.ne.jp/nishimotz/20090114/1231942324 を再掲する。
「Python チュートリアル」という本を読み、同じことを Ruby でやったら、と思って書きかけたエントリを、 この日記 に刺激されて、第3章「気楽な入門編」の部分だけ、とりあえず公開することにしました:
- ruby 1.8 / python 2.6 で確認。
- TODO: ruby 1.9 や python 3.0 についても要検討。
電卓として使う
Python
$ python >>> 2+2 4
Ruby
$ irb irb(main):001:0> 2+2 => 4
- 以下、irb のプロンプトは略記
複素数
Python
>>> a=1.5+0.5j >>> a.real 1.5 >>> a.imag 0.5 >>> abs(a) 1.58113...
- a.abs とはできない('complex' object has no attribute 'abs')
Ruby
> require 'complex' > a = Complex(1.5, 0.5) > a.real => 1.5 > a.imag => 0.5 > a.abs => 1.58113...
- abs は Python ではビルトイン関数だが Ruby では Complex オブジェクトのメソッド
文字列
Python
>>> '\"Yes,\" he said.' '"Yes," he said.' >>> '"Isn\'t," she said.' '"Isn\'t," she said.' >>> "'Isn\'t,' she said." "'Isn't,' she said."
- \ は常にエスケープ記号。
- ' 'の場合は' 'で表示され、" "の場合は" "で表示される。内部的な区別あり?
Ruby
> '\"Yes,\" he said.' => "\\\"Yes,\\\" he said." > '"Isn\'t," she said.' => "\"Isn't,\" she said." > "'Isn\'t,' she said." => "'Isn't,' she said."
- \ は "" の場合のみエスケープ記号。
- 常に "" で表示される。内部的な区別がない?
Python
>>> hello = "multi-line string\n\ ... second line\n\ ... third line with indent" >>> print hello multi-line string second line third line with indent >>>
Ruby
> hello = "multi-line string " second line " third line with indent" => "multi-line string\nsecond line\n third line with indent" > puts hello multi-line string second line third line with indent => nil >
- irb ではコンテクストに応じてプロンプトが変わる
Python
>>> print """line 1 ... line 2 ... """ line 1 line 2 >>>
Ruby
> puts """line 1 " line 2 " """ line 1 line 2 => nil
- トリプルクオートは Ruby でも使える(ように見える)
- 実は "" + " として解釈されているらしい(このエントリのコメント参照)
- 最後の改行の扱いが Python と Ruby で異なる
- puts は返り値 nil を返す
Ruby
> puts <<EOS " line 1 " line 2 " EOS line 1 line 2 => nil
- 念のためにヒアドキュメントも実験。最初の行の直前の改行の扱いがトリプルクオートと異なる。
文字列の結合
Python
>>> word = 'a' + 'b' >>> word 'ab' >>>
Ruby
> word = 'a' + 'b' => "ab" > word => "ab" >
Python
>>> 'str' 'ing' 'string' >>>
Ruby
> 'str' 'ing' => "string" >
- 隣接する2つの文字列リテラルは Ruby でも自動的に連結される
文字列のインデックス付け
Python
>>> 'abcdefg'[4] 'e'
- Python にはキャラクタ型は存在せず、1文字のキャラクタは長さ1の文字列。
Ruby
> 'abcdefg'[4] => 101
- Ruby (1.8) の文字列は文字コード列であることを意識しなくてはならない。C 言語に似ているとも言えるが、うっかり文字列が得られると思いこみがち。
- Python における 'abcdefg'[4] と同じようにインデックス4の文字から成る新たな文字列を得るには。。
- self[nth,len] : nthバイト番目から長さlenバイトの部分文字列を作成
> 'abcdefg'[4, 1] => "e"
- self[first..last] : インデックス first から last までのバイトを含む文字列を作成
> 'abcdefg'[4..4] => "e"
- self[first…last] : 文字列先頭を0番目の隙間として、fist 番目の隙間から last 番目の隙間までに含まれるバイト列を含んだ新しい文字列
> 'abcdefg'[4...5] => "e"
文字列のスライス
Python
>>> 'abcdefg'[0:2] 'ab' >>> 'abcdefg'[1:3] 'bc' >>> 'abcdefg'[:3] 'abc' >>> 'abcdefg'[3:] 'defg'
Ruby
> 'abcdefg'[0...2] => "ab" > 'abcdefg'[1...3] => "bc" > 'abcdefg'[0...3] => "abc" > 'abcdefg'[3...-1] => "def" > 'abcdefg'[3...0] => "" > 'abcdefg'[3..-1] => "defg"
- Python における : でのスライスは Ruby では … とほぼ同じ。
- ただし Python における [3:] は Ruby では [3..-1] となる。
- Ruby では .. / … は Range というオブジェクトである。
- .. 演算子で生成されれば終端を含む。… 演算子で生成されれば終端を含まない。
インデックスにおける負の数
Python
>>> 'abcdefg'[-1] 'g' >>> 'abcdefg'[-2] 'f' >>> 'abcdefg'[-2:] 'fg' >>> 'abcdefg'[:-2] 'abcde'
Ruby
> 'abcdefg'[-1] => 103 > 'abcdefg'[-1,1] => "g" > 'abcdefg'[-2,1] => "f" > 'abcdefg'[-2..-1] => "fg" > 'abcdefg'[0..-2] => "abcdef" > 'abcdefg'[0...-2] => "abcde"
最初と最後の文字の除去
since 2012-05-08
Ruby の s[1..-2] は Python では s[1:-1] と書く:
$ irb ruby-1.9.2-p290 :001 > "[title]"[1..-1] => "title]" ruby-1.9.2-p290 :002 > "[title]"[1..-2] => "title" $ python2.7 Python 2.7.2 (default, Jan 13 2012, 17:11:09) >>> print "[title]"[1:-1] title
文字列の長さ
Python
>>> len('abcdefg') 7 >>> 'abcdefg'.len AttributeError: 'str' object has no attribute 'len'
- Python ではビルトイン関数 len を使う
- len という変数名はうっかり使いたくなるので注意
Ruby
> 'abcdefg'.length => 7 > 'abcdefg'.len NoMethodError: undefined method `len' for "abcdefg":String > length('abcdefg') NoMethodError: undefined method `length' for main:Object
- Ruby では String オブジェクトのメソッド length を使う
マルチバイト文字列
- UTF-8 でソースを記述して、Python 2.6 / Ruby 1.8.6 の Win32 版で実行。
Python
#!/usr/bin/env python # -*- coding: utf-8 -*- print u'あいうえお' print u'あいうえお'[2] print len(u'あいうえお')
C:\>python mbstring.py あいうえお う 5
Ruby
puts 'あいうえお' puts 'あいうえお'[2] puts 'あいうえお'.length
縺ゅ>縺・∴縺 130 15
- 望ましい挙動を実現する例(1)
#!/usr/bin/env ruby -Ku # -*- coding: utf-8 -*- require 'iconv' class String def to_sjis Iconv.conv('shift-jis', 'utf-8', self) end def chars self.split(//) end end puts 'あいうえお'.to_sjis puts 'あいうえお'.chars[2].to_sjis puts 'あいうえお'.chars.length
C:\>ruby mbstring.rb あいうえお う 5
- 望ましい挙動を実現する例(2)
#!/usr/bin/env ruby -Ku # -*- coding: utf-8 -*- require 'iconv' class String def chars self.split(//) end end module Kernel alias_method :orig_puts, :puts def puts(s) orig_puts Iconv.conv('shift-jis', 'utf-8', s.to_s) end end puts 'あいうえお' puts 'あいうえお'.chars[2] puts 'あいうえお'.chars.length
C:\>ruby mbstring.rb あいうえお う 5
リスト
Python
C:\>python >>> a = ['ab', 'cd', 123, 45] >>> a ['ab', 'cd', 123, 45]
Ruby
C:\>irb > a = ['ab', 'cd', 123, 45] => ["ab", "cd", 123, 45]
個別の要素の操作
Python
>>> a[2] = a[2] + 23 >>> a ['ab', 'cd', 146, 45] >>> a[2] += 23 >>> a ['ab', 'cd', 169, 45]
Ruby
> a = ['ab', 'cd', 123, 45] => ["ab", "cd", 123, 45] > a[2] += 23 => 146 > a => ["ab", "cd", 146, 45] > a[2] = a[2] + 23 => 169 > a => ["ab", "cd", 169, 45]
要素の置換と挿入
Python
>>> a ['ab', 'cd', 169, 45] >>> a[0:2] = [111,222] >>> a [111, 222, 169, 45] >>> a[1:1] = ['ab', 'cd'] >>> a [111, 'ab', 'cd', 222, 169, 45]
Ruby
> a => ["ab", "cd", 169, 45] > a[0...2] = [111,222] => [111, 222] > a => [111, 222, 169, 45] > a[1...1] = ['ab', 'cd'] => ["ab", "cd"] > a => [111, "ab", "cd", 222, 169, 45]
- 文字列と同じく : は … に置き換えられる
リストの入れ子
Python
>>> q = [2,3] >>> p = [1,q,4] >>> p [1, [2, 3], 4]
Ruby
> q = [2,3] => [2, 3] > p = [1,q,4] => [1, [2, 3], 4]
- ここまでは全く同じ
Python
>>> p [1, [2, 3], 4] >>> len(p) 3 >>> p[1].append('xtra') >>> p [1, [2, 3, 'xtra'], 4]
Ruby
> p => [1, [2, 3], 4] > p.length => 3 > p[1].append('xtra') NoMethodError: undefined method `append' for [2, 3]:Array > p[1] << 'xtra' => [2, 3, "xtra"] > p => [1, [2, 3, "xtra"], 4] > p[1].push 'xtra' => [2, 3, "xtra", "xtra"] > p => [1, [2, 3, "xtra", "xtra"], 4]
- Python のビルトイン関数 len は Ruby では length メソッド
- Python の append メソッドは Ruby では « または push メソッド
プログラミングの基礎
Python
>>> a, b = 0, 1 >>> while b < 10: ... print b ... a, b = b, a+b ... 1 1 2 3 5 8 >>>
Ruby
> a, b = 0, 1 => [0, 1] > while b < 10 do * puts b > a, b = b, a+b > end 1 1 2 3 5 8 => nil >
- Python はブロックをインデントで表現する
- Ruby はブロックを do end で表現する
- Python の print は Ruby では puts
Python
>>> a, b = 0, 1 >>> while b < 1000: ... print b, ... a, b = b, a+b ... 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 >>>
Ruby
irb(main):035:0> a, b = 0, 1 => [0, 1] irb(main):036:0> while b < 1000 do irb(main):037:1* print b irb(main):038:1> a, b = b, a+b irb(main):039:1> end 1123581321345589144233377610987=> nil irb(main):040:0> irb(main):050:0> a, b = 0, 1 => [0, 1] irb(main):051:0> while b < 1000 do irb(main):052:1* print "#{b} " irb(main):053:1> a, b = b, a+b irb(main):054:1> end 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 => nil irb(main):055:0>
- Python の "print ," のような自動空白挿入は Ruby の print では行われない
識別子
Python:大文字と小文字の区別はない。
identifier ::= (letter|"_") (letter | digit | "_")* letter ::= lowercase | uppercase lowercase ::= "a"..."z" uppercase ::= "A"..."Z" digit ::= "0"..."9" >>> HOST = '' >>> HOST = '123' >>> HOST = 3
Ruby:大文字で始まるものは定数。
c:\work>irb irb(main):001:0> HOST = 'hoge' => "hoge" irb(main):002:0> HOST = 'hogehoge' (irb):2: warning: already initialized constant HOST => "hogehoge" irb(main):003:0>
文字列から数値への変換
c:\>python Python 2.6 (r26:66721, Oct 2 2008, 11:35:03) [MSC v.1500 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> int("100") 100 >>> float("1.2") 1.2 >>>
c:\>irb irb(main):001:0> "100".to_i => 100 irb(main):002:0> "1.2".to_f => 1.2
乱数
Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on win32 >>> import random >>> random.random() 0.27083066525160682
ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-mingw32] c:\>irb irb(main):002:0> rand => 0.370605840255809
実装例の比較
とある CSV 形式のデータを加工する処理を、Ruby で実装し、Python に移植してみた。使用した処理系は下記。
- Ruby 1.8.6 (cygwin)
- Python 2.5.1 (cygwin)
ファイルを読み込んで行ごとの処理をブロックの中で行う:
- Ruby では File.open().each_line { |line| }
- Python では for line in open(file):
#!/usr/bin/ruby -Ku # calc.rb # by nishimoto class Subject attr_reader :tasks attr_reader :wwls def initialize @tasks = {} @wwls = {} end def to_s s = "" %w[ T1 T2 ].each do |idx| s += "," + sprintf("%.2f", @tasks[idx] * 100.0 / 75.0) end %w[ A B ].each do |idx| s += ",#{@wwls[idx]}" end s end end subjects = {} Dir.glob("input-*.csv").each do |file| File.open(file).each_line do |line| ar = line.chomp.split(/,/) task, name = ar[0], ar[-1] next if name == 'name' unless subjects[name] subjects[name] = Subject.new end unless subjects[name].tasks.has_key?(task) subjects[name].tasks[task] = 0 end if ar[3] != '' and ar[3] == ar[4] subjects[name].tasks[task] += 1 end end end Dir.glob("wwl-*.csv").each do |file| File.open(file).each_line do |line| ar = line.chomp.split(/,/) name, t, wwl = ar[0], ar[1], ar[-1] next if name == 'G' subjects[name].wwls[t] = wwl end end File.open("_calc091026.csv", "w") do |file| puts "S,T1,T2,WWL-T1,WWL-T2" file.puts "S,T1,T2,WWL-T1,WWL-T2" subjects.keys.sort.each do |k| puts k + subjects[k].to_s file.puts k + subjects[k].to_s end end system("unix2dos _calc091026.csv")
#!/usr/bin/python # calc.py : ported from calc.rb # by nishimoto class Subject: def __init__(self): self.tasks = {} self.wwls = {} def to_s(self): s = "" for idx in [ 'T1', 'T2' ]: s += "," + "%.2f" % (self.tasks[idx] * 100.0 / 75.0) for idx in [ 'A', 'B' ]: s += "," + self.wwls[idx] return s subjects = {} import glob for file in glob.glob("input-*.csv"): for line in open(file): ar = line.rstrip().split(',') task, name = ar[0], ar[-1] if name == 'name': continue if not subjects.has_key(name): subjects[name] = Subject() if not subjects[name].tasks.has_key(task): subjects[name].tasks[task] = 0 if ar[3] != '' and ar[3] == ar[4]: subjects[name].tasks[task] += 1 for file in glob.glob("wwl-*.csv"): for line in open(file): ar = line.rstrip().split(',') name, t, wwl = ar[0], ar[1], ar[-1] if name == 'G': continue subjects[name].wwls[t] = wwl file = open("_calc091026.csv", "w") print "S,T1,T2,WWL-T1,WWL-T2" file.write("S,T1,T2,WWL-T1,WWL-T2") for k in sorted(subjects.iterkeys()): print k + subjects[k].to_s() file.write( k + subjects[k].to_s() ) import os os.system("unix2dos _calc091026.csv")
Python の with 文
python_with に移動。
python で rakefile もどきを
python のページから移動した。
- 追記 (2010-12-12) scons というツールがある。
依存解消機能はないが rakefile もどきを作る。
task.py pdf とやると pdf() を実行する。
#!/usr/bin/python # task.py doc = "paper" def default(): sh( "platex %s" % doc ) def pdf(): sh( "dvipdfmx %s" % doc ) def commit(): sh( "hg status -A" ) sh( "hg commit -m \"snapshot\"" ) def sh(s): import os; os.system(s) if __name__ == '__main__': import sys if len(sys.argv) == 2: exec("%s()" % sys.argv[1]) else: default()
備考:exec() は文を実行する。eval() は単一の式の評価しかできない。