內容

名稱

Attribute::Handlers - 屬性處理常式的較簡單定義

版本

本文件說明 Attribute::Handlers 的 1.03 版。

語法

    package MyClass;
    require 5.006;
    use Attribute::Handlers;
    no warnings 'redefine';


    sub Good : ATTR(SCALAR) {
	my ($package, $symbol, $referent, $attr, $data) = @_;

	# Invoked for any scalar variable with a :Good attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.

	# Do whatever to $referent here (executed in CHECK phase).
	...
    }

    sub Bad : ATTR(SCALAR) {
	# Invoked for any scalar variable with a :Bad attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.
	...
    }

    sub Good : ATTR(ARRAY) {
	# Invoked for any array variable with a :Good attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.
	...
    }

    sub Good : ATTR(HASH) {
	# Invoked for any hash variable with a :Good attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.
	...
    }

    sub Ugly : ATTR(CODE) {
	# Invoked for any subroutine declared in MyClass (or a 
	# derived class) with an :Ugly attribute.
	...
    }

    sub Omni : ATTR {
	# Invoked for any scalar, array, hash, or subroutine
	# with an :Omni attribute, provided the variable or
	# subroutine was declared in MyClass (or a derived class)
	# or the variable was typed to MyClass.
	# Use ref($_[2]) to determine what kind of referent it was.
	...
    }


    use Attribute::Handlers autotie => { Cycle => Tie::Cycle };

    my $next : Cycle(['A'..'Z']);

說明

當繼承一個套件時,此模組允許該套件的類別為特定屬性定義屬性處理常式子常式。隨後在該套件中定義的變數和子常式,或從該套件衍生的套件中定義的變數和子常式,可以賦予與屬性處理常式子常式名稱相同的屬性,然後將在編譯階段之一(即在 BEGINCHECKINITEND 區塊中)呼叫這些屬性處理常式子常式。(UNITCHECK 區塊不對應到全域編譯階段,因此無法在此處指定。)

若要建立處理常式,請將其定義為與所需屬性同名的子常式,並使用屬性 :ATTR 宣告子常式本身。例如

    package LoudDecl;
    use Attribute::Handlers;

    sub Loud :ATTR {
	my ($package, $symbol, $referent, $attr, $data, $phase,
	    $filename, $linenum) = @_;
	print STDERR
	    ref($referent), " ",
	    *{$symbol}{NAME}, " ",
	    "($referent) ", "was just declared ",
	    "and ascribed the ${attr} attribute ",
	    "with data ($data)\n",
	    "in phase $phase\n",
	    "in file $filename at line $linenum\n";
    }

這會在 LoudDecl 類別中為屬性 :Loud 建立一個處理常式。此後,在 LoudDecl 類別中使用 :Loud 屬性宣告的任何子常式

package LoudDecl;

sub foo: Loud {...}

都會呼叫上述處理常式,並傳遞

[0]

宣告此子常式的套件名稱;

[1]

包含此子常式的符號表項目 (typeglob) 參考;

[2]

此子常式的參考;

[3]

屬性的名稱;

[4]

與該屬性關聯的任何資料;

[5]

呼叫處理常式的階段名稱;

[6]

呼叫處理常式的檔案名稱;

[7]

此檔案中的行號。

同樣地,在套件中宣告任何具有 :Loud 屬性的變數

package LoudDecl;

my $foo :Loud;
my @foo :Loud;
my %foo :Loud;

會導致呼叫處理常式,並傳遞類似的引數清單 (當然,$_[2] 會是變數的參考)。

套件名稱引數通常會是宣告子常式的類別名稱,但它也可能是衍生類別的名稱 (因為處理常式會被繼承)。

如果詞彙變數被賦予屬性,則沒有它所屬的符號表,因此在這種情況下,符號表引數 ($_[1]) 會設定為字串 'LEXICAL'。同樣地,將屬性指定給匿名子常式會導致符號表引數為 'ANON'

資料引數會傳入與屬性關聯的值 (如果有)。例如,如果宣告了 &foo

sub foo :Loud("turn it up to 11, man!") {...}

