M.Hiroi's Home Page

Linux Programming

お気楽 Perl プログラミング超入門

[ PrevPage | Perl | NextPage ]

パッケージ

オブジェクト指向 (前編) では、パッケージによって名前空間が切り替わることを説明しました。今回はパッケージの基本的な機能について説明します。もともとパッケージは、関数を部品として手軽に扱うことができるように、ライブラリとしてまとめるための機能です。Perl には付属のライブラリのほかに、ユーザーが開発した多くのライブラリやモジュールが公開されています。これらを上手に利用すれば、プログラムを作成するときの手間を軽減することができます。また、自分で作ったプログラムをライブラリの形としてまとめておけば、簡単に再利用することができます。

●BEGIN と END

パッケージには 2 つの特別な関数 BEGIN と END を定義することができます。Perl はインタプリタですが、内部ではプログラムを中間コードにコンパイルしてから実行する方式です。通常、プログラムをすべて中間コードにコンパイルしてから実行するのですが、BEGIN は違います。BEGIN のコンパイルが終了すると、まだほかのプログラムがコンパイルされていなくても、その場でそれを実行するのです。パッケージで必要な初期化処理を BEGIN で定義することにより、パッケージ内の関数が呼び出される前に、確実に初期化を行うことができます。

BEGIN とは逆に、プログラムの終了時に実行される関数が END です。この関数はプログラムが正常終了するか異常終了するかにかかわらず、終了直前に呼び出されます。

BEGIN と END はいくつでも定義することができます。BEGIN の場合、定義された順番に実行され、END は定義された順番とは逆に実行されます。簡単な実行例を示しましょう。

リスト : BEGIN と END の例 (sample1801.pl)

use strict;
use warnings;

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 を省略することができます。実行結果は次のようになります。

$ perl sample1401.pl
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 (sample1802.pl)

use strict;
use warnings;

our $AUTOLOAD;

sub AUTOLOAD {
    my $command = $AUTOLOAD;
    $command =~ s/.*:://;
    system($command, @_);
}

print ls("sample1802.pl"), "\n";
$ perl sample1802.pl
sample1802.pl
0

最初に、大域変数 $AUTOLOAD よりコマンド名を取り出します。先頭にパッケージ名がついているので、先頭から :: までの文字を取り除きます。あとはコマンド名と引数を関数 system に渡します。system は実行したコマンドの終了コードを返します。

オートロードはメソッドの探索でも機能します。オブジェクト指向 (後編) で説明したように、メソッドは継承階層を深さ優先で探索されます。それでもメソッドが見つからない場合、今度はパッケージに定義されている AUTOLOAD を深さ優先で探索します。次の図を見てください。

クラス 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, Python, Ruby など最近ではガベージコレクションを採用している言語が多くなりました。ガベージコレクションのおかげで、私達はメモリを管理するための面倒なプログラムを書かずに済むのです。

Perl では、オブジェクトを返却するときに特別な処理を行うためのメソッド DESTORY が用意されています。DESTROY には、返却するオブジェクトへのリファレンスが渡されます。メモリ管理は Perl が行ってくれますが、オブジェクトが返却される前に、DESTORY を使ってなんらかの処理を行うことができるようになっています。

簡単な例を示しましょう。

リスト : DESTROY の使用例 (sample1803.pl)

use strict;
use warnings;

package Foo;

sub new {
    my $type = shift;
    my $obj ={};
    bless $obj, $type;
    $obj;
}

sub DESTROY {
    my $obj = shift;
    print "Foo DESTROY $obj\n";
}

package Bar;
our @ISA = ('Foo');

package main;

my $obj = Bar->new();
print "$obj\n";
$obj = Bar->new();
print "$obj\n";

まず最初に、クラス Foo を定義します。このクラスでは、オブジェクトを生成するメソッド new と DESTROY を定義します。クラス Bar は Foo を継承するだけです。次に、パッケージを main に切り替えて、Bar のオブジェクトを生成して変数 $obj にセットします。そして、新しいオブジェクトを生成して、同じ変数 $obj にセットします。実行結果は次のようになります。

