Perl包和模塊(內容來自beginning perl)

單文件版的perl程序只能用於構建較小的腳本程序。當代碼規模較大時,應該遵循下面兩條規則來構建程序。這樣能將程序的各個部分按功能一個一個地細化,便於維護,也便於後續開發。數組

能複用的代碼放進函數
能複用的函數放進模塊

名稱空間和包

名稱空間用於組織邏輯邏輯代碼和數據,一個名稱空間由一個包名,包內的全部子程序名以及包變量構成,出了這個名稱空間就沒法訪問該名稱空間內的內容,除非將其導入。有了包和名稱空間,就能夠避免名稱衝突問題。函數

包的名稱由0個或多個雙冒號分隔,如下都是有效的包名稱:測試

  • File::Find::Rule
  • Module::Starter
  • DBIx::Class
  • Moose
  • aliased

File::FindFile模塊沒有關係,File::Find::RuleFile::Find模塊也沒有任何關係,最多可能的關係是一個做者開發的模塊,方便區分,也可能不是同一個做者開發的,模塊名都是徹底獨立的。ui

模塊名應儘可能避免使用小寫字母命名(例如上面的aliased模塊),由於在使用use導入的時候,可能會被看成編譯指示詞。this

對於包名My::Number::Utilities,通常來講,它對應的文件是My/Number/Utilities.pm,它一般位於lib目錄下,即lib/My/Number/Utilities.pm。其中pm後綴文件表示一個perl模塊,一個模塊中能夠有多個包(實際上,一個包也能夠跨多個模塊文件)。儘管如此,但強烈建議一個模塊文件提供一個包,另一個建議是模塊文件名和包名稱應該要保持一致,雖然這不是必須的。prototype

(區分:模塊和包。模塊是文件,包是模塊內的程序(假設包在一個模塊內))debug

建立一個lib/My/Number目錄,而後建立一個名爲Utilities.pm的空文件。調試

$ mkdir -p lib/My/Number
$ touch lib/My/Number/Utilities.pm
$ tree lib/
lib/
└── My
    └── Number
        └── Utilities.pm

將如下代碼保存到Utilities.pm文件,其中is_prime子程序用於判斷數字是否爲質數(素數)。code

package My::Number::Utilities;

use strict;
use warnings;
our $VERSION = 0.01;

sub is_prime {
    my $number = $_[0];
    return if $number < 2;
    return 1 if $number == 2;
    for ( 2 .. int sqrt($number) ) {
        return if !($number % $_);
    }
    return 1;
}
1;

再在lib目錄的父目錄下建立一個perl程序文件listing_primes.pl,代碼以下:對象

use strict;
use warnings;
use diagnostics;

use lib 'lib';    # Perl we'll find modules in lib/

use My::Number::Utilities;
my @numbers = qw(
    3 2 39 7919 997 631 200
    7919 459 7919 623 997 867 15
);
my @primes = grep { My::Number::Utilities::is_prime($_) } @numbers;
print join ', ' => sort { $a <=> $b } @primes;

文件結構:

$ tree
.
├── lib
│   └── My
│       └── Number
│           └── Utilities.pm
└── list_primes.pl

而後執行:

$ perl list_primes.pl
2, 3, 631, 997, 997, 7919, 7919, 7919

回到上面的模塊文件Utilities.pm的代碼部分,這裏麪包含了建立模塊時的幾個規範語句:

package My::Number::Utilities;

use strict;
use warnings;
our $VERSION = 0.01;  # 設置模塊版本號

...這裏是模塊主代碼...

1;

第一行是包名My::Number::Utilities。它定義了該語句後面的全部內容(除了少數內容,如use指定的編譯指示strict、warnings)都屬於這個包。在此模塊文件中,整個文件直到文件尾部都屬於這個包範圍。若是這個包後面還發現了包定義語句,將進入新包的範圍。例如:

package My::Math;

use strict;
use warnings;
our $VERSION = 0.01;

sub sum {
    my @numbers = @_;
    my $total = 0;
    $total += $_ foreach @numbers;
    return $total;
}

# same file, different package
package My::Math::Strict;
use Scalar::Util 'looks_like_number';
our $VERSION = 0.01;
sub sum {
    my @numbers = @_;
    my $total = 0;
    $total += $_ foreach grep { looks_like_number($_) } @numbers;
    return $total;
}
1;

上面的模塊文件中定義了兩個包,兩個包中都定義了同名的sum()子程序,可是第一個sum子程序能夠經過My::Number::sum(@numbers)的方式調用,第二個sum子程序能夠經過My::Math::Strict::sum()的方式調用。但編譯指示strict和warnings是屬於整個文件的,也就是說兩個包都會收到這兩個指示的限定。

