內容

名稱

perlmod - Perl 模組(套件和符號表)

說明

這是您要找的文件嗎?

還有其他文件可能包含您要找的資訊

此文件

Perl 的套件、命名空間和一些類別資訊。

perlnewmod

製作新模組的教學。

perlmodstyle

建立新模組的最佳實務。

套件

與 Perl 4 不同,在 Perl 4 中所有變數都是動態的,並共用一個全域名稱空間,導致維護問題,Perl 5 提供了兩種機制來保護程式碼,避免其變數被其他程式碼覆蓋:使用 mystate 建立的詞彙範圍變數,以及透過 vars 實用程式或 our 關鍵字公開的名稱空間全域變數。任何全域變數都被視為名稱空間的一部分,並可透過「完全限定形式」存取。相反地,任何詞彙範圍變數都被視為該詞彙範圍的一部分,且沒有「完全限定形式」。

在 perl 中,名稱空間稱為「套件」,而 package 宣告會告訴編譯器要將哪個名稱空間加為前綴,套用於 our 變數和非限定動態名稱。這既能防止意外覆蓋,又能提供一個介面,用於刻意覆蓋在其他範圍或套件中宣告和使用的全域動態變數(當您想要執行此操作時)。

package 宣告的範圍從宣告本身到封閉區塊、eval 或檔案的結尾,以先出現者為準(與 my()、our()、state() 和 local() 算子相同的範圍,以及實驗性「參考別名」的效果,可能會變更),或直到下一個 package 宣告。非限定動態識別碼將位於此名稱空間中,但有少數識別碼例外,如果未限定,則預設為主要套件,而非如以下所述的目前套件。package 陳述式只會影響動態全域符號,包括子常式名稱和您已使用 local() 的變數,但不會影響使用 my()、our() 或 state() 建立的詞彙變數。

通常,package 陳述式是檔案中第一個宣告,該檔案是由 dorequireuse 算子包含在程式中。您可以在多個地方切換到套件:package 除了指定編譯器將用於該區塊或直到下一個 package 陳述式的動態符號的符號表之外,沒有其他作用。您可以透過在識別碼前加上套件名稱和兩個冒號來參照其他套件中的變數和檔案句柄:$Package::Variable。如果套件名稱為空,則假設為 main 套件。也就是說,$::sail 等於 $main::sail

舊的套件分隔符號為單引號,但現在雙冒號是較佳的分隔符號,部分原因是人類較容易閱讀,部分原因是 emacs 巨集較容易閱讀。它也讓 C++ 程式設計師感覺他們知道發生了什麼事,與使用單引號作為分隔符號相反,單引號是讓 Ada 程式設計師感覺他們知道發生了什麼事。由於舊式語法仍支援向後相容性,如果您嘗試使用類似 "This is $owner's house" 的字串,您將存取 $owner::s;也就是套件 owner 中的 $s 變數,這可能不是您的本意。使用大括號來消除歧義,例如 "This is ${owner}'s house"

使用 ' 作為套件分隔符號已不建議使用,且將在 Perl 5.40 中移除。

套件本身可能包含套件分隔符號,例如 $OUTER::INNER::var。不過,這並不表示名稱查詢的順序。沒有相對套件:所有符號都屬於目前套件的區域,或必須從外部套件名稱向下完全限定。例如,在套件 OUTER 中沒有任何地方 $INNER::var 指向 $OUTER::INNER::varINNER 指向一個完全獨立的全球套件。將套件名稱視為階層的習慣很強烈,但語言絕不會強制執行。

只有以字母(或底線)開頭的識別碼儲存在套件的符號表中。所有其他符號都保留在套件 main 中,包括所有標點符號變數,例如 $_。此外,當沒有限定時,識別碼 STDIN、STDOUT、STDERR、ARGV、ARGVOUT、ENV、INC 和 SIG 會強制放在套件 main 中,即使用於內建目的以外的目的。如果您有一個稱為 msy 的套件,則您無法使用識別碼的限定形式,因為它會被解釋為樣式比對、替換或轉寫。