$ perl sample1803.pl
Bar=HASH(0x557d92a46ef0)
Foo DESTROY Bar=HASH(0x557d92a46ef0)
Bar=HASH(0x557d92a46ec0)
Foo DESTROY Bar=HASH(0x557d92a46ec0)

最初のオブジェクトは $obj にしか格納されていないので、$obj の値が書き換えられたことにより、最初のオブジェクトはどの変数にも格納されていない状態 [*2]、つまり、どこからも参照されていない不用なオブジェクトになりました。ここで DESTROY が呼び出されます。また、実行結果からプログラムの終了時にもガベージコレクションが働いていることがわかります。

-- note --------
[*1] 「ゴミ集め」とも呼びます。
[*2] Perl のガベージコレクションは、オブジェクト (メモリ) を参照している変数を数えるリファレンスカウント方式です。簡単に説明すると、変数にオブジェクトを代入するときにオブジェクト内部のカウンタを +1 し、いままで格納されていたオブジェクトのカウンタを -1 します。このカウンタが 0 になったならば、そのオブジェクトを回収すればいいわけです。これに対し、Smalltalk, Lisp, Ruby ではマークアンドスイープ方式というガベージコレクションを採用しています。

●名前のインポート

今まではクラスを定義するためにパッケージを使ってきました。パッケージ内で定義された関数はメソッドとして扱われるため、矢印「->」を用いて簡単に呼び出すことができます。ところが、メソッドではない一般の関数を呼び出す場合、「->」を使うことができないため、完全修飾名で呼び出すことになります。いちいちパッケージ名をキー入力するのは面倒ですね。

このため Perl には、ほかのパッケージで定義された名前を取り込む機能「インポート (import)」が用意されています。インポートする名前の指定は簡単です。use で名前のリストを指定するだけです。ただし、このためにはパッケージ側でも名前を取り出す [*3] ための用意が必要です。Perl には、この作業を行うモジュール Exporter が標準で用意されています。簡単なプログラムを示しましょう。

リスト : 名前のエクスポート (Foo.pm)

use strict;
use warnings;

package Foo;
use Exporter;
our @ISA = ('Exporter');
our @EXPORT_OK = ('test1');

sub test1 {
    print "test1\n";
}

sub test2 {
    print "test2\n";
}

1;

最初に use でモジュール Exporter をロードします。Perl は use でモジュールをロードするとき、そのモジュールの import メソッドを呼び出すようになっています。Exporter にはデフォルトの import メソッドが定義されているので、これを継承することでインポートの機能を利用することができます。あとは、取り出す名前を配列 @EXPORT_OK にセットするだけです。それでは実行例を示しましょう。

リスト : インポートの実行例 (sample1804.pl)

use strict;
use warnings;
use Foo ('test1');

test1();
Foo::test2();
$ perl -I. sample1804.pl
test1
test2

モジュール Foo から test1 をインポートしています。test2 を呼び出す場合は、Foo::test2() としないとエラーになります。また、use に許可されていない名前 test2 を与えてもエラーとなります。

-- note --------
[*3] インポートに対応して「エクスポート (export)」といいます。

●use と require の違い

パッケージをロードする関数にはもうひとつ require がありますが、名前のインポートには対応していません。require はパッケージをロードするだけの機能しかないのです。use は require を使って実現することができます。

リスト : use Foo; と同等のプログラム

BEGIN {
    require "Foo.pm";
    Foo->import();
}

use は BEGIN と同様に扱われるので、プログラムの実行が始まる前に確実にモジュールをロードすることができます。逆に、プログラムの実行時にパッケージをロードしたい場合には require を使うことができます。なお、標準ライブラリには要求されたときにだけ関数をロードするモジュール AutoLoader が用意されているので、実際にプログラムするときは使ってみるといいでしょう。

●型グロブ

