MAGAZINE

ルーターマガジン

Python

RubyをPythonと比較してみて

2021.10.05
Pocket

今回,業務に携わるにあたってRubyに初めて触れました.

そこで,Rubyの使用感をPythonと比較しながら,簡単にまとめてみました.

Rubyとは

そもそもRubyとはなんなのか.Wikipediaを参考にすると以下のような紹介がされています.

"Ruby(ルビー)は、まつもとゆきひろ(通称: Matz)により開発されたオブジェクト指向スクリプト言語(スクリプト言語とはプログラミング言語の一分類)。日本で開発されたプログラミング言語としては初めて国際電気標準会議(IEC)で国際規格に認証された事例となった。"

Wikipediaより

要するに,最も有名な国産プログラミング言語がRubyです.

最近,日本語プログラミング言語である「なでしこ」が中学校の教科書で採用されたというニュースを耳にしました.なでしこやRubyのような国産プログラミング言語の利点として,コミュニティやライブラリの情報が日本語で入手しやすい点が挙げられます.

以上の理由から,Rubyは日本人プログラマとして習得しやすい言語の1つではないかと思います.

Rubyの利点

先ほど,Rubyは国産言語なので学習するべきと言いましたが,Rubyの利点は国産であるというだけではありません.

個人的に良いと思った点を主にPythonと比較しながら挙げていきたいと思います.

対話型実行:irb(Interactive Ruby)

最初に,デバッグに有用なirb(Interactive Ruby)機能について紹介します.

irbの利点を説明する導入として,まずプログラミング言語の種類について簡単に説明していきます.プログラミング言語には,コンパイル型言語とインタプリタ型言語という分類の仕方があります.前者は,始めに人間の書いたプログラムコードをコンパイル(マシン語に翻訳し実行ファイルとして出力)します.その後にコンパイルされた実行ファイルを実行することでプログラムを実現します.C言語やJavaなどがこれに分類されます.後者は入力されたプログラムコードを逐次翻訳しながらプログラムを実行していきます.RubyやPythonがこれに該当します.今回はこのインタプリタ型言語の特性を利用したirbについて説明します.

例えば,局所的な範囲でのプログラムのふるまいを検証したいケースを想定します.具体的な例として,割り算を行う際に,1/3, 1/3.0, 1.0/3 ではそれぞれ計算結果がどうなるか知りたいとします.このようなケースをコンパイル型言語で検証するためには,基本的に,プログラムを実行するためのおまじないを何行も書き,それをコンパイルし,生成された実行ファイルを実行することで,これらの挙動の違いを知ることができます.しかし,rubyの場合,5秒でこれらの検証を完了できます.

ここで用いるのがirbコマンドです.ターミナルで「irb」と打つと以下のような表示になり 「>」以後でrubyのプログラムを受け付けます.

$ irb 
irb(main):001:0>

実際にirbで先ほどの疑問を検証すると,次のようになり,簡単にプログラムの検証を行うことができます.

$ irb 
irb(main):001:0> 1/3
=> 0
irb(main):002:0> 1/3.0
=> 0.3333333333333333
irb(main):003:0> 1.0/3
=> 0.3333333333333333

このような対話型の実行環境のことをREPL(Read-Eval-Print Loop)と呼び,REPLはRubyの他に同じくインタプリタ型であるPythonなどが標準でサポートしています.

破壊的メソッド

rubyにはメソッド名の最後に "!" を含むメソッドがあり,これらは破壊的メソッドと呼ばれています.破壊的メソッドはその名の通り,そのメソッドを呼び出したオブジェクト自体の値を破壊し(変化させ)ます.実際の挙動を例にとって説明していきます.

gsubメソッドとgsub!メソッドの違い

文字列から任意の文字列を置換するメソッドであるgsubメソッドとその破壊的メソッドであるgsub!を例に取ります.以下の例でそれぞれの挙動の違いを確認すると,両者の違いがハッキリします.2つ目と3つ目の実行を見ると,最初のgsubメソッドは非破壊的メソッドであるため,呼び出し元のfruitオブジェクトの中身は"banana"のままです.対して,4つ目と5つ目の実行を見ると,gsub!メソッド実行後には呼び出し元のfruitオブジェクトの中身も変化し,"binini"となっています.これが破壊的メソッドのふるまいになります.

$ irb
irb(main):001:0> fruit = "banana"
irb(main):002:0> puts fruit.gsub("a", "i")
binini
irb(main):003:0> puts fruit
banana
irb(main):004:0> puts fruit.gsub!("a", "i")
binini
irb(main):005:0> puts fruit
binini