以前開頭為底線的變數會強制放入 main 套件中,但我們決定讓套件撰寫者能使用開頭底線來表示私有變數和方法名稱會更實用。不過,名稱為單一 _ 的變數和函式,例如 $_ 和 sub _,仍會強制放入 main 套件中。另請參閱 「perlvar 中的「變數名稱語法」

eval 的字串會在編譯 eval() 的套件中編譯。(不過,指派給 $SIG{} 會假設指定的訊號處理常式在 main 套件中。如果您希望在套件中有一個訊號處理常式,請限定訊號處理常式名稱。)例如,檢視 Perl 函式庫中的 perldb.pl。它最初會切換到 DB 套件,讓偵錯器不會干擾您嘗試偵錯的程式中的變數。不過,在不同的時間點,它會暫時切換回 main 套件,以在 main 套件(或您來自的任何地方)的內容中評估不同的表達式。請參閱 perldebug

特殊符號 __PACKAGE__ 包含目前的套件,但無法(輕鬆地)用於建構變數名稱。在 my($foo) 隱藏套件變數 $foo 之後,仍可以存取它,而不知道您在什麼套件中,例如 ${__PACKAGE__.'::foo'}

請參閱 perlsub 以了解與 my() 和 local() 相關的其他範圍問題,以及 perlref 以了解封閉元。

符號表

套件的符號表會儲存在名稱為該名稱加上兩個冒號的雜湊中。因此,主符號表的名稱為 %main::,或簡稱為 %::。同樣地,前面提到的巢狀套件的符號表名稱為 %OUTER::INNER::

雜湊中每個條目的值是您使用 *name 型態全域符號表示法時所指涉的內容。

local *main::foo    = *main::bar;

例如,您可以使用此功能列印出封包中的所有變數。標準但過時的 dumpvar.pl 函式庫和 CPAN 模組 Devel::Symdump 會使用此功能。

直接建立新的符號表項目或修改任何尚未成為 typeglob 的項目的結果未定義,且會隨著 perl 版本的不同而有所變更。

指定 typeglob 會執行別名處理作業,亦即

*dick = *richard;

會導致變數、子常式、格式以及可透過識別碼 richard 存取的檔案和目錄處理也透過識別碼 dick 存取。如果您只想將特定變數或子常式設為別名,請改為指定參考

*dick = \$richard;

這會讓 $richard 和 $dick 變成同一個變數,但 @richard 和 @dick 仍然是不同的陣列。很棘手,對吧?

下列陳述之間有一個細微的差別

*foo = *bar;
*foo = \$bar;

*foo = *bar 會讓 typeglob 本身變成同義詞,而 *foo = \$bar 會讓兩個不同 typeglob 的 SCALAR 部分指向同一個純量值。這表示下列程式碼

$bar = 1;
*foo = \$bar;       # Make $foo an alias for $bar

{
    local $bar = 2; # Restrict changes to block
    print $foo;     # Prints '1'!
}

會列印出「1」,因為 $foo 儲存對 原始 $bar 的參考。這個參考是由 local() 塞入的,而且會在區塊結束時還原。由於變數是透過 typeglob 存取的,您可以使用 *foo = *bar 建立可以在地端化的別名。(但請注意,這表示您不能有不同的 @foo@bar 等。)

之所以讓這一切變得重要,是因為 Exporter 模組使用 glob 別名作為匯入/匯出機制。您是否可以適當地在地端化從模組匯出的變數,取決於它是如何匯出的

@EXPORT = qw($FOO); # Usual form, can't be localized
@EXPORT = qw(*FOO); # Can be localized

您可以透過在需要當地值時使用完全限定名稱($Package::FOO),或在腳本中透過宣告 *FOO = *Package::FOO 來覆寫它,來解決第一個案例。