Perl は同じ名前でも、変数、配列、連想配列、関数などを定義することができます。たとえば、foo という名前でも $foo, @foo, %foo, &foo のように、先頭の記号で区別することができます。型グロブはこの名前を渡す方法です。Perl では *foo のように,名前の前に * をつけることで、その名前を持つすべてのデータを参照することができます。この *foo を「型グロブ (type glob)」 [*4] といいます。* はワイルドカードと同じように、$, @, %, & のどの文字にも一致するのです。型グロブも Perl で使用するデータのひとつです。

簡単な使用例を示しましょう。

リスト : 型グロブの使用例 (sample1805.pl)

use strict;
use warnings;

sub foo {
    local *g = shift;
    print "${*g} \n";
    print "@{*g} \n";
}

our $a = 1;
our @a = (10, 20, 30);
foo(*a);
$ perl sample1805.pl
1 
10 20 30 

関数 foo は型グロブを受け取ります。局所変数として型グロブを定義する場合、my を使うことはできません。ここは local の出番です。型グロブを介してデータにアクセスするには、二つの方法があります。一つは型グロブをブロックに入れて、その前にデータ型を表す文字を付けます。foo の中では、${*g} とすればスカラー変数に、@{*g} とすれば配列にアクセスすることができます。

もう一つは、ハッシュのように *g{'データ型'} とする方法です。この方法ではデータへのリファレンスが得られます。

*g{'SCALAR'}   # \$g
*g{'ARRAY'}    # \@g
*g{'HASH'}     # \%g
*g{'CODE'}     # \&g
*g{'GLOB'}     # \*g

次に、変数 $a と配列 @a に値をセットして foo(*a) を呼び出します。すると、局所変数 *g を介して、名前 a を持つデータにアクセスすることができます。したがって、関数 foo の結果は $a と @a の内容を表示します。

型グロブを使って渡せるデータは、大域変数か local で宣言した局所変数です。my で宣言した局所変数は、型グロブで渡すことができないので注意してください。

複数の配列を関数に渡す場合、Perl 4 では型グロブしか方法がありませんでした。Perl 5 にはリファレンスがあるので、そちらを使った方が簡単ですし、my 演算子との相性も良いようです。

-- note --------
[*4] UNIX 系 OS のシェルでは、メタ文字 (?, *, [ ], { } など) を含むパターンを使ってファイルを検索することを glob とか globbing と呼びます。このメタ文字は正規表現とは異なりますが、柔軟なファイル指定が可能です。

●シンボルテーブル

パッケージに登録される名前は、Perl 内部のハッシュ表に格納されます。このハッシュ表を「シンボルテーブル」といいます。インタプリタ型のプログラミング言語では、ハッシュ表で名前を管理することは常套手段です。Perl では、パッケージごとにシンボルテーブルが用意され、ユーザーからアクセス [*5] することができます。

パッケージに対応するシンボルテーブルは、パッケージ名に 2 つのコロンを付けた名前となります。たとえば、main パッケージに対応するシンボルテーブルは %main:: となります。パッケージ main に限り名前を省略して %:: とすることができます。パッケージ Foo のシンボルテーブルは %Foo:: となります。また、全パッケージのシンボルテーブルは、%main:: からアクセスできるようになっています。

Perl の場合、変数、配列、ハッシュ、関数などに同じ名前を付けることができますが、ハッシュ表には複数のデータを格納することができません。そこで Perl では、シンボルテーブルに型グロブをセットしています。型グロブの構造はとても簡単で、変数、配列、ハッシュ、関数など、各データへのポインタを格納しているだけです。次の図を見てください。

シンボルテーブル    型グロブ
      bar  -------> *bar
                    変数へのポインタ($bar)
                    配列へのポインタ(@bar)
                    ハッシュへのポインタ(%bar)
                    関数へのポインタ(&bar)
                    ファイルハンドルへのポインタ(bar)
                    フォーマットへのポインタ (bar)


          図 : シンボルテーブルと型グロブ

M.Hiroi は型グロブというデータをなかなか理解できなかったのですが、シンボルテーブルの構造を見て、ようやく納得することができました。変数や関数に同じ名前を付けることができるのも、名前とデータの間に型グロブが介在しているからなのです。型グロブのようなデータ構造は、ユーザーから隠しておく方が安全なのですが (実際に危険な使い方もある)、それをデータとして公開してしまうところが Perl らしいと思いました。

