K.Sasada's Home Page

こめんとのついか

こめんとこめんと!

message

please set comment :).

_2(Sun)

Ruby VM アドベントカレンダーの 2 日目です.

今日も,Ruby 2.0 の新機能のご紹介です.

これまで,現在の実行箇所のバックトレース情報を取得するための Kernel#caller というメソッドがありましたが,これを若干使いやすくする Kernel#caller_locations というメソッドが導入されました.

バックトレース情報とは,実行中のスレッドが,どんなメソッド呼び出し(およびブロック呼び出し)を行っているか,というものを示す情報で,例外発生時に出力されるようなものです.

require 'pp'

def m1
  raise
end

def m2; m1; end
def m3; m2; end
def m4; m3; end
def m5; m4; end

pp m5
#=>
t.rb:4:in `m1': unhandled exception
	from t.rb:7:in `m2'
	from t.rb:8:in `m3'
	from t.rb:9:in `m4'
	from t.rb:10:in `m5'
	from t.rb:12:in `<main>'

ちなみに,例外発生時のバックトレース情報は,Exception#backtrace によって取得することができます.

require 'pp'

def m1
  raise
end

def m2; m1; end
def m3; m2; end
def m4; m3; end
def m5; m4; end

begin
  pp m5
rescue => e
  pp e.backtrace
end

["t.rb:4:in `m1'",
 "t.rb:7:in `m2'",
 "t.rb:8:in `m3'",
 "t.rb:9:in `m4'",
 "t.rb:10:in `m5'",
 "t.rb:13:in `<main>'"]

さて,現在のバックトレースを取得するたびに毎回例外を出していては大変なので,Kernel#caller メソッドが存在します.

require 'pp'

def m1
  caller
end

def m2; m1; end
def m3; m2; end
def m4; m3; end
def m5; m4; end

pp m5
#=>
["t.rb:7:in `m2'",
 "t.rb:8:in `m3'",
 "t.rb:9:in `m4'",
 "t.rb:10:in `m5'",
 "t.rb:12:in `<main>'"]

Exception#backtrace で取り出したバックトレース情報にくらべて,`m1' メソッド実行中である,という情報が落ちていますね.caller メソッドは第一引数に整数値を取り,何個目のバックトレースを取得するのか,という指定が行え,デフォルト値は 1 になっています.つまり,1 つ上のトレースを取得する,ということになります.これを 0 にすると,現在のフレーム位置のトレースを取得することができます.

require 'pp'

def m1
  caller(0)
end

def m2; m1; end
def m3; m2; end
def m4; m3; end
def m5; m4; end

pp m5
#=>
["t.rb:4:in `m1'",
 "t.rb:7:in `m2'",
 "t.rb:8:in `m3'",
 "t.rb:9:in `m4'",
 "t.rb:10:in `m5'",
 "t.rb:12:in `<main>'"]

これで,Exception#backtrace と同じ表示になりました.なお,なぜデフォルト値が 1 であるか,ですが,そういうユースケースが多かったから,ですかねぇ.

ちなみに,他のスレッドの現在のバックトレース情報は何か,ということを調べる Thread#backtrace というメソッドが存在します.

require 'pp'

def m1
  sleep
end

def m2; m1; end
def m3; m2; end
def m4; m3; end
def m5; m4; end

th = Thread.new{
  m5
}
sleep 0.1
pp th.backtrace
#=>
["t.rb:4:in `sleep'",
 "t.rb:4:in `m1'",
 "t.rb:7:in `m2'",
 "t.rb:8:in `m3'",
 "t.rb:9:in `m4'",
 "t.rb:10:in `m5'",
 "t.rb:13:in `block in <main>'"]

Thraed#backtrace メソッドを利用することで,スレッド th は,今 sleep で寝ている,ということがわかりますね.


さて,1.9 までの caller では,上記のように "#{path}:#{lineno} #{method_name}" のような形の文字列の配列が返ってきます.ただ,時々 path だけ,lineno だけ,method_name だけが欲しい,という場合があります.この場合,正規表現なんかで文字列を切り出さなければなりません.

そこで,Ruby 2.0 から Kernel#caller_locations というメソッドが導入される予定です(多分).caller と同じ使い方で使えるのですが,返ってくる値が Thread::Backtrace::Location というクラスのオブジェクトになります.ちょっと長いですが,ふつーの人は,このクラス名を書くことは無いので許して下さい.

では,caller の代わりに caller_locations を使ってみます.

require 'pp'

def m1
  caller_locations
end

def m2; m1; end
def m3; m2; end
def m4; m3; end
def m5; m4; end

pp m5
#=>
["t.rb:7:in `m2'",
 "t.rb:8:in `m3'",
 "t.rb:9:in `m4'",
 "t.rb:10:in `m5'",
 "t.rb:12:in `<main>'"]

はい,さっきと同じ結果が得られましたね.同じような表示になってしまうのでちょっとわかりづらいので,各要素のクラスを確認してみましょう.

require 'pp'

def m1
  caller_locations
end