*x = \$y 機制可用於傳遞和傳回便宜的參考進入或離開子常式,如果您不想複製整個內容。它只在指定給動態變數時有效,不適用於字彙變數。

    %some_hash = ();			# can't be my()
    *some_hash = fn( \%another_hash );
    sub fn {
	local *hashsym = shift;
	# now use %hashsym normally, and you
	# will affect the caller's %another_hash
	my %nhash = (); # do what you want
	return \%nhash;
    }

在返回時,此參考會覆寫由 *some_hash 類型全域變數指定的符號表中的雜湊槽。當您不想要記得明確解除變數參考時,這是一個相當棘手的便宜傳遞參考的方式。

符號表的另一個用途是建立「常數」純量。

*PI = \3.14159265358979;

現在您無法變更 $PI,這在整體上來說可能是一件好事。這與常數子程式不同,常數子程式會在編譯時進行最佳化。常數子程式是一個原型,用於不帶任何引數,並傳回常數表達式。請參閱 perlsub 以取得這些詳細資訊。use constant 實用指令是這些的方便簡寫。

您可以說 *foo{PACKAGE}*foo{NAME} 來找出 *foo 符號表條目來自哪個名稱和套件。這在子程式中可能會很有用,因為它會將類型全域變數傳遞為引數

sub identify_typeglob {
    my $glob = shift;
    print 'You gave me ', *{$glob}{PACKAGE},
        '::', *{$glob}{NAME}, "\n";
}
identify_typeglob *foo;
identify_typeglob *bar::baz;

這會列印

You gave me main::foo
You gave me bar::baz

*foo{THING} 符號也可以用來取得 *foo 個別元素的參考。請參閱 perlref

子程式定義(以及宣告)不一定需要位於其符號表所在的套件中。您可以透過明確限定子程式的名稱來定義其套件外的子程式

package main;
sub Some_package::foo { ... }   # &foo defined in Some_package

這只是一個編譯時類型全域變數指派的簡寫

BEGIN { *Some_package::foo = sub { ... } }

而且等於寫

    {
	package Some_package;
	sub foo { ... }
    }

在前兩個版本中,子程式的內文在詞彙上位於主套件中,位於 Some_package 中。因此,像這樣

    package main;

    $Some_package::name = "fred";
    $main::name = "barney";

    sub Some_package::foo {
	print "in ", __PACKAGE__, ": \$name is '$name'\n";
    }

    Some_package::foo();

列印

in main: $name is 'barney'

而不是

in Some_package: $name is 'fred'

這也對 SUPER:: 限定詞的使用有影響(請參閱 perlobj)。

BEGIN、UNITCHECK、CHECK、INIT 和 END

在 Perl 程式執行開始和結束時,會執行五個特別命名的程式碼區塊。這些區塊是 BEGINUNITCHECKCHECKINITEND 區塊。

這些程式區塊可以加上 sub 前綴,以產生子常式的樣貌(儘管這不被認為是好的風格)。應該注意的是,這些程式區塊並未真正存在為已命名的子常式(儘管它們的樣貌如此)。揭露這一點的是,你可以在程式中擁有這些程式區塊的多個,而且它們將在適當的時刻全部執行。因此你無法依名稱執行任何這些程式區塊。

BEGIN 程式區塊會盡快執行,也就是說,在它完全定義的那一刻,甚至在解析包含檔案(或字串)的其餘部分之前。你可以在檔案(或已評估的字串)中擁有多個 BEGIN 區塊;它們將按照定義順序執行。由於 BEGIN 程式區塊會立即執行,因此它可以從其他檔案中提取子常式等定義,以便在編譯和執行時間的其餘部分中可見。一旦 BEGIN 執行,它就會立即未定義,並且它使用的任何程式碼都會傳回 Perl 的記憶體池。

