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 のページから移動した。

rubyrakeみたいなものはどう作ればいいか?

  • 追記 (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() は単一の式の評価しかできない。

python_vs_ruby.txt · 最終更新: 2022/12/24 11:20 by Takuya Nishimoto
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0