def m2; m1; end
def m3; m2; end
def m4; m3; end
def m5; m4; end

pp m5.map{|e| e.class}
#=>
[Thread::Backtrace::Location,
 Thread::Backtrace::Location,
 Thread::Backtrace::Location,
 Thread::Backtrace::Location,
 Thread::Backtrace::Location]

Thread::Backtrace::Location というクラスであることがわかります.Thread::Backtrace::Location.inspect が,意図的に String#inspect の結果と同じ表示をするよになっているから,同じように見えるんですね.

(え,あなたの Ruby 2.0 では

[#<Thread::Backtrace::Location:0x0000000159e920>,
 #<Thread::Backtrace::Location:0x0000000159e8f8>,
 #<Thread::Backtrace::Location:0x0000000159e8d0>,
 #<Thread::Backtrace::Location:0x0000000159e8a8>,
 #<Thread::Backtrace::Location:0x0000000159e880>]

と表示されるって? もしかして,まだ Ruby 2.0.0 preview2 をお使いじゃないんですか!? ([ANN] ruby 2.0.0-preview2 released))

(inspect の変更を preview2 リリース直前に入れたのでした)

さて,Thread::Backtrace::Location はいくつかの便利なメソッドを持っています.まず,to_s を行うと,caller の返す文字列と同じ表記になります.

他には,次のようなメソッドがあります.

label と base_label に関しては説明が必要でしょう.

例えば,ブロックの中で caller を呼び出すと,次のように表示されます.

1.times{
  p caller(0)
}
#=> ["t.rb:3:in `block in <main>'", "t.rb:2:in `times'", "t.rb:2:in `<main>'"]

トップレベルなので,<main> がメソッド名にあたる部分でですが,ブロックの中で実行されているので,`block in <main>' という表記が書いてあります.ただ,ここで欲しいのは <main> だけだったりすると,これもわざわざ正規表現で切り出すのはしんどいので,base_label とすれば <main> の部分だけ取り出せる,というようにしています.

では,実際に試してみましょう.

require 'pp'

def m1
  1.times{
    $locs = caller_locations
  }
end

def m2; m1; end

class C
  tap{
    m2
  }
end

$locs.each{|loc|
  puts "path         : #{loc.path}"
  puts "absolute_path: #{loc.absolute_path}"
  puts "lineno       : #{loc.lineno}"
  puts "label        : #{loc.label}"
  puts "base_label   : #{loc.base_label}"
}

#=>
path         : t.rb
absolute_path: c:/ko1/src/rb/t.rb
lineno       : 4
label        : times
base_label   : times
path         : t.rb
absolute_path: c:/ko1/src/rb/t.rb
lineno       : 4
label        : m1
base_label   : m1
path         : t.rb
absolute_path: c:/ko1/src/rb/t.rb
lineno       : 9
label        : m2
base_label   : m2
path         : t.rb
absolute_path: c:/ko1/src/rb/t.rb
lineno       : 13
label        : block in <class:C>
base_label   : <class:C>
path         : t.rb
absolute_path: c:/ko1/src/rb/t.rb
lineno       : 14
label        : tap
base_label   : tap
path         : t.rb
absolute_path: c:/ko1/src/rb/t.rb
lineno       : 14
label        : <class:C>
base_label   : <class:C>
path         : t.rb
absolute_path: c:/ko1/src/rb/t.rb
lineno       : 11
label        : <main>
base_label   : <main>

昨日 紹介した ISeq#path なんかと同じようなインターフェースになっているというのがわかると思います.というか,同じようになるように作りました.

なお,Thread#backtrace も,Thread::Backtrace::Location の配列を返す Thread#backtrace_locations というメソッドが追加されています.

では,今日はこんな感じで.


というわけで,Ruby 2.0.0 preview2 が出ました!([ANN] ruby 2.0.0-preview2 released))

皆さん試して下さい.協力して下さった全ての皆様に感謝致します.

_kou(Mon Dec 03 10:10:13 +0900 2012)

inspectの結果は


[#<Thread::Backtrace::Location "t.rb", 7, "in `m2'">,
 ...]

とか、


[#<["t.rb", 7, "in `m2'"]>,
 ...]

というようにパースされた結果が分かれていたほうがうれしいなぁと思いました。caller_locationsを使いたいときはパースされた結果が欲しいので、どうパースされたかがわかったほうが嬉しいです。 to_sの方はcallerと同じでよいと思います。

_ささだ(Mon Dec 03 10:49:40 +0900 2012)

 ML でやりましょう.ticket 作って貰えますか?


好きにコメントを編集してください。ただし、あまり他の人のコメントを書き換えることは感心しません。



back

tton 記述が使えます。YukiWikiな記述してりゃ問題ありません。

「行頭に#code」 と、「行頭に#end」 で挟むと、その間の行は pre で囲まれます。プログラムのソースを書くときに使ってください。

例:

#code

(なんかプログラム書く)

#end

リンクは

[[なまえ|http://www.example.org]]

とか

[[http://www.example.org]]

で貼れます。

$Date: 2003/04/28 10:27:51 $