則包含字串 "turn it up to 11, man!" 的陣列參考會作為最後一個引數傳遞。

Attribute::Handlers 會盡力在將資料引數 ($_[4]) 傳遞給處理常式之前,將其轉換為可用的格式 (但請參閱 "非詮釋性屬性處理常式")。如果這些努力成功,則會在陣列參考中傳遞已詮釋的資料;如果失敗,則會以字串形式傳遞原始資料。例如,所有這些

sub foo :Loud(till=>ears=>are=>bleeding) {...}
sub foo :Loud(qw/till ears are bleeding/) {...}
sub foo :Loud(qw/till, ears, are, bleeding/) {...}
sub foo :Loud(till,ears,are,bleeding) {...}

都會導致它傳遞 ['till','ears','are','bleeding'] 作為處理常式的資料引數。而

sub foo :Loud(['till','ears','are','bleeding']) {...}

會導致它傳遞 [ ['till','ears','are','bleeding'] ];資料中指定的陣列參考傳遞在標準陣列參考內,表示詮釋成功。

但是,如果資料無法解析為有效的 Perl,則會將其作為未詮釋的字串傳遞。例如

sub foo :Loud(my,ears,are,bleeding) {...}
sub foo :Loud(qw/my ears are bleeding) {...}

導致字串 'my,ears,are,bleeding''qw/my ears are bleeding' 分別作為資料引數傳遞。

如果沒有值與屬性關聯,則傳遞 undef

類型化詞法變數

不論在何個套件中宣告,如果詞法變數被指定屬性,則呼叫的處理常式屬於它被類型化的套件。例如,下列宣告

package OtherClass;

my LoudDecl $loudobj : Loud;
my LoudDecl @loudobjs : Loud;
my LoudDecl %loudobjex : Loud;

導致呼叫 LoudDecl::Loud 處理常式(即使 OtherClass 也為 :Loud 屬性定義處理常式)。

類型特定屬性處理常式

如果宣告屬性處理常式,且 :ATTR 規格說明內建類型(SCALARARRAYHASHCODE)的名稱,則處理常式只套用於該類型的宣告。例如,下列定義

package LoudDecl;

sub RealLoud :ATTR(SCALAR) { print "Yeeeeow!" }

建立只套用於純量的屬性處理常式

package Painful;
use base LoudDecl;

my $metal : RealLoud;           # invokes &LoudDecl::RealLoud
my @metal : RealLoud;           # error: unknown attribute
my %metal : RealLoud;           # error: unknown attribute
sub metal : RealLoud {...}      # error: unknown attribute

當然,您也可以為這些類型宣告個別處理常式(但您需要指定 no warnings 'redefine' 來靜默執行)。

package LoudDecl;
use Attribute::Handlers;
no warnings 'redefine';

sub RealLoud :ATTR(SCALAR) { print "Yeeeeow!" }
sub RealLoud :ATTR(ARRAY) { print "Urrrrrrrrrr!" }
sub RealLoud :ATTR(HASH) { print "Arrrrrgggghhhhhh!" }
sub RealLoud :ATTR(CODE) { croak "Real loud sub torpedoed" }

您也可以明確指出單一處理常式用於所有類型參照,如下所示

package LoudDecl;
use Attribute::Handlers;

sub SeriousLoud :ATTR(ANY) { warn "Hearing loss imminent" }

(也就是說,ATTR(ANY):ATTR 的同義詞。)

非詮釋性屬性處理常式

偶爾,Attribute::Handlers 努力將資料引數($_[4])轉換成可用格式,在傳遞給處理常式之前,會遇到阻礙。

您可以透過使用關鍵字 RAWDATA 宣告屬性處理常式來關閉這種熱心助人的行為。例如

sub Raw          : ATTR(RAWDATA) {...}
sub Nekkid       : ATTR(SCALAR,RAWDATA) {...}
sub Au::Naturale : ATTR(RAWDATA,ANY) {...}

然後,處理常式絕對不會嘗試詮釋它接收的資料,而只是將其作為字串傳遞

my $power : Raw(1..100);        # handlers receives "1..100"