END 程式區塊會盡可能晚執行,也就是說,在 Perl 完成執行程式之後,且在解釋器退出之前,即使它是因為 die() 函式而退出。(但如果它透過 exec 轉換成另一個程式,或被訊號炸毀,則不會執行——你必須自己攔截(如果你能的話)。)你可以在檔案中擁有多個 END 區塊——它們將按照定義的相反順序執行;也就是說:後進先出 (LIFO)。當你使用 -c 開關執行 Perl,或編譯失敗時,不會執行 END 區塊。

請注意,END 程式區塊不會在字串 eval() 的結尾執行:如果在字串 eval() 中建立任何 END 程式區塊,它們將在解釋器退出之前,按照 LIFO 順序執行,就像該套件的任何其他 END 程式區塊一樣。

END 程式區塊內,$? 包含程式將傳遞給 exit() 的值。你可以修改 $? 以變更程式的退出值。小心意外變更 $?(例如,透過 system 執行某些內容)。

END 區塊內,${^GLOBAL_PHASE} 的值會是 "END"

END 區塊類似的是 defer 區塊,儘管它們作用於個別區塊作用域的生命週期,而不是整個程式。它們在 "defer" in perlsyn 中有說明。

UNITCHECKCHECKINIT 程式碼區塊對於捕捉主程式編譯階段和執行階段之間的轉換很有用。

UNITCHECK 區塊在定義它們的單元編譯後立即執行。主程式檔案和它載入的每個模組都是編譯單元,就像字串 eval、使用正規表示式中的 (?{ }) 建構執行的執行時間程式碼、對 do FILErequire FILE 的呼叫,以及命令列上 -e 開關後的程式碼。

BEGINUNITCHECK 區塊與直譯器的階段沒有直接關係。它們可以在任何階段建立和執行。

CHECK 程式碼區塊在 Perl 初始編譯階段結束且執行時間開始之前,以 LIFO 順序執行。CHECK 程式碼區塊用於 Perl 編譯器套件中,以儲存程式的編譯狀態。

CHECK 區塊內,${^GLOBAL_PHASE} 的值會是 "CHECK"

INIT 區塊在 Perl 執行時間開始執行之前,以「先進先出」(FIFO) 順序執行。

INIT 區塊內,${^GLOBAL_PHASE} 的值會是 "INIT"

require、字串 do 或字串 eval 編譯的程式碼中的 CHECKINIT 區塊,如果發生在主編譯階段結束後,將不會執行;這在 mod_perl 和其他使用這些函數在執行時載入程式碼的持續性環境中會是個問題。

當您對 Perl 使用 -n-p 參數時,BEGINEND 的作用就像在 awk 中一樣,作為一個簡化案例。當您對僅編譯語法檢查使用 -c 參數時,BEGINCHECK 區塊都會執行,儘管您的主程式碼並不會執行。

begincheck 程式最終會讓一切都清楚

#!/usr/bin/perl

# begincheck

print         "10. Ordinary code runs at runtime.\n";

END { print   "16.   So this is the end of the tale.\n" }
INIT { print  " 7. INIT blocks run FIFO just before runtime.\n" }
UNITCHECK {
  print       " 4.   And therefore before any CHECK blocks.\n"
}
CHECK { print " 6.   So this is the sixth line.\n" }

print         "11.   It runs in order, of course.\n";

BEGIN { print " 1. BEGIN blocks run FIFO during compilation.\n" }
END { print   "15.   Read perlmod for the rest of the story.\n" }
CHECK { print " 5. CHECK blocks run LIFO after all compilation.\n" }
INIT { print  " 8.   Run this again, using Perl's -c switch.\n" }

print         "12.   This is anti-obfuscated code.\n";

END { print   "14. END blocks run LIFO at quitting time.\n" }
BEGIN { print " 2.   So this line comes out second.\n" }
UNITCHECK {
 print " 3. UNITCHECK blocks run LIFO after each file is compiled.\n"
}
INIT { print  " 9.   You'll see the difference right away.\n" }

print         "13.   It only _looks_ like it should be confusing.\n";

__END__

Perl 類別