破壊的メソッドが便利なケース ~Python と比較~

それではどのような時にこの破壊的メソッドが便利なのか,例を挙げて説明していきます.

ランダムな値の整数10個でコラッツ問題が正しいかを検証するRubyプログラムを以下に記しました.

#繰り返し操作
def calculate(num)
  if num.even?
    # 偶数の場合: numを2で割る
    return num / 2
  else
    # 奇数の場合: numに3をかけて1を足す
    return num * 3 + 1
  end
end

#ランダムな整数を10個生成し,配列に格納
p num_list = Array.new(10){rand 100}
loop do
  # 配列の各値を計算
  p num_list.map!{|num| calculate(num)}     # !のつく破壊的メソッド
  # 値が1のものを削除
  num_list.delete(1)     # !のつかない破壊的メソッド
  # num_listの中身が無くなったら終了
  break if num_list.empty?
end

今回注目すべきは

p num_list.map!{|num| calculate(num)}     # !のつく破壊的メソッド

num_list.delete(1)     # !のつかない破壊的メソッド

の2行です.

1つ目のmap!メソッドは "!" がついており,破壊的メソッドとして,num_list配列の各値をcalculateメソッドの返り値に置き換えています.加えて2つ目のdeleteメソッドも "!" はついていませんが,破壊的メソッドとしてnum_list配列を元の配列から値が1のものを削除した配列に変化させています.このように "!" がつかない破壊的メソッドもあるため,注意が必要です.

この二つのメソッドは共にnum_listオブジェクトから呼び出されており,この例の様にループなどでオブジェクトの中身を何度も変化させるような場合に破壊的メソッドは有用です.仮に破壊的メソッドを用いずに同じ操作をするプログラムを書こうとすると,以下のようにnum_listにnum_listを変化させたものを代入する書き方になり,冗長になります.

num_list = num_list.map{|num| calculate(num)} 

実際に同じプログラムをPythonで書くと以下のようになるかと思います.

import random

def calculate(num):
  if num % 2 == 0:
    # 偶数の場合: numを2で割る
    return num / 2
  else:
    # 奇数の場合: numに3をかけて1を足す
    return num * 3 + 1

num_list = [random.randint(1, 100) for i in range(10)] 
print (num_list)
while True:
  # 配列の各値を計算
  num_list = list(map(calculate, num_list))
  print (num_list)
  # 値が1のものを削除
  num_list = [num for num in num_list if num != 1]
  # num_listの中身が無くなったら終了
  if len(num_list) == 0: break

先ほど挙げた2行に該当する以下の2行に注目します.

num_list.map!{|num| calculate(num)}

-> num_list = list(map(calculate, num_list))

num_list.delete(1)

-> num_list = [num for num in num_list if num != 1]

Rubyと比較するとRubyの方がスッキリしていて,可読性が高いと感じると思います.(好みの問題もあると思いますが...)

ぼっち演算子 (&.)

Rubyには,ぼっち演算子と呼ばれる演算子があります.正確には safe navigation operator と言い,nilオブジェクトが何らかのメソッドを呼び出す操作を行った際にNoMethodErrorを回避する役割を担ってくれます.

まず,Rubyにおけるメソッド呼び出しからおさらいすると,Rubyでのメソッド呼び出しは以下のように「.」を用いて呼び出されます.

オブジェクト.メソッド

この時,呼び出し元のオブジェクトに該当するメソッドがあれば,そのメソッドが呼び出されます.しかし,該当するメソッドが存在しない場合にはNoMethodErrorが投げられ,そんなメソッドは存在しないと言われてしまいます.

以下に示すプログラムを用いて説明していきます.nil オブジェクトには nil? メソッド(オブジェクトが nil か否かを返してくれるメソッド)が存在し,2行目のようにメソッドを呼び出すと,true(nil オブジェクトである)という返り値を返してくれます.次に,3つ目の命令文をみると,objメソッドが lengthメソッドを呼び出していますが,nilオブジェクトにはそんなメソッドは存在しないので,NoMethodErrorが生じてしまっています.これを回避するためにぼっち演算子を使用します.4つ目の命令文のように,メソッド呼び出しを行う「.」の前にアンパサンド(&)を書くと,NoMethodErrorの代わりにnilを返してくれます.

irb(main):001:0> obj = nil
irb(main):002:0> p obj.nil?
true
irb(main):003:0> p obj.length
Traceback (most recent call last):
        ...
NoMethodError (undefined method 'length' for nil:NilClass)
irb(main):004:0> p obj&.length
nil

ぼっち演算子が便利なケース ~Python と比較~