階段特定屬性處理常式

預設情況下,屬性處理常式在編譯階段結束時(在 CHECK 區塊中)被呼叫。在大部分情況下,這似乎是最理想的,因為大多數可以定義的東西都在那時定義,但尚未執行任何內容。

不過,可以設定在程式編譯或執行中的其他時間點呼叫的屬性處理常式,方法是明確指出您希望呼叫屬性處理常式的階段(或階段)。例如

sub Early    :ATTR(SCALAR,BEGIN) {...}
sub Normal   :ATTR(SCALAR,CHECK) {...}
sub Late     :ATTR(SCALAR,INIT) {...}
sub Final    :ATTR(SCALAR,END) {...}
sub Bookends :ATTR(SCALAR,BEGIN,END) {...}

如同最後一個範例所示,可以設定處理常式在兩個或更多階段被(重新)呼叫。階段名稱傳遞為處理常式的最後一個引數。

請注意,排程為 BEGIN 階段的屬性處理常式會在偵測到屬性時立即處理(即在執行任何後續定義的 BEGIN 區塊之前)。

屬性作為 tie 介面

屬性會產生一個絕佳且直覺的介面,可透過它來繫結變數。例如

    use Attribute::Handlers;
    use Tie::Cycle;

    sub UNIVERSAL::Cycle : ATTR(SCALAR) {
	my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
	$data = [ $data ] unless ref $data eq 'ARRAY';
	tie $$referent, 'Tie::Cycle', $data;
    }

    # and thereafter...

    package main;

    my $next : Cycle('A'..'Z');     # $next is now a tied variable

    while (<>) {
	print $next;
    }

請注意,由於 Cycle 屬性在 $data 變數中接收其引數,因此如果屬性給定一個引數清單,$data 將包含一個單一陣列參考;否則,它將直接包含單一引數。由於 Tie::Cycle 要求其循環值傳遞為陣列參考,這表示我們需要將非陣列參考引數包裝在陣列建構函式中

$data = [ $data ] unless ref $data eq 'ARRAY';

然而,通常情況正好相反:可繫結類別預期其引數為扁平化清單,因此屬性看起來像

    sub UNIVERSAL::Cycle : ATTR(SCALAR) {
	my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
	my @data = ref $data eq 'ARRAY' ? @$data : $data;
	tie $$referent, 'Tie::Whatever', @data;
    }

這種軟體模式廣泛適用,因此 Attribute::Handlers 提供一種自動化方式:在 use Attribute::Handlers 陳述式中指定 'autotie'。因此,循環範例也可以寫成

    use Attribute::Handlers autotie => { Cycle => 'Tie::Cycle' };

    # and thereafter...

    package main;

    my $next : Cycle(['A'..'Z']);     # $next is now a tied variable

    while (<>) {
	print $next;
    }

請注意,我們現在必須傳遞循環值作為陣列參考,因為 autotie 機制將 tie 引數清單傳遞為清單(如 Tie::Whatever 範例中),而不是作為陣列參考(如本節開頭處的原始 Tie::Cycle 範例中)。

'autotie' 之後的引數是對雜湊的參考,其中每個鍵是待建立屬性的名稱,每個值是應繫結到指定該屬性的變數的類別。

請注意,不再需要匯入 Tie::Cycle 模組——Attribute::Handlers 會自動處理。您甚至可以透過將引數附加到類別名稱,傳遞給模組的 import 子常式。例如

    use Attribute::Handlers
	 autotie => { Dir => 'Tie::Dir qw(DIR_UNLINK)' };

如果屬性名稱沒有限定,則屬性會安裝在目前的套件中。否則,它會安裝在限定詞的套件中

package Here;

use Attribute::Handlers autotie => {
     Other::Good => Tie::SecureHash, # tie attr installed in Other::
             Bad => Tie::Taxes,      # tie attr installed in Here::
 UNIVERSAL::Ugly => Software::Patent # tie attr installed everywhere
};

