open-uri の close とか
ruby の open-uri の open で、サイトをスクレイピングするプログラムを見かけたのだけれど、「close 呼ばなくていいの?」と思って色々調べたのでメモしておきます。結論としては、ブロック渡した時は呼ばなくていいけど基本的には呼ぶ。
調べたのは ruby 2.3 なので古くなるかもです、がそんなに変わらなそうな部分?
ブロック渡した時は呼ばなくていい
open メソッドの実装を見てみます。
> require 'open-uri' => true > method(:open).source_location => ["/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/open-uri.rb", 29]
https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L29-L39
def open(name, *rest, &block) # :doc: if name.respond_to?(:open) name.open(*rest, &block) elsif name.respond_to?(:to_str) && %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name && (uri = URI.parse(name)).respond_to?(:open) uri.open(*rest, &block) else open_uri_original_open(name, *rest, &block) end end
今回は引数として url 文字列の見渡すので、引数 name は URL として解釈され、URI#open が呼ばれるみたいです。そこだけ切り出して実行してみます。
> (uri = URI.parse('https://ushumpei.hatenablog.com')).respond_to?(:open) => true > uri => #<URI::HTTPS https://ushumpei.hatenablog.com> > uri.method(:open).source_location => ["/System/Library/Frameworks/Ruby.framework/Versions/2.3/usr/lib/ruby/2.3.0/open-uri.rb", 716]
https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L716-L718
OpenURI::OpenRead#open が呼ばれるみたい。その中から OpenURI.open_uri が呼ばれている。
https://github.com/ruby/ruby/blob/ruby_2_3/lib/open-uri.rb#L132-L166 (抜粋)
if block_given? begin yield io ensure if io.respond_to? :close! io.close! # Tempfile else io.close if !io.closed? end end else io end
153 行目で block_given? によりブロックが与えられていた時は close を呼ぶように実装されている (ローンパターンか)
なのでブロック渡せば close は明示的に呼ばなくてもいいことがわかりました。
気になったコード
html = open('https://ushumpei.hatenablog.com').read
え、これ絶対 close されないのでは?と思ったので lsof で開きっぱなしなことを確認しました。確認コマンドとその意味は以下の感じになります。
$ lsof -c ruby | grep open-uri | wc -l
rubyが開いているファイルを見たいので-c rubyでコマンド名を指定しました。- 一時ファイル名に
open-uriが付いていたのでそれで絞り込み - 行数取得
該当コード実行前の lsof -c ruby | grep open-uri | wc -l は 0
該当コード実行 (irb は開きっぱなし)
> require 'open-uri'
=> true
> 10.times { |n| open('https://ushumpei.hatenablog.com').read }
=> 10
該当コード実行後の lsof -c ruby | grep open-uri | wc -l は 10。irb を終了するとファイル数は 0 に戻りました。
ブロック渡した時は irb 開きっぱなしの状態でもファイル数は 0 のままです。
(注意: open-uri で open した時に、lsof で open-uri がひっついていている行が出力される、という事象は、ソースコードレベルでは確認しきれていません。ちょっとお粗末になり申し訳ないですが、「多分 open 時に作成された開かれているファイルの行だ」という予想に基づいている、と断っておかなければいけません)
まとめ
open(XXX).readはopen(XXX) { |f| f.read }とかに変えようlsofってlist open filesなんですね。ポート使っているプロセス調べるコマンドかと思っていた。- というか linux が全てをファイルという形で抽象化しているアレのアレなのか。(よく知らない)
- そういえば
openって|lsとかでパイプつけたコマンド渡すと実行できたりしてアレらしいので、OpenURI.open_uri呼んじゃった方がいいっぽい。
追記 2019/04/07
- 可読性微妙かもだけど、
open(XXX).readの書き換えopen(XXX, &:read)もいけるのか、 ruby すごい