Ruby 画像ダウンロード編 (7) open について その 1 【計算数学 I】

更新日時:

open について (1)

本セクションでは、ファイルを開き、書き込む操作についてお話します。 複数メソッドがあり、それらが open というメソッド名 or 関数名であることは共通しています。これらの違いを意識することなくプログラムが書けるところは Ruby のよいところでしょう。しかし、本教材の目的を考慮して、違いもきちんと説明したいと思います。 続けて、ファイルを開く際のオプションについてご説明します。これらは Linux 標準教科書にも書いていることがベースとなっています。

我々の目標は、画像を 333 枚ダウンロードすることでした。「ダウンロードする」とは具体的には何を言っているのでしょうか。 通信のことは Ruby に全部任せられます( open-uri が全部やってくれます)。 ですから、画像の情報をサーバから持ってくるところまでは、何ら気にする必要はありません。 「情報を持って」きたあとは、それを自分の好きな場所に保存する必要があります。 ですから、我々がするべきことは3つです。

  • URL を指定し、画像の情報を受け取る
  • ローカルのファイルを開く
  • 開いたローカルのファイルに、画像の情報を書き込む

もちろんこれらの操作はライブラリがサポートしてくれます。私たちが意識するべきことは、このように動作を分解して、 それをプログラムとして記述するということです。「ダウンロードする」という単一のコマンドがあってそれを実行するのではなく、 上の 3 つを、明示的に記述することになります。 2 番目がファイルを開く操作であるのは当然ですが、実は1番目もそうです。 だから、ここから先は、ファイルを開く動作について詳しく学びます。

Kernel.#open と File.open

まずはじめにネチネチしたことから述べておきます。 この話は些細な事ですが、あとの理解で重要なので、とりあえず読むことをおすすめします。

今まで、クラスメソッドは (クラス名).(メソッド名) で呼び出すと書いてきました。モジュール関数も同様でした。 しかし、Kernel というモジュールのモジュール関数は特殊です。いついかなる場合でも (関数) で呼び出せます。なぜでしょうか? 実はほぼ全てのクラスは、Kernel モジュールを include しているからです。正確に言うと、ほぼ全てのクラスは、Object クラスを継承しています。Object クラスは、Kernel モジュールを予め include しています。だから少し前で書いたことから、(関数) で呼び出せます。

演習

これまでの説明から、次の帰結を納得してください。空欄を埋めてください。

  • 単に open と書いた時と、File.open と書いたときは、別のメソッド or 関数を呼び出している。
  • 前者は ( ) モジュールの open というモジュール関数を呼び出しているのに対し、後者は ( ) クラスの open というクラスメソッドを呼び出している。

前者を Kernel.#open をいうことにします。

ではその違いは? となるのですが、実はほとんど違いがありません。ローカルのファイルを開く点においてはほとんど同じことができます。この時点での違いと言えば、前者はコマンドの実行やパイプの生成に使用できることでしょうか。

Kernel.#open と open-uri ライブラリ

「なんだよこんなにしっかり読ませておいて違いが殆ど無いとは」とお思いかもしれませんが、次の段階に進むと大事になってきます。 open-uri ライブラリを読み込む( require 'open-uri' )と、Kernel.#open が再定義され、機能が拡張されます。 具体的には、URL を文字列として open の引数とすると、その URL を「開いて」返してくれます。どのような形で返すか、何をするかは後述します。

注意してほしいのは、

  • Kernel.#open の機能を、open-uri ライブラリで拡張した結果、Kernel.#open で URL を開けるようになった

ということであり、

  • Kernel.#open に最初から URL を開く機能があったわけではない
  • File.open には影響がないため、File.open に URL を開く機能が備わるわけではない

ということです。以下の実行例で理解を確認してみてください。 2 行目と 5 行目は失敗しています。4 行目は成功しています( 4 行目の実行結果は、私たちにわかりやすい形で返り値を記述できていないだけです)。

irb(main):001:0> url = "http://utmsks.github.io/"
=> "http://utmsks.github.io/"
irb(main):002:0> open(url)
Errno::ENOENT: No such file or directory @ rb_sysopen - http://utmsks.github.io/
(中略)
irb(main):003:0> require 'open-uri'
=> true
irb(main):004:0> open(url)
=> #<StringIO:0x007fd97280d650 @base_uri=#<URI::HTTP http://utmsks.github.io/>, (中略)
irb(main):005:0> File.open(url)
Errno::ENOENT: No such file or directory @ rb_sysopen - http://utmsks.github.io/
(以下略)

また、従来の open の機能はきちんと保持しています。パスを渡せばローカルのファイルを開くことができます。 文字列として渡されたものが URL なのか パスなのかは自動で判別されます。

ファイルを開く際のオプション

次の問題は、具体的にどうやって開いたファイルを取り使うのかです。その書き方に進みたいところです。 しかし、外堀から埋めていくことにしましょう。実は、ファイルを開くには、大きく3つのオプションがあります。

  • 読み取り専用で開く (書き込みはできない) Ruby の Kernel.#open はこちらがデフォルトで、オプションは付けなくてよろしい
  • 書き込み専用で開く 'w' オプション または 'a' オプション
  • 読み込み書き込み両方可能で開く

この3つの差は、Linux 標準教科書でご理解下さったことでしょう。ですから、どれで開くかを明示的に指定する必要があります。

「わざわざ不可能なもので開く理由なんてないだろ。全部読み込みも書き込みも可能なもので開けばいいだろ」と思う人もいるでしょう。しかし、書き込む必要が無いときは、読み取り専用で開くほうが無難です。誤って書き込んで、ファイルを破壊するのを未然に防ぐに越したことはないからです。プログラミング言語はあっさりと、基本的には何の警告もなしに実行が進みます。書き込んだ後は、戻すことができません(別の手段でバックアップを用意していれば別ですが)。だから、読み込みだけが必要なときは、書き込み可能では開かないほうがよい。これが普通の見解だと思います。

さて、Ruby をはじめとする多くのプログラミング言語では、ファイルの書き込み方は、少なくとも次の2種類が用意されています。

  • もしファイルが存在していたら、全部消して、1から書き込む 'w' オプションをつける
  • もしファイルが存在していたら、末尾に追記する 'a' オプション

この差は容易に納得されるでしょう。オプションの文字列は、ダブルクオーテーションマークで指定しても問題ありません。 さて、今回の場合、

  • URL を指定し、画像の情報を受け取る
  • ローカルのファイルを開く

の2つを、Kernel.#open で実行します。開いた画像を、開いたローカルのファイルに書き込みます。

演習

今回開く 2 つのファイルは、以下のオプションで開くことになります。なぜでしょうか? 上の議論から理由を確かめてください。 受け取った画像は、読み取り専用で開く。 ローカルのファイルは、'w' オプションで開く。

ナビゲーション

この教材は、東京大学理学部数学科専門科目「計算数学 I」のために執筆されたものです。 このサイトに掲載する際に、記事を分けてあります。 他の回はRuby 画像ダウンロード編 一覧 #ks1-ruby-downloadから御覧ください。

Ruby 入門 (計算数学実習資料集)には他の TA が書いた教材があります。

コメントする