自動繫結最常使用在實際繫結的模組中,並且需要將其屬性匯出到任何呼叫它們的模組。為了便於執行此操作,Attribute::Handlers 識別一個特殊的「偽類別」——__CALLER__,可以指定為屬性的限定詞

    package Tie::Me::Kangaroo::Down::Sport;

    use Attribute::Handlers autotie =>
	 { '__CALLER__::Roo' => __PACKAGE__ };

這會導致 Attribute::Handlers 在匯入 Tie::Me::Kangaroo::Down::Sport 模組的套件中定義 Roo 屬性。

請注意,引用 __CALLER__::Roo 識別項很重要,因為 perl 5.8 中的錯誤會拒絕解析它並導致未知錯誤。

傳遞繫結物件給 tie

偶爾,將繫結到 TIESCALAR、TIEHASH 等的物件參考傳遞給繫結它的物件非常重要。

autotie 機制也支援此功能。下列程式碼

use Attribute::Handlers autotieref => { Selfish => Tie::Selfish };
my $var : Selfish(@args);

與下列程式碼具有相同效果

tie my $var, 'Tie::Selfish', @args;

但當 "autotieref" 用於 "autotie"

use Attribute::Handlers autotieref => { Selfish => Tie::Selfish };
my $var : Selfish(@args);

效果是傳遞 tie 呼叫一個額外的變數參考給被綁定的變數

tie my $var, 'Tie::Selfish', \$var, @args;

範例

如果在 "SYNOPSIS" 中顯示的類別被放置在 MyClass.pm 模組中,則下列程式碼

package main;
use MyClass;

my MyClass $slr :Good :Bad(1**1-1) :Omni(-vorous);

package SomeOtherClass;
use base MyClass;

sub tent { 'acle' }

sub fn :Ugly(sister) :Omni('po',tent()) {...}
my @arr :Good :Omni(s/cie/nt/);
my %hsh :Good(q/bye/) :Omni(q/bus/);

將導致呼叫下列處理常式

# my MyClass $slr :Good :Bad(1**1-1) :Omni(-vorous);

MyClass::Good:ATTR(SCALAR)( 'MyClass',          # class
                            'LEXICAL',          # no typeglob
                            \$slr,              # referent
                            'Good',             # attr name
                            undef               # no attr data
                            'CHECK',            # compiler phase
                          );

MyClass::Bad:ATTR(SCALAR)( 'MyClass',           # class
                           'LEXICAL',           # no typeglob
                           \$slr,               # referent
                           'Bad',               # attr name
                           0                    # eval'd attr data
                           'CHECK',             # compiler phase
                         );

MyClass::Omni:ATTR(SCALAR)( 'MyClass',          # class
                            'LEXICAL',          # no typeglob
                            \$slr,              # referent
                            'Omni',             # attr name
                            '-vorous'           # eval'd attr data
                            'CHECK',            # compiler phase
                          );


# sub fn :Ugly(sister) :Omni('po',tent()) {...}

MyClass::UGLY:ATTR(CODE)( 'SomeOtherClass',     # class
                          \*SomeOtherClass::fn, # typeglob
                          \&SomeOtherClass::fn, # referent
                          'Ugly',               # attr name
                          'sister'              # eval'd attr data
                          'CHECK',              # compiler phase
                        );

MyClass::Omni:ATTR(CODE)( 'SomeOtherClass',     # class
                          \*SomeOtherClass::fn, # typeglob
                          \&SomeOtherClass::fn, # referent
                          'Omni',               # attr name
                          ['po','acle']         # eval'd attr data
                          'CHECK',              # compiler phase
                        );


# my @arr :Good :Omni(s/cie/nt/);

MyClass::Good:ATTR(ARRAY)( 'SomeOtherClass',    # class
                           'LEXICAL',           # no typeglob
                           \@arr,               # referent
                           'Good',              # attr name
                           undef                # no attr data
                           'CHECK',             # compiler phase
                         );

MyClass::Omni:ATTR(ARRAY)( 'SomeOtherClass',    # class
                           'LEXICAL',           # no typeglob
                           \@arr,               # referent
                           'Omni',              # attr name
                           ""                   # eval'd attr data 
                           'CHECK',             # compiler phase
                         );


