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 すごい