設計上,静的なオブジェクトのメソッド呼び出しにおいてこのようなエラーは起きることは少ないですが,メソッド呼び出し元のオブジェクトが動的な場合にはこのようなエラーが発生してしまうケースは多々あります.このような設計のプログラムの場合にぼっち演算子を使用すると,オブジェクトがnilである場合の場合分けを記述しなくて良いので,スッキリしたコードを書くことができます.

具体例として,表データを読み込んで各フィールドに対して処理を行い,処理後の値を格納し直したデータをアウトプットしたい状況を想定します.この際に,フィールドが空(null)である場合があります.空のフィールドに対してオブジェクトが存在する時の処理をそのまま行うと問題が生じるため,nullである場合の条件分岐を書き加える必要があります.

空のオブジェクトを含む配列の各値に処理を施すコード例を用いて,ぼっち演算子の利点を説明していきます.まず,以下に示す Python コード例から見ていくと,4行目で各値に対してreplaceメソッドを呼び出していますが,Noneオブジェクトはreplace属性(Attribute)を持っていないため,AttributeErrorが発生します.そのため,これを回避するために値がNoneであるか否かの場合分けを行う必要があり,それを記述したものが5行目になります.

arr = ["banana", "apple", None]
new_arr = []
for num in arr:
  # new_arr.append(num.replace("a", "i"))     #AttributeError発生
  new_arr.append(None if num is None else num.replace("a", "i"))     #Noneか否かによる場合分け

上記のPythonコード例と同様のふるまいをするRubyコードを以下に記しました.Pythonのコード例と同様に4行目で各値に対してgsubメソッドを呼び出していますが,nilオブジェクトは gsubメソッドを持たないため,NoMethodErrorが発生します.Rubyでは,これを回避するための場合分けを行う代わりに,ぼっち演算子を用いることで対処することができます(5行目).

arr = ["banana", "apple", nil]
new_arr = []
arr.each do |num|
  # new_arr.push(num.gsub("a", "i"))     #NoMethodError発生
  new_arr.push(num&.gsub("a", "i"))     #ぼっち演算子使用
end

このように比較してみると,ぼっち演算子を使用した方がプログラムがはるかにスッキリすることが分かると思います.Rubyで nilオブジェクトに対して条件分岐を行う際には,是非ぼっち演算子を使いましょう.

標準出力メソッド

最後にRubyの標準出力メソッドについて紹介します.Rubyには豊富な標準出力メソッドがありますが,今回は中でも便利なpメソッドについて紹介します.

irb(main):001:0> str = "文字列"
irb(main):002:0> num = 100
irb(main):003:0> arr = [1, 2, 3]
irb(main):004:0> hash = {"Jan." => 1, "Feb." => 2, "Mar." => 3}
irb(main):005:0> obj = Object.new
irb(main):006:0> p str, num, arr, hash, obj
"文字列"
100
[1, 2, 3]
{"Jan."=>1, "Feb."=>2, "Mar."=>3}
#<Object:0x00007f9c9c12bac0>

pメソッドはあらゆるオブジェクトを引数にとり,それを表示します.例のようにString型以外のオブジェクトも表示してくれて,おまけにどんな型であるかを知ることが出来ます.これはデバッグの時にとても便利で,知りたい変数の文頭にpと1文字書くだけで変数の中身を知ることができます.Python(Python3)の標準出力も同様に便利ですが,以下のように丸括弧で変数を囲ってあげる必要があります.

print(val)

また,Pythonの場合にはインデントを揃える必要があるため,以下のような書き方ができません.

for i in range(1, 11):
print(i)     #インデントされていないため, IndentationError が発生

しかし,Rubyの場合にはインデントを気にする必要がないため,デバッグの時にササっと値を確かめたい,といった時に便利です.

for i in 1..10 do
p  i     #本来はインデントを揃えるべきだが, ササっと確認したいだけの時には行の行頭にpを書き込むだけで良い
end

以上の理由から,標準出力メソッドの使い勝手の良さはRubyに軍配が上がると思います.

終わりに

これまで説明した利便性から察してもらえると思いますが,Rubyの習得難易度は他の言語より易しめです.そのため,プログラミング初心者におすすめの言語の1つかなと思います.また,既に別の言語を習得している人でも,Rubyは使い勝手の良い言語なので覚えておいて損はないと思います.口頭言語でも2言語習得より,3言語以上の習得の方が脳がより活発になるという報告もあるので,脳の活性化のために是非チャレンジしてみてはいかがでしょうか?

Pocket

CONTACT

お問い合わせ・ご依頼はこちらから