# my %hsh :Good(q/bye) :Omni(q/bus/);

MyClass::Good:ATTR(HASH)( 'SomeOtherClass',     # class
                          'LEXICAL',            # no typeglob
                          \%hsh,                # referent
                          'Good',               # attr name
                          'q/bye'               # raw attr data
                          'CHECK',              # compiler phase
                        );

MyClass::Omni:ATTR(HASH)( 'SomeOtherClass',     # class
                          'LEXICAL',            # no typeglob
                          \%hsh,                # referent
                          'Omni',               # attr name
                          'bus'                 # eval'd attr data
                          'CHECK',              # compiler phase
                        );

在 UNIVERSAL 中安裝處理常式,讓它們成為...呃...通用的。例如

package Descriptions;
use Attribute::Handlers;

my %name;
sub name { return $name{$_[2]}||*{$_[1]}{NAME} }

sub UNIVERSAL::Name :ATTR {
    $name{$_[2]} = $_[4];
}

sub UNIVERSAL::Purpose :ATTR {
    print STDERR "Purpose of ", &name, " is $_[4]\n";
}

sub UNIVERSAL::Unit :ATTR {
    print STDERR &name, " measured in $_[4]\n";
}

讓您撰寫

use Descriptions;

my $capacity : Name(capacity)
             : Purpose(to store max storage capacity for files)
             : Unit(Gb);


package Other;

sub foo : Purpose(to foo all data before barring it) { }

# etc.

公用函式

此模組提供單一公用函式 findsym()

findsym
my $symbol = Attribute::Handlers::findsym($package, $referent);

此函式在 $package 的符號表中尋找 $referent 的 typeglob,它是一個變數或子常式的參考 (SCALAR、ARRAY、HASH 或 CODE)。如果它找到 typeglob,它會傳回它。否則,它會傳回 undef。請注意,findsym 會記住它先前成功找到的 typeglob,因此使用相同引數的後續呼叫應該會快很多。

診斷

錯誤的屬性類型:ATTR(%s)

屬性處理常式已使用 :ATTR(ref_type) 指定,但它定義為處理的參考類型不是五種許可類型之一:SCALARARRAYHASHCODEANY

屬性處理常式 %s 不處理 %s 屬性

已定義指定名稱 的屬性處理常式,但未定義為處理指定類型的宣告。通常在嘗試將 VAR 屬性處理常式套用至子常式,或將 SCALAR 屬性處理常式套用至其他類型的變數時會遇到。

在套件 %s 中宣告 %s 屬性可能會與未來的保留字衝突

已宣告一個名稱全部為小寫的屬性處理常式。名稱全部為小寫的屬性可能在某天對 Perl 本身具有意義,即使目前大多數沒有。請改用混合大小寫的屬性名稱。

一個子常式不能有兩個 ATTR 規格說明

你就是不能,好嗎?請改為將所有規格說明放在一起,並在單一 ATTR(specification) 中用逗號分隔它們。

無法自動繫結 %s

您只能為 "SCALAR""ARRAY""HASH" 類型宣告自動化。它們是 Perl 可以繫結的唯一項目(除了無法宣告的類型 glob)。

內部錯誤:%s 符號遺失

程式狀態有誤。一個帶有屬性的子常式在宣告和呼叫其屬性處理常式之間消失。

無法套用 END 處理常式

您已為套用至詞彙變數的屬性定義 END 處理常式。由於變數在 END 期間可能不可用,因此不會執行此操作。

作者

Damian Conway (damian@conway.org)。此模組的維護者現為 Rafael Garcia-Suarez (rgarciasuarez@gmail.com)。

CPAN 發行的維護者為 Steffen Mueller (smueller@cpan.org)。如有 CPAN 模組封裝方面的技術問題,請聯絡他。

錯誤

毫無疑問,這段時髦的程式碼中潛藏著嚴重的錯誤 :-) 歡迎回報錯誤和其他意見回饋。

著作權和授權

  Copyright (c) 2001-2014, Damian Conway. All Rights Reserved.
This module is free software. It may be used, redistributed
    and/or modified under the same terms as Perl itself.