Perl 中沒有特殊的類別語法,但一個套件可以作為一個類別,如果它提供子常式作為方法。這樣的套件也可以從另一個類別(套件)衍生一些方法,方法是在其全域 @ISA 陣列(必須是套件全域,而不是字彙)中列出其他套件名稱。

有關此內容的更多資訊,請參閱 perlootutperlobj

Perl 模組

模組只不過是函式庫檔案中的一組相關函數,即一個與檔案同名的 Perl 套件。它特別設計為可供其他模組或程式重複使用。它可以透過提供一種機制,將其一些符號匯出到使用它的任何套件的符號表中,或者它可以作為類別定義,並透過對類別及其物件的方法呼叫,隱式地提供其語意,而無需明確匯出任何內容。或者它可以同時執行這兩種操作。

例如,要啟動一個名為 Some::Module 的傳統非 OO 模組,請建立一個名為 Some/Module.pm 的檔案,並從這個範本開始

package Some::Module;  # assumes Some/Module.pm

use v5.36;

# Get the import method from Exporter to export functions and
# variables
use Exporter 5.57 'import';

# set the version for version checking
our $VERSION     = '1.00';

# Functions and variables which are exported by default
our @EXPORT      = qw(func1 func2);

# Functions and variables which can be optionally exported
our @EXPORT_OK   = qw($Var1 %Hashit func3);

# exported package globals go here
our $Var1    = '';
our %Hashit  = ();

# non-exported package globals go here
# (they are still accessible as $Some::Module::stuff)
our @more    = ();
our $stuff   = '';

# file-private lexicals go here, before any functions which use them
my $priv_var    = '';
my %secret_hash = ();

# here's a file-private function as a closure,
# callable as $priv_func->();
my $priv_func = sub {
    ...
};

# make all your functions, whether exported or not;
# remember to put something interesting in the {} stubs
sub func1      { ... }
sub func2      { ... }

# this one isn't always exported, but could be called directly
# as Some::Module::func3()
sub func3      { ... }

END { ... }       # module clean-up code here (global destructor)

1;  # don't forget to return a true value from the file

然後繼續在函數中宣告和使用您的變數,而無需任何限定。有關模組建立中的機制和樣式問題的詳細資訊,請參閱 Exporterperlmodlib

Perl 模組透過以下方式納入您的程式中

use Module;

use Module LIST;

這完全等於

BEGIN { require 'Module.pm'; 'Module'->import; }

BEGIN { require 'Module.pm'; 'Module'->import( LIST ); }

作為特殊情況

use Module ();

完全等於

BEGIN { require 'Module.pm'; }

所有 Perl 模組檔案的副檔名為 .pmuse 運算子假設如此,因此您不必在引號中拼寫出「Module.pm」。這也有助於區分新模組與舊的 .pl.ph 檔案。模組名稱也大寫,除非它們作為實用程式運作;實用程式實際上是編譯器指令,有時稱為「實用模組」(或如果您是古典主義者,則稱為「實用程式」)。

以下兩個陳述

require SomeModule;
require "SomeModule.pm";

在兩個方面有所不同。在第一個情況中,模組名稱中的任何雙冒號,例如 Some::Module,都會轉換為系統的目錄分隔符號,通常為「/」。第二個情況不會,並且必須明確指定。另一個不同之處在於,看到第一個 require 線索在編譯器中,涉及「SomeModule」的間接物件符號,例如 $ob = purge SomeModule,是方法呼叫,而不是函式呼叫。(是的,這確實會產生差異。)

由於 use 陳述暗示 BEGIN 區塊,因此語意的匯入在編譯 use 陳述時立即發生,在編譯檔案的其餘部分之前。這就是它能夠作為實用程式機制運作的方式,也是模組能夠宣告子常式的,這些子常式隨後可作為當前檔案其餘部分的清單或單元運算子。如果您使用 require 代替 use,則無法執行此操作。使用 require,您可能會遇到此問題

require Cwd;		# make Cwd:: accessible
$here = Cwd::getcwd();