有時候想要限定包的做用域,只需將包放進一個代碼塊便可:

package My::Package;
use strict;
use warnings;
our $VERSION = 0.01;

{
    package My::Package::Debug;
    our $VERSION = 0.01;
    # this belongs to My::Package::Debug
    sub debug {
        # some debug routine
    }
}
# any code here belongs to My::Package;
1;

你可能已經注意到了,模塊文件的尾部老是使用1;結尾。當你定義一個模塊的時候,這個模塊文件必須返回一個真值,不然使用use導入模塊的時候,將會出現編譯錯誤(實際上require在運行時也會報錯)。通常來講,你們都喜歡在文件尾部使用一個1;來表明真值,但若是你使用其它字符的話(如'one'),可能會給出warning。

use VS. require

通常來講,當你須要導入一個模塊時,你可能會使用use語句:

use My::Number::Utilities;

use語句的用途很廣,對於模塊方面的功能來講,有如下幾種相關操做:

use VERSION
use Module VERSION LIST
use Module VERSION
use Module LIST
use Module

其中use VERSION告訴perl,運行最低多少版本的perl(也就是說能使用從哪一個版本以後的特性)。有幾種描述版本號的形式,例如想要perl以version 5.8.1或更高版本運行:

use v5.8.1;
use 5.8.1;
use 5.008_001;

版本號前綴的"v"要求以3部分數值形式描述版本號(稱爲v-string),不建議使用這種描述形式,由於可能會出問題。

另外,當使用use 5.11.0;或更高版本後,將直接隱含strict編譯指示,也就是說無需再寫use strict;

對於這幾種形式的use語句:

use Module
use Module LIST
use Module VERSION
use Module VERSION LIST

例如:

use Test::More;

Test::More模塊用於測試代碼。假如想要使用這個模塊中的某個功能subtest(),但這個功能直到改模塊的v0.96版纔開始提供,所以你能夠指定最低的模塊版本號。

use Test::More 0.96;
# 或
use Test::More v0.96.0;

當perl開始裝載Test::More的時候,會檢查該模塊的包變量$Test::More::VERSION,若是發現版本低於0.96,將自動觸發croak()。

強烈建議爲每一個模塊設置版本號our $VERSION=NUM;,這樣當發現某個版本(如0.01)的模塊有bug後,使用該模塊的程序能夠經過最低版本號(如0.02)來避免這個bug。

our $VERSION = 0.01;

use Test::More時,能夠接受一個導入列表。當使用use裝載一個模塊的時候,perl會自動搜索一個名爲import()的函數,而後將列表參數傳遞給import(),由import()實現導入的功能。所以,能夠這樣使用:

use Test::More tests => 13;

perl會將列表[tests,13]做爲參數傳遞給Test::More::import()

若是隻是裝載模塊,不想導入任何功能,能夠傳遞一個空列表:

use Test::More ();

最後,能夠結合版本號和導入的參數列表:

use Test::More 0.96 tests => 13;

除了使用use,還可使用require導入模塊(此外,eval、do均可以導入)。use語句是在編譯器進行模塊裝載的,而require是在運行時導入模塊文件的。

require My::Number::Utilities;

通常來講,除非必要,都只需使用use便可。但有時候爲了延遲裝載模塊,可使用require。例如,使用Data::Dumper模塊調試數據,想要只在某處失敗的時候裝載該模塊:

sub debug {
    my @args=@_;
    require Data::Dumper;
    Data::Dumper::Dumper(\@args);
}

這樣,只有在某處失敗,開始調用debug()的時候,纔會導入這個模塊,其餘時候都不會觸發該模塊,所以必須使用全名Data::Dumper::Dumper()

包變量

包變量有時候稱爲全局變量,雖然包自身是局部的,由於一個模塊文件中能夠定義多個包,在只有一個包的狀況下,它們確實是等價的概念,但即便一個文件中多個包的狀況下,包變量也是對全部外界可見的。

除了my修飾的對象,全部屬性、代碼都獨屬於各自所在的包(若是沒有聲明包,則是默認的main包),因此經過包名稱能夠找到包中的內容(my不屬於包,因此不能訪問)。

可使用徹底限定名稱或our來聲明屬於本包的包變量,甚至不加任何修飾符,但不加修飾符會被use strict阻止:

use strict;
use warnings;
use 5.010;

package My::Number::Utilities;

$My::Number::Utilities::PI=3.14;  # 聲明屬於本包的包變量
# 或者
# our $PI=3.14    # 聲明屬於本包的包變量
# 或者
# $PI=3.14        # 也是聲明包變量,但會被strict阻止而聲明失敗
say $My::Number::Utilities::PI;

our、my、local