-- note --------
[*5] システム内部の情報にアクセスする機能を「リフレクション」と呼ぶことがあります。

●パッケージ中のシンボルを求める

たとえば、Foo に定義されているシンボルは、次のように探し出すことができます。

リスト : パッケージ Foo のシンボルを出力 (sample1806.pl)

use strict;
use warnings;

package Foo;

our $foo = 10;
our $bar = 1.2345;
our $baz = "hello, world";

package main;

foreach my $name (keys %Foo::){
    print "$name\n";
}
$ perl sample1806.pl
foo
baz
bar

シンボルテーブルといってもハッシュと同じようにアクセスすることができます。実際に実行させてみると、いろいろなシンボルが登録されていることがわかります。ただし、変数が定義されているのか、配列が定義されているのかまではわかりません。そこで、パッケージ内のグローバル変数とその値を表示するプログラムを作ってみましょう。

リスト : パッケージのグローバル変数をダンプ (Dumpvar.pm)

package Dumpvar;

use strict;
use warnings;
use Exporter;

our @ISA = ('Exporter');
our @EXPORT_OK = ('dumpvar');

sub dumpvar {
    my $packname = shift;
    local(*alias);
    local(*table) = $main::{"${packname}::"};
    $, = " ";
    while (my ($name, $globalvalue) = each(%{*table})) {
        *alias = $globalvalue;
        print "----- $name -----\n";
        if (defined(${*alias})) {
            print "  \$$name ${*alias}\n";
        }
        if (@{*alias}) {
            print "  \@$name @{*alias}\n";
        }
        if (%{*alias}) {
            print "  \%$name ", %{*alias}, "\n";
        }
    }
}

1;

関数 dumpbar はパッケージ名を受け取り、そのパッケージに定義されているグローバル変数の中で、変数、配列、ハッシュの値を表示します。型グロブは my で定義できないので local を使っています。変数 *table にパッケージのシンボルテーブル(型グロブ)をセットします。

local(*table) = $main::{"${packname}::"};

main パッケージから型グロブを取り出していますが、パッケージ名を作るところで "$packname::" とすると packname:: の変数値に置換しようとするため正常に動作しません。そこで、packname を { } で囲んで :: と区切ってください。これで、パッケージ名の後ろにコロンを 2 つ付けることができます。文字列を { } で区切る機能は、もともと UNIX 系 OS のシェルで用いられているものです。

あとは、関数 each でシンボルテーブルの内容を取り出します。$globalvar には型グロブがセットされていることに注意してください。次に、defined で変数が定義されているかチェックします。最近の Perl では、配列とハッシュに defined を使うのは推奨されていないので、配列とハッシュの大きさでチェックしています。あとは値を出力するだけです。特殊変数 $, は出力フィールドセパレータといって、 print での区切り文字を指定します。これによりハッシュを print するときに、キーと要素の間を空白で区切ることができます。

それでは、簡単な実行例を示しましょう。

リスト : Dumpvar の使用例 (sample1807.pl)

use strict;
use warnings;
use Dumpvar ('dumpvar');

package Foo;
our $x = 10;
our @x = (10, 20, 30);
our %y = (a => 100, b => 200);

package main;
dumpvar('Foo');
$ perl -I. sample1807.pl
----- y -----
  %y  a 100 b 200
----- x -----
  $x 10
  @x 10 20 30

パッケージ Foo で定義されたグローバル変数が出力されましたね。このように、Perl はシステムの内部にアクセスできるので、Perl でデバッガなどのシステムツールを比較的容易に作成することができます。

パッケージの基本的な使い方は、これで十分だと思います。もっと詳しい仕組みを知りたい方は 参考文献 2, 4 をお読みくださいませ。


初版 2015 年 5 月 31 日
改訂 2023 年 3 月 21 日

Copyright (C) 2015-2023 Makoto Hiroi
All rights reserved.

[ PrevPage | Perl | NextPage ]