おきらくPerlプログラミング入門 〜〜めざせ Perl マスター〜〜 広井 誠 第19回 ○パッケージ 今回は Perl のオブジェクト指向と深くかかわっているパッケージについて詳 しく説明します。もともとパッケージは、関数を部品として手軽に扱うことがで きるように、ライブラリとしてまとめるための機能です。Perl には付属のライ ブラリの他に、ユーザーが開発した多くのライブラリやモジュールが公開されて います。これらを上手に利用すれば、プログラムを作成する時の手間を軽減する ことができます。また、自分で作ったプログラムをライブラリの形としてまとめ ておけば、簡単に再利用することができます。 パッケージによって名前空間が切り替わることは第 15 回で説明しました。今 回はパッケージの機能について詳しく説明します。 ○BEGIN と END パッケージには2つの特別な関数 BEGIN と END を定義することができます。 Perl はインタプリタですが、内部ではプログラムを中間コードにコンパイルし てから実行する方式です。通常、プログラムを全て中間コードにコンパイルして から実行するのですが、BEGIN は違います。BEGIN のコンパイルが終了すると、 まだ他のプログラムがコンパイルされていなくても、その場でそれを実行するの です。パッケージで必要な初期化処理を BEGIN で定義することにより、パッケー ジ内の関数が呼び出される前に、確実に初期化を行うことができます。 BEGIN とは逆に、プログラムの終了時に実行される関数が END です。この関 数はプログラムが正常終了するか異常終了するかにかかわらず、終了直前に呼び 出されます。 BEGIN と END はいくつでも定義することができます。BEGIN の場合、定義さ れた順番に実行され、END は定義された順番とは逆に実行されます。簡単な実行 例を示しましょう。 # BEGIN と END の例 print "メインプログラム\n"; BEGIN { print "BEGIN 1\n"; } BEGIN { print "BEGIN 2\n"; } END { print "END 1\n"; } END { print "END 2\n"; } このプログラムはパッケージの宣言がないので main パッケージとして扱われ ます。この場合でも BEGIN と END を使うことができます。それから、BEGIN と END を定義する時は sub を省略することができます。実行結果は次のようにな ります。 BEGIN 1 BEGIN 2 メインプログラム END 2 END 1 BEGIN は定義された順に、END は定義された順番とは逆に実行されているのが わかりますね。BEGIN と END は、もともとプログラミング言語 AWK にある機能 です。Perl を実行する時にオプション -p や -n を指定すると、AWK と同様の 動作をさせることができます。 ○オートロード Perl は呼び出された関数がパッケージ内で見つからない場合、AUTOLOAD() と いう関数を探し、見つかればそれを実行します。この時 AUTOLOAD() には、呼び 出された関数と同じ引数が渡され、変数 $AUTOLOAD には呼び出された関数の完 全修飾名(パッケージ名::関数名)がセットされます。たとえば、Perl には外 部コマンドを実行する関数 system がありますが、次に示す AUTOLOAD() を定義 することで、関数と同じように外部コマンドを実行することができます。 # 外部コマンドを呼び出す AUTOLOAD sub AUTOLOAD { my $command = $AUTOLOAD; $command =~ s/.*:://; system($command, @_); } 最初に、変数 $AUTOLOAD よりコマンド名を取り出します。先頭にパッケージ 名がついているので、先頭から :: までの文字を取り除きます。後は、コマンド 名と引数を関数 system に渡します。system は実行した外部コマンドの標準出 力を文字列として返します。これで、dir( '*.pl' ) とすれば dir が呼び出さ れ、その結果を文字列として受け取ることができます。他のコマンドも同様に呼 び出すことができるので、外部コマンドを多く呼び出すプログラムを作る時には とても役に立ちます。Perl の標準ライブラリには、この機能より完全なモジュー ル Shell があるので、興味のある方は使ってみるといいでしょう。 この他にも、eval を使ってその場で関数を作成する、特殊な goto を使って 作成した関数にジャンプする、など高度なテクニックがあります。興味のある方 は参考文献を読んでください。 オートロードは、メソッドの探索でも機能します。第 17 回で説明したように、 メソッドは継承階層を深さ優先で探索されます。それでもメソッドが見つからな い場合、今度はパッケージに定義されている AUTOLOAD() を深さ優先で探索しま す。次の図を見てください。 A B C │ │ │ │ │ │ D E F \ │ / \│/ G メソッドの探索:G→D→A→E→B→F→C ↓ 見つからない場合 AUTOLOADの探索:G→D→A→E→B→F→C 図 1 : 多重継承におけるメソッドの探索 クラスGは、クラスD、E、Fを多重継承しています。D、E、Fのスーパーク ラスはそれぞれA、B、Cです。クラスGで @ISA = (D, E, F) と設定されてい るとすると、メソッドは深さ優先で探索されるので、順序は「G→D→A→E→ B→F→C」となります。それでもメソッドが見つからない場合、AUTOLOAD を 探索します。この探索も深さ優先なので、「G→D→A→E→B→F→C」と同 じ順序になります。通常のパッケージでは、関数が見つからない時に AUTOLOAD を調べますが、クラスの場合は継承階層をチェックしてから、AUTOLOAD を調べ ることに注意してください。 ○オブジェクトの廃棄 オブジェクト指向に関連して、オートロードの注意事項をもう一つ説明します。 今まで、無名の配列や無名のハッシュなど、プログラムの実行時にメモリを取得 する機能を使ってきました。Perl のオブジェクト指向は、これらの機能を土台 に成り立っています。今までのプログラムでは、生成したオブジェクトを使うだ けですが、オブジェクトが不用になる場合もあります。他のプログラミング言語、 C言語やC++では、実行時に取得したメモリが不用になったら、それを返却しな いといけません。C言語の場合、実行時にメモリを取得するための関数 malloc と、メモリを返却する関数 free が用意されています。メモリには限りがあるの で、malloc でメモリを取得するだけでは、いつかはメモリ不足となりプログラ ムは実行できなくなります。C/C++ の場合、不用になったメモリを返すことは プログラマの責任なのです。 Perl の場合、C言語の free に相当する関数はありません。Perl には、不用 になったメモリを回収する「ガベージコレクション(garbage collection) [*1] という機能があるからです。Perl の他にも、ガベージコレクションが搭載され ているプログラミング言語はたくさんあります。smalltalk や Lisp など伝統的 な言語の他にも、Java, Tcl/Tk, Phyton, Ruby など最近の言語でもガベージコ レクションを採用しています。ガベージコレクションのおかげで、私達はメモリ を管理するための面倒なプログラムを書かずに済むのです。 Perl では、オブジェクトを返却する時に特別な処理を行うためのメソッド DESTORY が用意されています。DESTROY には、返却するオブジェクトへのリファ レンスが渡されます。メモリ管理は Perl が行ってくれますが、オブジェクトが 返却される前に、DESTORY を使って何らかの処理を行うことができるようになっ ています。 今までのプログラムでは DESTORY が必要になることはありませんが、このメ ソッドにもオートロードが働くことに注意してください。DESTORY が定義されて いないパッケージで AUTOLOAD が定義されていると、DESTORY ではなく AUTOLOAD が実行されることになります。簡単な例題を示しましょう。 # クラスの定義 package Foo; sub new { my $type = shift; my $obj ={}; bless $obj, $type; $obj; } sub AUTOLOAD { print "Foo AUTOLOAD\n"; } package Bar; @ISA = ( Foo ); まずクラス Foo を定義します。このクラスでは、オブジェクトを生成するメ ソッド new と AUTOLOAD を定義します。クラス Bar は Foo を継承するだけで す。それでは実際に Bar のオブジェクトを生成してみましょう。 package main; $obj = Bar->new(); print "$obj\n"; $obj = Bar->new(); print "$obj\n"; パッケージを main に切り替えて、Bar のオブジェクトを生成して変数 $obj にセットします。次に、新しいオブジェクトを生成して、同じ変数 $obj にセッ トします。実行結果は次のようになります。 Bar=HASH(0x650c64) Foo AUTOLOAD Bar=HASH(0x655858) Foo AUTOLOAD 最初のオブジェクトは $obj にしか格納されていないので、$obj の値が書き 換えられたことにより、最初のオブジェクトはどの変数にも格納されていない状 態 [*2]、つまり、どこからも参照されていない不用なオブジェクトになりまし た。ここで DESTROY が呼び出されます。Bar と Foo には DESTORY が定義され ていないので、AUTOLOAD が検索されます。この場合、Foo に定義されている AUTOLOAD が実行されます。また、実行結果からプログラムの終了時にもガベー ジコレクションが働いていることがわかります。 DESTORY メソッドをオートロードしたくない場合は、AUTOLOAD でメソッド名 をチェックするといいでしょう。 # DESTORY をチェック sub AUTOLOAD { return if $AUTOLOAD =~ /::DESTORY$/; print "Foo AUTOLOAD\n"; } 正規表現を /::DESTORY/ とすると、名前の途中に DESTORY があるメソッドにも 一致してしまいます。末尾を表す正規表現 $ を忘れないでくださいね。 [*1] 「ゴミ集め」とも呼びます。 [*2] Perl のガベージコレクションは、オブジェクト(メモリ)を参照し ている変数を数えるリファレンスカウント方式です。簡単に説明する と、変数にオブジェクトを代入する時にオブジェクト内部のカウンタ を +1 し、今まで格納されていたオブジェクトのカウンタを -1 しま す。このカウンタが 0 になったならば、そのオブジェクトを回収す ればいいわけです。この他に Tcl/Tk や Phyton がリファレンスカウ ント方式です。これに対し、smalltalk, Lisp, Ruby ではマークアン ドスイープ方式というガベージコレクションを採用しています。拙作 の Lisp インタプリタ VTOL もマークアンドスイープ方式です。 ○名前のインポート 今まではクラスを定義するためにパッケージを使ってきました。パッケージ内 で定義された関数はメソッドとして扱われるため、矢印「->」を用いて簡単に呼 び出すことができます。ところが、メソッドではない一般の関数を呼び出す場合、 「->」を使うことができないため、完全修飾名で呼び出すことになります。いち いちパッケージ名をキー入力するのは面倒ですね。このため Perl には、他のパッ ケージで定義された名前を取り込む機能「インポート(import)」が用意されてい ます。インポートする名前の指定は簡単です。use で名前のリストを指定するだ けです。ただし、このためにはパッケージ側でも名前を取り出す [*3] ための用 意が必要です。Perl には、この作業を行うモジュール Exporter が標準で用意 されています。簡単なプログラムを示しましょう。 # 名前のエクスポート package Foo; use Exporter; @ISA = ('Exporter'); @EXPORT_OK = ('test1'); sub test1 { print "test1\n"; } sub test2 { print "test2\n"; } 最初に use でモジュール Exporter をロードします。Perl は use でモジュー ルをロードする時、そのモジュールの import メソッドを呼び出すようになって います。Exporter にはデフォルトの import メソッドが定義されているので、 これを継承することでインポートの機能を利用することができます。後は、取り 出す名前を配列 @EXPORT_OK にセットするだけです。それでは実行例を示しましょ う。 use Foo ( 'test1' ); test1(); Foo::test2(); 実行結果 test1 test2 モジュール Foo から test1 をインポートしています。test2 を呼び出す場合 は、Foo::test2() としないとエラーになります。また、use に許可されていな い名前 test2 を与えてもエラーとなります。 [*3] インポートに対応して「エクスポート(export)」といいます。 ○use と require の違い パッケージをロードする関数にはもう一つ require がありますが、名前のイ ンポートには対応していません。require はパッケージをロードするだけの機能 しかないのです。use は require を使って実現することができます。 # use Foo; と同等のプログラム BEGIN { require "Foo.pm"; Foo->import(); } use は BEGIN と同様に扱われるので、プログラムの実行が始まる前に確実に モジュールをロードすることができます。逆に、プログラムの実行時にパッケー ジをロードしたい場合には require を使うことができます。実際にプログラム する時は、要求された時にだけ関数をロードするモジュール AutoLoader が標準 ライブラリに用意されているので、そちらを使ってみるといいでしょう。 ○シンボルテーブル パッケージに登録される名前は、Perl 内部のハッシュ表に格納されます。こ のハッシュ表を「シンボルテーブル」といいます。smalltalk や Lisp などのイ ンタプリタでは、ハッシュ表で名前を管理することは常套手段です。Perl では、 パッケージごとにシンボルテーブルが用意され、ユーザーからアクセス [*4] す ることができます。 パッケージに対応するシンボルテーブルは、パッケージ名に2つのコロンを付 けた名前となります。たとえば、main パッケージに対応するシンボルテーブル は %main:: となります。パッケージ main に限り名前を省略して %:: とするこ とができます。パッケージ Foo のシンボルテーブルは %Foo:: となります。ま た、全パッケージのシンボルテーブルは、%main:: からアクセスできるようになっ ています。 Perl の場合、変数、配列、ハッシュ、関数などに同じ名前を付けることがで きますが、ハッシュ表には複数のデータを格納することができません。そこで Perl では、シンボルテーブルに型グロブをセットしています。型グロブの構造 はとても簡単で、変数、配列、ハッシュ、関数など、各データへのポインタを格 納しているだけです。次の図を見てください。 シンボルテーブル 型グロブ bar -------> *bar 変数へのポインタ($bar) 配列へのポインタ(@bar) ハッシュへのポインタ(%bar) 関数へのポインタ(&bar) ファイルハンドルへのポインタ(bar) フォーマットへのポインタ (bar) 図2:シンボルテーブルと型グロブ 筆者は型グロブというデータをなかなか理解できなかったのですが、シンボルテー ブルの構造を見て、ようやく納得することができました。変数や関数に同じ名前 を付けることができるのも、名前とデータの間に型グロブが介在しているからな のです。型グロブのようなデータ構造は、ユーザーから隠しておく方が安全なの ですが(実際に危険な使い方もある)、それをデータとして公開してしまうとこ ろが Perl らしいと思いました。 たとえば、Foo に定義されているシンボルは、次のように探し出すことができ ます。 # パッケージ Foo のシンボルを出力 foreach $name ( keys %Foo:: ){ print "$name\n"; } シンボルテーブルといってもハッシュと同じようにアクセスすることができます。 実際に実行させてみると、いろいろなシンボルが登録されていることがわかりま す。ただし、変数が定義されているのか、配列が定義されているのかまではわか りません。そこで、パッケージ内のグローバル変数とその値を表示するプログラ ムを作ってみましょう。 # パッケージのグローバル変数をダンプ package Dumpvar; use Exporter; @ISA = ('Exporter'); @EXPORT_OK = ('dumpvar'); sub dumpvar { my $packname = shift; local( *alias ); local( *table ) = $main::{"${packname}::"}; $, = " "; while( ($name, $globalvalue) = each( %table ) ){ *alias = $globalvalue; print "----- $name -----\n"; if( defined( $alias ) ){ print " \$$name $alias\n"; } if( defined( @alias ) ){ print " \@$name @alias\n"; } if( defined( %alias ) ){ print " \%$name ", %alias, "\n"; } } } 関数 dumpbar はパッケージ名を受け取り、そのパッケージに定義されている グローバル変数の中で、変数、配列、ハッシュの値を表示します。型グロブは my で定義できないので local を使っています。変数 *table にパッケージのシ ンボルテーブル(型グロブ)をセットします。 local( *table ) = $main::{"${packname}::"}; main パッケージから型グロブを取り出していますが、パッケージ名を作るとこ ろで、"$packname::" とすると packname:: の変数値に置換しようとするため正 常に動作しません。そこで、packname を {} で囲んで :: と区切ってください。 これで、パッケージ名の後ろにコロンを2つ付けることができます。文字列を {} で区切る機能は、もともと UNIX 系のシェルで用いられているものです。ま た、この処理はシンボリックリファレンスを使うと、次のように書き換えること ができます。 local( *table ) = *{"${packname}::"}; デリファレンスを行う場合、ブロック {} を使って優先順位を明確にできること を思い出してください。この場合、Perl は {} の中の実行結果がリファレンス であることを期待しています。ところが、結果がリファレンスではなく文字列な ので、シンボリックリファレンスとなるのです。したがって、$packname が Foo であれば、*table には型グロブ *Foo:: がセットされます。 後は、関数 each でシンボルテーブルの内容を取り出します。$globalvar に は型グロブがセットされていることに注意してください。defined で変数、配列、 ハッシュが定義されているかチェックし、値を出力するだけです。特殊変数 $, は出力フィールドセパレータといって、 print での区切り文字を指定します。 これによりハッシュを print する時に、キーと要素の間を空白で区切ることが できます。 それでは、簡単な実行例を示しましょう。 use Dumpvar ('dumpvar'); package Foo; $x = 10; @x = (10, 20, 30); %y = ( 'a' => 100, 'b' => 200); package main; dumpvar( 'Foo' ); 実行結果 ----- x ----- $x 10 @x 10 20 30 ----- y ----- %y a 100 b 200 パッケージ Foo で定義されたグローバル変数が出力されましたね。このように、 Perl はシステムの内部にアクセスできるので、Perl でデバッガなどのシステム ツールを比較的容易に作成することができます。 パッケージの基本的な使い方は、これで十分だと思います。もっと詳しい仕組 みを知りたい方は、参考文献を読んでください。 [*4] システム内部の情報にアクセスする機能を「リフレクション」と呼ぶ ことがあります。 ○次回は? 次回が最終回となりますが、変数とクラスを結び付ける働きをするタイについ て説明する予定です。お楽しみに。 ―参考文献― [1] Larry Wall, Tom Christiansen, Randal L. Schwartz 共著「プログラミン グPerl」改訂版 オライリー・ジャパン 1997 [2] Sriram Srinivasan 著「実用Perlプログラミング」オライリー・ジャ パン 1998 (EOF)