use Cwd;			# import names from Cwd::
$here = getcwd();

require Cwd;	    	# make Cwd:: accessible
$here = getcwd(); 		# oops! no main::getcwd()

一般而言,建議使用 use Module () 而非 require Module,因為它在編譯時而非在程式執行過程中確定模組可用性。例外情況是,如果兩個模組各自分別嘗試 use 彼此,並且每個模組也從另一個模組呼叫函式。在這種情況下,很容易改用 require

Perl 套件可以巢狀在其他套件名稱內,因此我們可以有包含 :: 的套件名稱。但如果我們直接將該套件名稱用作檔案名稱,在某些系統上會產生難以處理或不可能的檔案名稱。因此,如果模組名稱為 Text::Soundex,則其定義實際上會在函式庫檔案 Text/Soundex.pm 中找到。

Perl 模組總是有一個 .pm 檔案,但可能也有動態連結的可執行檔 (通常以 .so 結尾) 或自動載入的子常式定義 (通常以 .al 結尾) 與模組相關聯。如果是這樣,這些對模組使用者來說將完全透明。載入 (或安排自動載入) 任何其他功能是 .pm 檔案的責任。例如,儘管 POSIX 模組碰巧同時執行動態載入和自動載入,但使用者只需說 use POSIX 即可取得所有功能。

讓您的模組執行緒安全

Perl 支援一種稱為解釋器執行緒 (ithreads) 的執行緒。這些執行緒可以明確和隱含地使用。

Ithreads 透過複製資料樹運作,以便不同執行緒之間不共用任何資料。這些執行緒可以使用 threads 模組或在 win32 上執行 fork() (偽造的 fork() 支援) 來使用。當複製執行緒時,所有 Perl 資料都會被複製,但非 Perl 資料無法自動複製。5.8.0 之後的 Perl 支援 CLONE 特殊子常式。在 CLONE 中,您可以執行任何需要執行的操作,例如在必要時處理非 Perl 資料的複製。CLONE 將作為類別方法針對每個已定義 (或繼承) 它的方法套件呼叫一次。它將在新的執行緒環境中呼叫,因此所有修改都在新的區域中進行。目前,CLONE 是在沒有呼叫者套件名稱以外的參數下呼叫的,但程式碼不應假設這將保持不變,因為未來很可能會傳遞額外的參數來提供有關複製狀態的更多資訊。

如果您想要複製所有物件,您需要按套件追蹤它們。這只需使用雜湊和 Scalar::Util::weaken() 即可完成。

Perl 5.8.7 之後支援 CLONE_SKIP 特殊子例程。與 CLONE 相似,CLONE_SKIP 在每個套件中呼叫一次;然而,它是在複製開始之前,以及在父執行緒的背景下呼叫。如果它傳回真值,則不會複製該類別的任何物件;或者更確切地說,它們將作為未祝福的未定義值複製。例如:如果在父項中有一個單一祝福雜湊的兩個參照,則在子項中將會有兩個參照指向一個未定義的標量值。這提供了一個簡單的機制來使模組執行緒安全;只要在類別的頂端加入 sub CLONE_SKIP { 1 },而 DESTROY() 現在只會在每個物件中呼叫一次。當然,如果子執行緒需要使用物件,則需要更精密的做法。

CLONE 相似,CLONE_SKIP 目前呼叫時沒有呼叫套件名稱以外的參數,儘管這可能會改變。同樣地,為了允許未來的擴充,傳回值應為單一的 01 值。

另請參閱

請參閱 perlmodlib 以了解與建置 Perl 模組和類別相關的一般樣式問題,以及標準函式庫和 CPAN 的說明,Exporter 以了解 Perl 的標準匯入/匯出機制如何運作,perlootutperlobj 以深入了解如何建立類別,perlobj 以取得物件的硬核參考文件,perlsub 以了解函式和範圍,以及 perlxstutperlguts 以取得更多有關撰寫擴充模組的資訊。