可使用local和our兩個修飾符修飾變量。如下是my、local、our的區別:

  • my:將變量限定在一個代碼塊中。對於包範圍內的my變量,它是包私有的,其它包沒法訪問。my實現的是詞法做用域
  • our:聲明一個詞法做用域,但卻引用一個全局變量。換句話說,our能夠在給定做用域範圍內操做全局變量,退出做用域後仍然有效。和my接近,都是詞法做用域
    • 其實是在詞法做用域內定義一個和全局變量同名的詞法變量,這個詞法變量指向(引用)全局變量,因此修改這個詞法變量的同時是在修改全局變量,退出做用域的時候,這個詞法變量消失,但全局變量已經被修改了
  • local:臨時操做全局變量,給全局變量賦值,退出做用域後消失,並恢復原始的全局變量值。local實現的是動態做用域
    • 除了local這個詞語的意思和局部有關,在實際效果中和局部沒任何關係。若是非要理解成局部的意思,能夠認爲local將全局變量暫時改變成了局部變量,在退出局部環境後又恢復全局變量
  • 若是要修飾除了標量、數組、hash外的其它內容,只能使用local,例如修飾文件句柄(local FH),修飾typeglob
  • 當使用這3種修飾符時,若是隻是聲明沒有賦值,my和local會將對象初始化爲undef或空列表(),而our不會修改與之關聯的全局變量的值(由於它操做的就是全局變量)

my和our的異同:

  • 同:都聲明一個詞法做用域,退出做用域時詞法變量都消失
  • 同:都覆蓋全部已同名的命令
  • 異:my聲明的詞法變量存放在臨時範圍暫存器中,和包獨立
  • 異:our聲明的詞法變量,但操做的是全局變量(包變量)

our和local的異同:

  • 同:都操做全局變量
  • 異:our修改的全局變量在退出做用域後仍然有效
  • 異:local修改的全局變量在退出做用域後就恢復爲以前的全局變量

另外一種理解my/local/our的區別:

  • our confines names to a scope
  • local confines values to a scope
  • my confines both names and values to a scope

訪問和修改包變量

要訪問某個包中的變量,可使用徹底限定名稱$模塊::變量。但能夠在包中使用our語句修飾一個變量,使得這個變量能夠直接做爲包變量覆蓋詞法變量,直到退出做用域爲止。

例如,Data::Dumper模塊提供了控制模塊行爲的包變量:

use Data::Dumper;
# sort hash keys alphabetically
local $Data::Dumper::Sortkeys = 1;
# tighten up indentation
local $Data::Dumper::Indent = 1;
print Dumper(\%hash);

若是想要將包變量被別的包訪問,可讓別的包經過徹底限定名稱的形式。但這不是一個好主意,稍後會解釋。不過如今,你能夠訪問這些包變量:

package My::Number::Utilities;
use strict;
use warnings;
our $VERSION = 0.01;

$My::Number::Utilities::PI = 3.14159265359;
$My::Number::Utilities::E = 2.71828182846;
$My::Number::Uitlities::PHI = 1.61803398874; # golden ratio
@My::Number::Utilities::FIRST_PRIMES = qw(
    2 3 5 7 11 13 17 19 23 29
    31 37 41 43 47 53 59 61 67 71
);
sub is_prime {
#
}
1;

如你所見,定義了幾個包變量,但這裏隱藏了一個問題:$My::Number::Uitlities::PHI這個包變量的包名稱拼錯了。爲了不寫全包名容易出錯,因而使用our修飾詞聲明變量同時忽略包名:

our $PI = 3.14159265359;
our $E = 2.71828182846;
our $PHI = 1.61803398874; # golden ratio
our @FIRST_PRIMES = qw(
    2 3 5 7 11 13 17 19 23 29
    31 37 41 43 47 53 59 61 67 71
);

這時在其它包中也能經過徹底限定名稱訪問該包中的變量。

但必須注意的是,直接定義包變量是能直接被其它可訪問它的包修改的。例如,在list_primers.pl文件中從兩個包訪問My::Number::Utilities包中的our $VERSION=0.01

#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;
use 5.010;

use lib 'lib';
{
    use My::Number::Utilities;
    say "block1,1: ",$My::Number::Utilities::VERSION;  # 輸出:0.01
    $My::Number::Utilities::VERSION =3;
    say "block1,2: ",$My::Number::Utilities::VERSION;  # 輸出:3
}

say "line: ",$My::Number::Utilities::VERSION;  # 輸出:3

{
    use My::Number::Utilities;
    say "block2,1: ",$My::Number::Utilities::VERSION;  # 輸出:3
    $My::Number::Utilities::VERSION=4;
    say "block2,2: ",$My::Number::Utilities::VERSION;  # 輸出:4
}

上面使用了兩次use導入這個模塊,但實際上只在編譯期間導入了一次,因此每次訪問和操做的對象都是同一個目標。

爲了避免讓其它包修改這種常量型的數值,能夠經過子程序來定義它。例如:

sub pi {3.14};

而後這個值就成了只讀的值了。在其它想要獲取這個值的包中,只需執行這個函數便可:

package Universe::Roman;
use My::Number::Utilities;
my $PI = My::Number::Utilities::pi();

因此,除非必要,不要使用our定義包變量,以免被其它包修改。

Exporter導出模塊屬性

定義好一個模塊後,想要使用這個模塊中的屬性,可使用徹底限定名稱的方式。但徹底限定名稱畢竟比較長,寫起來比較麻煩,也比較容易出錯。

可使用Exporter模塊來導出模塊屬性,而後在使用模塊的其它文件中使用import()導入指定屬性。

例如,My::Number::Utilities模塊的內容以下:

package My::Number::Utilities;
use strict;
use warnings;
our $VERSION = 0.01;

use base 'Exporter';
our @EXPORT_OK = qw(pi is_prime);    # 導出屬性
our %EXPORT_TAGS = ( all => \@EXPORT_OK );  # 按標籤導出

sub pi() { 3.14166 }   # 設置爲null prototypes

sub is_prime {
    my $number = $_[0];
    return if $number < 2;
    return 1 if $number == 2;
    for ( 2 .. int sqrt($number) ) {
        return if !($number % $_);
    }
    return 1;
}
1;

該模塊將子程序pi()和is_prime()都進行了導出,此外還導出了一個名爲all的標籤,其中use base 'Exporter'表示繼承Exporter模塊。對於非面向對象的模塊來講,能夠不用使用繼承的方式實現一樣的效果use Exporter 'import';

而後其它程序就能夠導入該模塊已導出的子程序:

use My::Number::Utilities 'pi', 'is_prime';
use My::Number::Utilities 'is_prime';
use My::Number::Utilities qw(pi is_prime);  # 建議該方法
use My::Number::Utilities ();    # 什麼都不導入

當其它程序導入模塊的屬性列表時,perl會調用Exporter::import()方法,而後根據指定要導入的屬性列表搜索模塊My::Number::Utilities模塊的@EXPORT@EXPORT_OK%EXPORT_TAGS變量,只有存在於@EXPORT_OK@EXPORT中的屬性才能被導出,但強烈建議不要使用@EXPORT,由於它會導出全部函數給使用該模塊的程序,使得程序沒法控制、決定要導入哪些屬性,這可能會無心中導入一個和當前程序中同名的函數並覆蓋。

當導入的屬性列表是一個空列表時(即上面代碼的最後一行),表示不會調用import(),也就是什麼都不會去導入,僅僅只是裝載這個模塊,這時若是想要引用該模塊中的屬性,必須寫徹底限定名稱。

前面使用%EXPORT_TAGS定義了一個標籤all:

our %EXPORT_TAGS = ( all => \@EXPORT_OK );  # 按標籤導出

這是一個hash結構,hash的key是標籤名,value是要導出的屬性列表的引用。上面導出的是all標籤,其值是\@EXPORT_OK。當使用該模塊的時候,就能夠經過標籤來導入:

use My::Number::Utilities ':all';

當模塊中要導出的子程序較多的時候,使用標籤對函數進行分類,這樣在使用該模塊導入屬性時能夠按照標籤名導入而不用輸入大量的函數名。例如,導出一大堆的標籤:

our %EXPORT_TAGS = ( 
    all => \@EXPORT_OK,
    constant => [qw(pi phi e)],   # 導出常量
    cgi => [qw(get_ip get_uri get_host)],  # 導出CGI類的函數
);

而後導入的時候:

use My::Number::Utilities ':all';
use My::Number::Utilities qw(:constant :cgi);

空原型(null prototype):sub pi() { 3.14 }

當perl看到一個null prototype時,若是這個子程序的函數體很是簡單,perl在編譯時會嘗試直接用這個函數體的返回值替換這個函數的調用。例如:
use My::Number::Utilities 'pi';
print pi; # pi在編譯期間就會直接替換爲3.14

對於常量的導出,你可能會常常看到這樣的聲明方式:

our @EXPORT_OK = qw(PI E PHI);
use constant PI  => 3.14159265359;
use constant E   => 2.71828182846;
use constant PHI => 1.61803398874;

經過"constant"編譯指示,常量會被建立爲null prototype的子程序,也就是說,上面的代碼和下面的代碼是等價的:

our @EXPORT_OK = qw(pi e phi);
sub pi() { 3.14159265359 }
sub e() { 2.71828182846 }
sub phi() { 1.61803398874 }
相關文章
相關標籤/搜索